
Dingtalk Todo
- 377 installs
- 97 repo stars
- Updated June 26, 2026
- breath57/dingtalk-skills
dingtalk-todo is a Claude Code skill that creates, assigns, updates, and tracks DingTalk todos from coding sessions for developers who automate enterprise task handoff.
About
dingtalk-todo is a Claude Code skill from breath57/dingtalk-skills that connects agent sessions to DingTalk's todo APIs. The skill lets agents create tasks, assign owners, update status, and track completion while you stay inside a coding or review workflow. It targets teams on DingTalk who want engineering work—bug fixes, refactors, follow-ups—captured as trackable todos instead of chat-only promises. Typical flows start from a PR comment, incident note, or standup summary and end with structured todos linked to the right assignees. Reach for dingtalk-todo when you need programmatic task creation from Claude or Cursor during development, especially in enterprise environments where DingTalk is the system of record for cross-team handoffs. The skill maps coding-session context—stack traces, PR links, owner handles—into DingTalk fields so tasks stay traceable across sprints. It reduces context loss when agents finish a fix but follow-ups must persist in the enterprise task board.
- DingTalk todo CRUD flows
- Task assignment automation
- Agent-created work items
- Status and due-date sync
- Enterprise workflow triggers
Dingtalk Todo by the numbers
- 377 all-time installs (skills.sh)
- Ranked #461 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/breath57/dingtalk-skills --skill dingtalk-todoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 377 |
|---|---|
| repo stars | ★ 97 |
| Last updated | June 26, 2026 |
| Repository | breath57/dingtalk-skills ↗ |
How do you create DingTalk todos from a coding agent?
Let agents create, assign, update, and track DingTalk todos from coding sessions to automate task handoff across enterprise teams.
Who is it for?
Developers on DingTalk who want agents to file and track engineering todos during active coding or review sessions.
Skip if: Teams not using DingTalk or developers who only need local TODO comments without enterprise task-system integration.
When should I use this skill?
The user asks to create, assign, update, or track DingTalk todos from a development session.
What you get
DingTalk todo records with assignees, status updates, and tracked completion inside enterprise task lists.
Files
钉钉待办技能
负责钉钉待办(Todo)的所有操作。本文件为策略指南,仅包含决策逻辑和工作流程。完整 API 请求格式见文末「references/api.md 查阅索引」。
---
工作流程(每次执行前)
1. 读取配置 → 用一条 grep -E 命令一次性读取配置文件~/.dingtalk-skills/config, 所有所需配置键值(配置文件跨会话保留,无需重复询问) 2. 仅收集缺失配置 → 若配置文件不存在或缺少某项,一次性询问用户所有缺失的值,不要逐条问 3. 持久化 → 将收集到的值写入 ~/.dingtalk-skills/config 文件,后续无需再问 4. 获取/复用 Token → 有效期内复用缓存(缓存 7000 秒,约 2 小时),避免重复请求;遇 401 重新获取 5. 执行操作 → 凡是包含变量替换、管道或多行逻辑的命令,/tmp/<task>.sh 再 bash /tmp/<task>.sh 执行。不要把多行命令直接粘到终端里(终端工具会截断),也不要用 <<'EOF' 语法(heredoc 在工具中同样会被截断导致变量丢失)
凭证禁止在输出中完整打印,确认时仅显示前 4 位 + ****所需配置
| 配置键 | 说明 | 如何获取 |
|---|---|---|
DINGTALK_APP_KEY | 应用 AppKey | 钉钉开放平台 → 应用管理 → 凭证信息 |
DINGTALK_APP_SECRET | 应用 AppSecret | 同上 |
DINGTALK_MY_USER_ID | 当前用户的企业员工 ID(userId) | 管理后台 → 通讯录 → 成员管理 → 点击姓名查看(不是手机号、不是 unionId) |
DINGTALK_MY_OPERATOR_ID | 当前用户的 unionId | 首次由脚本自动通过 userId 转换获取并写入 |
身份标识说明
钉钉有两种用户 ID,不同 API 使用不同的 ID:
| 标识 | 说明 | 如何获取 |
|---|---|---|
userId(= staffId) | 企业内部员工 ID,最容易获取 | 管理后台 → 通讯录 → 成员管理 → 点击姓名查看;或调用手机号查询 API |
unionId | 跨企业/跨应用唯一 | 通过 userId 调用 API 转换获取 |
- 待办 API 的路径参数 `{unionId}` 和查询参数 `operatorId` 均使用 unionId
- executorIds / participantIds(指派同事)也使用 unionId
- 因此配置中优先收集
userId(用户容易拿到),由脚本自动转换为unionId
userId → unionId 转换
需要旧版 access_token(与新版不同):
# 1. 获取旧版 token
OLD_TOKEN=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
# 2. userId → unionId
UNION_ID=$(curl -s -X POST "https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${OLD_TOKEN}" \
-H 'Content-Type: application/json' \
-d "{\"userid\":\"${USER_ID}\"}" | grep -o '"unionid":"[^"]*"' | cut -d'"' -f4)
# 3. 写入配置文件
echo "DINGTALK_MY_OPERATOR_ID=$UNION_ID" >> ~/.dingtalk-skills/config⚠️ 注意:返回体中result.unionid(无下划线)有值,result.union_id(有下划线)可能为空。
给同事创建待办时
如果用户要给同事创建待办(指定 executorIds),需要同事的 unionId。向用户询问同事的 userId(管理后台可查),然后用上述方法转换。
执行脚本模板
#!/bin/bash
set -e
CONFIG=~/.dingtalk-skills/config
APP_KEY=$(grep '^DINGTALK_APP_KEY=' "$CONFIG" | cut -d= -f2-)
APP_SECRET=$(grep '^DINGTALK_APP_SECRET=' "$CONFIG" | cut -d= -f2-)
USER_ID=$(grep '^DINGTALK_MY_USER_ID=' "$CONFIG" | cut -d= -f2-)
# 新版 Token 缓存(用于待办 API)
CACHED_TOKEN=$(grep '^DINGTALK_ACCESS_TOKEN=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
TOKEN_EXPIRY=$(grep '^DINGTALK_TOKEN_EXPIRY=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
NOW=$(date +%s)
if [ -n "$CACHED_TOKEN" ] && [ -n "$TOKEN_EXPIRY" ] && [ "$NOW" -lt "$TOKEN_EXPIRY" ]; then
TOKEN=$CACHED_TOKEN
else
RESP=$(curl -s -X POST https://api.dingtalk.com/v1.0/oauth2/accessToken \
-H 'Content-Type: application/json' \
-d "{\"appKey\":\"$APP_KEY\",\"appSecret\":\"$APP_SECRET\"}")
TOKEN=$(echo "$RESP" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
sed -i '/^DINGTALK_ACCESS_TOKEN=/d;/^DINGTALK_TOKEN_EXPIRY=/d' "$CONFIG"
echo "DINGTALK_ACCESS_TOKEN=$TOKEN" >> "$CONFIG"
echo "DINGTALK_TOKEN_EXPIRY=$((NOW + 7000))" >> "$CONFIG"
fi
# unionId:优先从配置读取,未存储时自动从 userId 转换并写入
UNION_ID=$(grep '^DINGTALK_MY_OPERATOR_ID=' "$CONFIG" 2>/dev/null | cut -d= -f2-)
if [ -z "$UNION_ID" ]; then
OLD_TOKEN=$(curl -s "https://oapi.dingtalk.com/gettoken?appkey=${APP_KEY}&appsecret=${APP_SECRET}" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
UNION_ID=$(curl -s -X POST "https://oapi.dingtalk.com/topapi/v2/user/get?access_token=${OLD_TOKEN}" \
-H 'Content-Type: application/json' \
-d "{\"userid\":\"${USER_ID}\"}" | grep -o '"unionid":"[^"]*"' | cut -d'"' -f4)
echo "DINGTALK_MY_OPERATOR_ID=$UNION_ID" >> "$CONFIG"
fi
# 在此追加具体 API 调用,例如创建待办:
RESULT=$(curl -s -X POST \
"https://api.dingtalk.com/v1.0/todo/users/${UNION_ID}/tasks?operatorId=${UNION_ID}" \
-H "x-acs-dingtalk-access-token: $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"subject\":\"今天完成需求评审\"}")
echo "$RESULT"
TASK_ID=$(echo "$RESULT" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "创建成功,taskId=$TASK_ID"---
⚠️ 通过应用 API 创建的待办显示在钉钉「待办」的 Teambition 分类下,不是「个人」分类。
⚠️ 通过 API 创建的任务无法在钉钉 UI 里手动删除,只能通过 API 删除。
references/api.md 查阅索引
确定好要做什么之后,用以下命令从 references/api.md 中提取对应章节的完整 API 细节(请求格式、参数说明、返回值示例):
# 身份标识与 userId ↔ unionId 转换(28 行)
grep -A 28 "^## 身份标识" references/api.md
# 创建待办(含所有可选字段)(47 行)
grep -A 47 "^## 1. 创建待办" references/api.md
# 获取待办详情(29 行)
grep -A 29 "^## 2. 获取待办详情" references/api.md
# 查询待办列表(含分页)(42 行)
grep -A 42 "^## 3. 查询待办列表" references/api.md
# 更新待办(25 行)
grep -A 25 "^## 4. 更新待办" references/api.md
# 删除待办(16 行)
grep -A 16 "^## 5. 删除待办" references/api.md
# 错误码表(9 行)
grep -A 9 "^## 错误码" references/api.md
# 所需应用权限(7 行)
grep -A 7 "^## 所需应用权限" references/api.md钉钉待办 API 参考
接口基础 URL:https://api.dingtalk.com公共请求头:x-acs-dingtalk-access-token: <token>+Content-Type: application/json
所有 {unionId} 替换为操作者的 unionId(从 userId 转换获得,见下方「身份标识」章节)---
身份标识与 userId ↔ unionId 转换
待办 API 的路径参数 {unionId}、查询参数 operatorId、以及 executorIds / participantIds 均使用 unionId。 但 unionId 无法直接在管理后台查看,需要通过 userId 调用 API 转换。
钉钉用户 ID 体系
| 标识 | 说明 | 作用域 | 待办 API 支持 |
|---|---|---|---|
userId(= staffId) | 企业内部员工 ID | 单个企业内唯一 | ❌ 不能直接用于待办 API |
unionId | 跨企业/跨应用的用户 ID | 同一法人跨组织唯一 | ✅ 路径参数和 operatorId 均使用此 ID |
userId 获取方式
1. 管理后台(最简单):PC 端钉钉 → 工作台 → 管理后台 → 通讯录 → 成员管理 → 点击姓名查看 2. 手机号查询:POST /topapi/v2/user/getbymobile?access_token=<旧版token> 3. 机器人回调:消息体中 senderStaffId 字段
userId ↔ unionId 互转
以下 API 使用旧版 access_token(GET https://oapi.dingtalk.com/gettoken?appkey=&appsecret=):
| 方向 | API | 请求 body | 返回值 |
|---|---|---|---|
| userId → unionId | POST /topapi/v2/user/get?access_token=<旧版token> | {"userid": "<userId>"} | result.unionid(注意:无下划线的 unionid 有值,union_id 可能为空) |
| unionId → userId | POST /topapi/user/getbyunionid?access_token=<旧版token> | {"unionid": "<unionId>"} | result.userid |
---
1. 创建待办
POST /v1.0/todo/users/{unionId}/tasks?operatorId={unionId}
请求体
{
"subject": "完成需求评审(必填)",
"description": "详细说明(选填)",
"dueTime": 1700000000000,
"reminderTimeStamp": 1699990000000,
"priority": 10,
"executorIds": ["unionId1", "unionId2"],
"participantIds": ["unionId3"]
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
subject | string | ✅ | 待办标题 |
description | string | — | 描述 |
dueTime | long | — | 截止时间(毫秒时间戳,0 表示无截止) |
reminderTimeStamp | long | — | 提醒时间(毫秒时间戳) |
priority | int | — | 优先级(10=高, 20=中默认, 30=低) |
executorIds | string[] | — | 执行者 unionId 列表 |
participantIds | string[] | — | 参与者 unionId 列表 |
响应
{
"id": "taskXXXXXXXXXXXXXXXX",
"subject": "完成需求评审",
"done": false,
"dueTime": 1700000000000,
"priority": 20,
"creatorId": "unionId",
"bizTag": "teambition",
"createdTime": 1700000000000,
"modifiedTime": 1700000000000
}⚠️ 返回的任务 ID 字段名为id,不是taskId
---
2. 获取待办详情
GET /v1.0/todo/users/{unionId}/tasks/{taskId}
无请求体。
响应
{
"id": "taskXXXXXXXXXXXXXXXX",
"subject": "完成需求评审",
"description": "详细说明",
"done": false,
"dueTime": 1700000000000,
"priority": 20,
"creatorId": "unionId",
"executorIds": ["unionId1"],
"participantIds": ["unionId2"],
"detailUrl": {
"appUrl": "dingtalk://...",
"pcUrl": "https://..."
}
}删除后调用 GET 会返回 HTTP 400,body 包含 "task not exist"---
3. 查询待办列表
POST /v1.0/todo/users/{unionId}/tasks/list?operatorId={unionId}
⚠️ 需要应用权限:Todo.Todo.Read或Custom.Todo.Read(查询三方/自建应用待办)
请求体
{
"isDone": false,
"nextToken": "",
"fromDueTime": 0,
"toDueTime": 0
}| 字段 | 类型 | 说明 |
|---|---|---|
isDone | bool | true=已完成,false=未完成 |
nextToken | string | 分页游标,首次传空字符串 |
fromDueTime | long | 截止时间起始过滤(毫秒) |
toDueTime | long | 截止时间结束过滤(毫秒) |
响应
{
"todoCards": [
{
"id": "taskXXXXXXXXXXXXXXXX",
"subject": "完成需求评审",
"done": false,
"dueTime": 1700000000000,
"priority": 20
}
],
"nextToken": "下一页游标(末页时为空)"
}---
4. 更新待办
PUT /v1.0/todo/users/{unionId}/tasks/{taskId}?operatorId={unionId}
请求体(所有字段均可选)
{
"subject": "新标题",
"description": "新描述",
"done": true,
"dueTime": 1700000000000
}响应
{
"requestId": "xxxxxxxx",
"result": true
}---
5. 删除待办
DELETE /v1.0/todo/users/{unionId}/tasks/{taskId}?operatorId={unionId}
无请求体。
响应
{
"requestId": "xxxxxxxx",
"result": true
}---
错误码
| HTTP 状态码 | code | 说明 | 处理建议 |
|---|---|---|---|
| 400 | ResourceNotFound.TaskNotExist | 任务不存在(含已删除) | 确认 taskId 是否正确 |
| 403 | Forbidden.AccessDenied.* | 缺少对应权限 | 在开放平台申请 Todo.Todo.Write、Todo.Todo.Read 或 Custom.Todo.Read |
| 401 | Unauthorized | token 无效或过期 | 重新获取 accessToken |
---
所需应用权限
| 权限 | 操作范围 |
|---|---|
Todo.Todo.Write | 创建 / 更新 / 删除待办 |
Todo.Todo.Read | 查询企业下用户待办列表 |
Custom.Todo.Read | 查询用户企业类型待办列表(含三方/自建应用产生的待办) |
申请地址:开放平台 → 应用管理 → 权限管理 → 搜索对应权限名称
Related skills
FAQ
What can dingtalk-todo do in a session?
dingtalk-todo lets agents create, assign, update, and track DingTalk todos from coding sessions. Tasks appear in DingTalk lists with assignees and status fields for enterprise teams.
Who should use dingtalk-todo?
dingtalk-todo suits developers whose organization runs on DingTalk and who want follow-up work captured as todos during PR reviews, incidents, or standups without manual copy-paste.