钉钉待办API迁移实战:从旧版接口升级到新版待办任务接口

发布时间:2026/8/5 9:16:57
钉钉待办API迁移实战:从旧版接口升级到新版待办任务接口
1. 项目缘起从“旧”到“新”的待办接口迁移之痛最近在重构一个内部任务协同系统核心功能之一就是自动将系统内的任务同步到钉钉待办方便团队成员在钉钉里统一查看和处理。这个功能原本跑得好好的用的是钉钉开放平台提供的“创建待办”接口。直到上周测试同学突然反馈所有新创建的待办任务在钉钉App里都看不到了但接口调用却返回成功。我心里咯噔一下知道该来的终于来了——钉钉的待办接口升级了。这其实不是个例如果你也在用钉钉的待办API很可能已经遇到了类似问题。钉钉官方已经逐步将旧版待办接口/topapi/workrecord/add迁移至新版待办任务接口/v1.0/todo/tasks。旧接口虽然目前还能调用成功但创建的任务可能无法在客户端正常展示属于“静默失效”。对于依赖此功能的应用来说这是个必须立刻解决的“暗礁”。今天我就结合这次迁移实战把新版待办任务接口的调用细节、避坑要点以及如何平滑过渡一次性讲透。无论你是Java、Python还是其他语言的开发者这篇文章都能帮你快速搞定这个升级。2. 新旧接口对比不仅仅是URL变了在动手改代码之前我们必须先搞清楚新旧两套接口到底有哪些不同。这不仅仅是换个请求地址那么简单其设计理念和数据结构都有显著差异。理解这些差异是避免后续踩坑的关键。2.1 旧版接口简单直接的任务记录旧版接口topapi/workrecord/add的设计更偏向于“工作记录”或“任务提醒”。它的核心字段相对简单userid: 接收任务的员工ID。create_time: 任务创建时间。title: 任务标题。url: 点击任务后跳转的链接。formItemList: 一个可选的表单列表用于展示任务的一些附加信息如内容、优先级等。它的工作流程是创建一个任务记录然后钉钉会向对应用户发送一条待办通知。这个接口的权限校验依赖于微应用的管理员权限调用相对直接。2.2 新版接口面向协同的待办任务体系新版接口v1.0/todo/tasks则属于“钉钉待办”这个更独立、功能更丰富的产品体系。它的设计更加精细和强大独立的权限体系调用新版接口必须使用“待办”应用所对应的AppKey和AppSecret而不再是旧版那个微应用的凭证。这是第一个也是最重要的不同点。你需要在钉钉开放平台为你的应用开通“待办”能力并获取对应的凭证。更丰富的任务模型执行者executorIds一个任务可以指定多个执行者支持协同处理。参与者participantIds可以设置任务的关注者或参与者他们能看到任务但未必需要处理。详情页detailUrl任务卡片本身的跳转链接。操作栏actionList可以自定义任务卡片下方的操作按钮例如“完成”、“转交”、“评论”等每个按钮可以绑定不同的跳转链接bizCallBack。来源信息sourceId, source用于标识任务来自哪个外部系统便于归类和同步。截止时间dueTime与提醒时间reminder支持更精细的时间管理。不同的API网关和域名新版接口通常通过https://api.dingtalk.com这个网关进行调用而旧版接口可能走的是oapi.dingtalk.com。HTTP方法也从旧的POST变成了新的POST但路径和参数结构完全不同。为了更直观我将核心差异整理成了下表特性维度旧版接口 (/topapi/workrecord/add)新版接口 (/v1.0/todo/tasks)影响与注意事项权限凭证微应用的AppKey/Secret或企业自建应用的SuiteKey/Secret必须使用“待办”应用的AppKey/Secret迁移第一步创建待办应用并获取新凭证。旧凭证完全失效。API网关oapi.dingtalk.comapi.dingtalk.com代码中请求的基地址需要修改。任务接收方userid(单个)executorIds(数组可多个)从单用户任务升级为可多人执行的任务。任务跳转url(单一链接)detailUrl(详情页) actionList(操作按钮回调)交互更丰富可以为一个任务定义多个操作入口。任务来源无明确字段source,sourceId(必填)用于标识外部系统必须合理设置建议用“系统名:业务类型”的格式。通知能力创建即发送通知创建后可通过单独的/v1.0/todo/tasks/{taskId}/send接口发送通知创建任务和发送通知解耦控制更灵活。状态同步较弱支持完成(done)、删除(delete)等状态操作并有回调通知可以实现外部系统与钉钉待办的状态同步。注意上表中的“待办应用”是指在开放平台“应用开发”中创建的、类型为“待办”的应用。它和你的“微应用”或“H5应用”是独立的需要单独配置和授权。3. 实战三步走搞定新版待办任务集成了解了理论差异我们开始动手。整个迁移过程可以概括为三个核心步骤准备新凭证、重构请求体、处理响应与通知。3.1 第一步在开放平台创建与配置待办应用这是所有工作的前提没有正确的应用和凭证一切调用都是徒劳。登录钉钉开放平台进入开发者后台。创建待办应用在“应用开发”页面点击创建应用选择“待办”类型。填写应用名称、描述等信息。创建成功后你会获得这个待办应用的AppKey和AppSecret。请妥善保存这将是后续所有API调用的钥匙。配置应用权限在应用详情页的“权限管理”中确保已添加“待办任务读写权限”todo:task:write。通常创建待办应用时默认已添加。授权给企业在“版本管理与发布”中将应用发布到线上并确保需要使用的企业组织已经授权了该应用。只有被授权企业的员工才能被成功创建待办任务。3.2 第二步获取访问令牌与构造请求新版接口使用标准的OAuth 2.0客户端凭证模式获取Token与旧版获取access_token的方式类似但域名和参数略有不同。获取 Access Token# 请求示例 (使用 curl) curl -X POST \ https://api.dingtalk.com/v1.0/oauth2/accessToken \ -H Content-Type: application/json \ -d { appKey: 你的待办应用AppKey, appSecret: 你的待办应用AppSecret } # 响应示例 { expireIn: 7200, accessToken: xxxxxx, corpId: xxxx }Token有效期为7200秒2小时需要在自己的服务端做好缓存和刷新机制避免频繁请求。构造创建任务的请求体这是最核心的部分一个最小化但可用的创建任务请求体如下{ subject: 【系统提醒】请审核月度报销单, creatorId: manager123, // 创建者工号需在接收方企业内 description: 员工张三提交了2023年10月的报销单总金额为1250元请尽快处理。, executorIds: [zhangsan, lisi], // 执行者工号列表至少一个 participantIds: [wangwu], // 参与者工号列表可选 detailUrl: { appUrl: https://your-internal-system.com/task/12345, // PC端跳转地址 pcUrl: https://your-internal-system.com/task/12345 // 移动端跳转地址可与appUrl相同 }, sourceId: finance_audit:12345, // 外部系统任务唯一ID建议包含业务类型 source: 财务审核系统, // 外部系统来源名称 dueTime: 1698768000000, // 截止时间戳毫秒可选 priority: 20 // 优先级可选默认20 }关键字段解读与避坑点creatorId必须是钉钉企业内的有效员工ID且该员工所在企业必须已授权你的待办应用。否则任务创建会失败。executorIds任务执行人列表。即使你只想指定一个人也必须用数组格式如[zhangsan]。这是新手最容易出错的地方之一。detailUrl这里的appUrl和pcUrl是必填项。如果你没有独立的移动端页面可以填同一个URL。这个链接是点击任务主标题区域的跳转地址。source和sourceId强烈建议认真规划这两个字段。source用于标识你的系统如“CRM”、“OA”sourceId是你系统内部任务的唯一标识。钉钉会使用sourcesourceId作为去重依据。如果你用同一个组合重复调用钉钉会更新已有的任务而不是新建。这既是优点避免重复任务也可能是坑如果sourceId生成逻辑有误会导致任务被意外覆盖。priority优先级数值越低越优先。默认是20。你可以根据业务需要设置例如紧急任务设为10。3.3 第三步发送请求与处理通知拿到Token和构造好请求体后就可以调用创建接口了。调用创建任务接口curl -X POST \ https://api.dingtalk.com/v1.0/todo/tasks \ -H Content-Type: application/json \ -H x-acs-dingtalk-access-token: 上一步获取的accessToken \ -d 上面构造的JSON请求体如果成功响应如下{ id: 5c5f-4b3a-..., // 钉钉侧生成的待办任务ID subject: 【系统提醒】请审核月度报销单, creatorId: manager123, executorIds: [zhangsan, lisi], sourceId: finance_audit:12345, source: 财务审核系统 }请务必保存返回的id这是后续更新、完成或删除该任务的唯一依据。发送待办通知任务创建成功后在钉钉待办列表里就有了但用户不会立即收到通知。需要调用另一个接口来发送提醒curl -X POST \ https://api.dingtalk.com/v1.0/todo/tasks/{taskId}/send \ // 将{taskId}替换为上一步返回的id -H x-acs-dingtalk-access-token: your_access_token \ -H Content-Type: application/json \ -d { operatorId: manager123 // 操作者ID通常是创建者 }调用成功后指定的执行者executorIds就会在钉钉上收到待办任务的通知了。这种“创建”与“通知”分离的设计给了开发者在业务流程上更大的控制权。例如你可以先创建任务草稿等某个条件满足后再发送通知。4. 深度踩坑与排查指南在实际迁移和开发过程中我遇到了不少报错和诡异的问题。下面我把这些坑和排查思路梳理出来希望能帮你节省大量时间。4.1 错误码 400参数校验不通过这是最常见的一类错误响应体里通常会给出具体的错误信息。“executorIds” is required 没传executorIds或者传了空数组[]。必须保证数组里至少有一个有效的用户ID。“detailUrl” is required 忘记传detailUrl对象或者里面的appUrl/pcUrl为空。“source” is required或“sourceId” is required 漏填了这两个字段中的任何一个。它们都是必填项。User not found 指定的creatorId或executorIds中的用户ID在当前企业不存在或者该企业未授权你的待办应用。请特别注意即使该用户存在于钉钉但如果他/她不在你已经授权了的那个企业里也会报这个错。你需要确认调用接口时使用的accessToken所对应的企业即待办应用授权给的企业是否包含了这些用户。Invalid JSON format 请求体不是合法的JSON。常见于字符串拼接构造JSON时忘了转义引号或处理换行符。建议在代码中始终使用JSON库来序列化对象。4.2 错误码 403权限不足No permission to access this API 使用的accessToken对应的应用没有待办任务的读写权限。请回到开放平台检查你的待办应用是否已添加todo:task:write权限。Invalid authenticationaccessToken无效或已过期。检查你的Token获取逻辑和缓存刷新机制。确保调用业务接口时使用的是最新有效的Token。4.3 错误码 500 或连接问题Internal server error或Connection closed mid-response 通常是钉钉服务端临时问题。首先检查你的请求参数是否超大虽然待办任务接口对字段长度限制较宽但超长的描述或URL也可能引发问题。如果参数正常可以稍后重试。如果持续失败需要检查钉钉开放平台的状态公告。网络超时或不可达 确认你的服务器能正常访问api.dingtalk.com域名。有些公司内网环境可能有出口防火墙限制。4.4 任务创建成功但客户端不显示这是从旧接口迁移过来时最典型的问题症状是接口返回成功200有任务ID但在执行者的钉钉App里就是找不到这个待办。首先检查应用授权百分之八十的问题出在这里。请务必确认你调用接口时使用的appKey和appSecret是来自一个已经成功授权给目标企业的待办应用。用旧微应用的凭证调用新接口即使返回成功任务也是“幽灵”状态。检查用户ID有效性 确认executorIds里的用户ID在当前accessToken所代表的企业内是真实存在的。可以用钉钉的获取用户信息接口先验证一下。检查通知是否发送 任务创建后默认不会出现在用户的“今日”或“待办”列表直到调用“发送通知”接口。请确认你是否调用了/v1.0/todo/tasks/{taskId}/send。查看“已隐藏”或“其他来源” 在钉钉待办界面有时任务会被归类或过滤。让用户检查一下待办列表的“全部”标签或者看看是否有按来源分类的筛选。4.5 状态同步与回调配置新版接口支持任务状态变化如完成、删除时向你的服务器发送回调通知。这对于保持外部系统与钉钉待办状态一致非常有用。配置回调地址在待办应用的后台“事件与回调”页面配置一个HTTPS的接收地址。钉钉会向这个地址推送事件。订阅事件你需要订阅todo_task_change事件类型。处理回调当用户在钉钉里完成或删除任务时钉钉会发送一个加密的POST请求到你的回调地址。你需要解密请求体钉钉提供了各语言的解密SDK。解析出事件类型eventType和任务IDtaskId等信息。根据eventType(例如todo_task_completed、todo_task_deleted) 更新你自己系统中对应任务的状态。注意签名和重试务必验证回调请求的签名以确保请求来自钉钉。同时你的回调接口需要快速返回成功HTTP 200否则钉钉会认为推送失败并进行重试。5. 进阶打造更佳用户体验的待办集成完成了基础接入我们可以看看如何利用新接口的特性做出体验更好的集成。5.1 自定义操作按钮与业务回调新版待办任务卡片底部可以配置一组操作按钮比如“查看详情”、“开始处理”、“确认完成”等。这比旧版只有一个跳转链接强大得多。在创建任务的请求体中可以添加actionList字段{ // ... 其他字段同上 actionList: [ { name: 处理, actionUrl: { appUrl: https://your-system.com/task/12345/action/process, pcUrl: https://your-system.com/task/12345/action/process } }, { name: 完成, actionUrl: { appUrl: https://your-system.com/task/12345/action/finish, pcUrl: https://your-system.com/task/12345/action/finish } } ] }当用户点击“完成”按钮时会跳转到你配置的actionUrl。你可以在自己的页面处理完业务逻辑例如在你的系统里标记任务完成后再调用钉钉的接口/v1.0/todo/tasks/{taskId}/done来同步更新钉钉待办的任务状态。这样就形成了一个闭环的业务流。5.2 任务更新、完成与删除任务不是一成不变的我们需要更新它。更新任务使用PATCH /v1.0/todo/tasks/{taskId}接口。你可以更新标题(subject)、描述(description)、截止时间(dueTime)、执行者(executorIds)等大部分字段。注意更新执行者时新的列表会完全覆盖旧的列表。标记完成任务使用POST /v1.0/todo/tasks/{taskId}/done接口。需要传递operatorId操作者ID。这会将任务状态改为完成。删除任务使用DELETE /v1.0/todo/tasks/{taskId}接口。同样需要operatorId。5.3 关于“虚拟位置”、“打卡”等热词的无关性澄清在分析网络热词时我注意到“钉钉打卡虚拟位置”等词频繁出现。这里必须明确本文讨论的“待办任务”API与“打卡”、“定位”等功能毫无关系。钉钉打卡涉及的是完全不同的另一套权限和接口主要与考勤相关并且任何讨论或提供“虚拟位置”、“修改定位”以实现虚假打卡的技术内容不仅违反钉钉平台规则也可能涉及不当行为。作为开发者我们应该专注于利用开放平台提供的合法接口创造提升工作效率的工具而不是钻营漏洞。待办任务API是一个纯粹用于任务管理和协同的正向工具。6. 迁移策略与代码示例片段最后分享一下从旧接口迁移到新接口的平滑策略并提供一段Java Spring Boot风格的代码示例供大家参考。平滑迁移策略并行运行期在旧接口完全失效前同时实现新旧两套接口的调用逻辑。根据配置或特性开关决定使用哪一套。这样即使新版接口遇到问题可以快速回退。数据映射与补偿将旧系统的任务数据尤其是sourceId按照新规则进行映射。对于已经通过旧接口创建且仍在进行中的任务可以考虑通过新接口重新创建一次并通过消息通知用户关注新任务逐步淘汰旧任务。全面测试务必在测试环境用真实的测试企业号覆盖单人多任务、多人协同、更新、完成、回调等全流程。Java代码示例使用HttpClientimport org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.*; Component public class DingTalkTodoService { private String appKey your_todo_app_key; private String appSecret your_todo_app_secret; private String accessToken; private long tokenExpireTime; private final RestTemplate restTemplate new RestTemplate(); private final ObjectMapper objectMapper new ObjectMapper(); // 1. 获取AccessToken (带缓存) private String getAccessToken() { if (accessToken ! null System.currentTimeMillis() tokenExpireTime) { return accessToken; } String url https://api.dingtalk.com/v1.0/oauth2/accessToken; ObjectNode requestBody objectMapper.createObjectNode(); requestBody.put(appKey, appKey); requestBody.put(appSecret, appSecret); Map response restTemplate.postForObject(url, requestBody, Map.class); this.accessToken (String) response.get(accessToken); long expireIn Long.parseLong(response.get(expireIn).toString()); this.tokenExpireTime System.currentTimeMillis() (expireIn - 300) * 1000; // 提前5分钟过期 return this.accessToken; } // 2. 创建待办任务 public String createTodoTask(String creatorId, ListString executorIds, String subject, String description, String source, String sourceId, String detailUrl) { String url https://api.dingtalk.com/v1.0/todo/tasks; String token getAccessToken(); ObjectNode requestBody objectMapper.createObjectNode(); requestBody.put(creatorId, creatorId); requestBody.putArray(executorIds).addAll(executorIds); requestBody.put(subject, subject); requestBody.put(description, description); requestBody.put(source, source); requestBody.put(sourceId, sourceId); ObjectNode urlNode objectMapper.createObjectNode(); urlNode.put(appUrl, detailUrl); urlNode.put(pcUrl, detailUrl); requestBody.set(detailUrl, urlNode); // 可以添加更多字段如 dueTime, priority, actionList 等 // requestBody.put(dueTime, System.currentTimeMillis() 86400000L); // 截止时间1天后 // requestBody.put(priority, 10); HttpHeaders headers new HttpHeaders(); headers.set(x-acs-dingtalk-access-token, token); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityString entity new HttpEntity(requestBody.toString(), headers); Map response restTemplate.postForObject(url, entity, Map.class); return (String) response.get(id); // 返回钉钉任务ID } // 3. 发送待办通知 public void sendTodoNotification(String taskId, String operatorId) { String url https://api.dingtalk.com/v1.0/todo/tasks/ taskId /send; String token getAccessToken(); ObjectNode requestBody objectMapper.createObjectNode(); requestBody.put(operatorId, operatorId); HttpHeaders headers new HttpHeaders(); headers.set(x-acs-dingtalk-access-token, token); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityString entity new HttpEntity(requestBody.toString(), headers); restTemplate.postForObject(url, entity, Void.class); } }这段代码提供了最核心的Token管理和任务创建功能。在实际项目中你需要将其纳入你的服务治理框架如加入断路器、重试机制并处理好异常。最关键的是管理好你的appKey和appSecret不要硬编码在代码里应该使用配置中心或环境变量。迁移到新版待办接口虽然初期有学习成本但其更清晰的数据模型、更强大的协同能力和更完善的状态管理对于构建严肃的企业协同功能来说无疑是更长期和可靠的选择。