钉钉文档技能
负责钉钉知识库和文档的所有操作,通过钉钉开放平台 API 实现。
API 详情见
references/api.md
。
配置管理(每次开始前必读)
配置文件路径
~/.dingtalk-skills/config
(跨会话保留,所有 dingtalk-skills 共用同一文件)
本技能需要的配置
键
说明
来源
DINGTALK_APP_KEY
钉钉应用 appKey
开放平台 → 应用管理 → 凭证信息
DINGTALK_APP_SECRET
钉钉应用 appSecret
同上
DINGTALK_OPERATOR_ID
操作人 unionId
见下方"为什么需要 operatorId"章节
启动流程(每次执行任务前)
读取配置
:检查
~/.dingtalk-skills/config
是否存在,解析已有键值
识别缺失项
:找出上表中尚未配置的键
一次性收集
:将所有缺失项合并为一条提问,
不要逐条询问
,例如:
需要以下信息才能继续(已有的无需再填):
钉钉应用 appKey(钉钉开放平台 → 应用管理 → 凭证信息)
钉钉应用 appSecret
你的钉钉 userId 或 unionId(以便以你的身份操作文档)
持久化
:将用户提供的值追加写入 config,后续直接读取,无需再问
执行任务
:配置完整后开始操作
注意
:
APP_KEY
/
APP_SECRET
/
OPERATOR_ID
属于凭证,禁止在输出中完整打印,确认时仅显示前 4 位 +
。
认证
每次调用 API 前,用 appKey/appSecret 获取当次的 accessToken(有效期 2 小时):
POST https://api.dingtalk.com/v1.0/oauth2/accessToken
Content-Type: application/json
{
"appKey": "<应用 appKey>",
"appSecret": "<应用 appSecret>"
}
返回:
{ "accessToken": "xxx", "expireIn": 7200 }
所有后续请求均需携带请求头:
x-acs-dingtalk-access-token:
为什么需要 operatorId(unionId)
钉钉开放平台要求所有
写操作
(创建文档、写入内容、管理成员等)必须代表一个真实用户身份执行,而不是以匿名应用身份操作。
operatorId
就是声明"这个操作是谁做的"——它会被记录到文档的变更历史,并用于权限校验。
值为操作人的
unionId
(钉钉跨组织唯一 ID),
不是
userId
(仅组织内唯一)
错误传入 userId 会导致权限报错或文档归属错误
如何获取 unionId
方法一:已知 userId → 换取 unionId(最常用)
userId
通常可从通讯录、免登、消息回调等场景直接获得。
第一步:获取旧式
access_token
(与新版 accessToken 不同,此处单独获取):
GET https://oapi.dingtalk.com/gettoken?appkey=&appsecret=
返回:
{ "access_token": "xxx", "expires_in": 7200 }
第二步:用 userId 查询用户详情,取出 unionId:
POST https://oapi.dingtalk.com/topapi/v2/user/get?access_token=<旧式token>
Content-Type: application/json
{ "userid": "<钉钉 userId>" }
返回字段
result.unionid
(无下划线)即为 unionId。
注意:
result.union_id
(有下划线)在专属钉钉组织中可能为空,请使用
result.unionid
。
方法二:机器人/消息场景
用户通过钉钉机器人或消息触发时,消息体中直接包含
senderUnionId
字段,可直接作为
operatorId
使用,无需额外查询。
方法三:直接询问用户
若上述方式不可用,在初始配置阶段询问用户提供 userId 或 unionId(均可在钉钉个人信息页查看)。获取后写入 config,后续操作无需重复获取。
核心操作
1. 查询用户知识库列表
用户想查看有哪些知识库时:
GET https://api.dingtalk.com/v2.0/wiki/workspaces?operatorId=&maxResults=20&nextToken=<分页令牌>
x-acs-dingtalk-access-token:
如有
nextToken
则继续翻页,直到无
nextToken
为止。返回字段中
workspaceId
和
rootNodeId
供后续操作使用。
2. 查询知识库信息
GET https://api.dingtalk.com/v2.0/wiki/workspaces/{workspaceId}?operatorId=
x-acs-dingtalk-access-token:
3. 查询目录结构(节点列表)
用户想看知识库里有哪些文档/文件夹时:
GET https://api.dingtalk.com/v2.0/wiki/nodes?parentNodeId=&operatorId=&maxResults=50
x-acs-dingtalk-access-token:
parentNodeId
传知识库的
rootNodeId
可列出顶层内容,传子文件夹
nodeId
可深入查看。
每个节点包含:
nodeId
、
name
、
type
(
FILE
/
FOLDER
)、
category
、
workspaceId
、
url
。
4. 查询单个节点信息(通过 nodeId)
GET https://api.dingtalk.com/v2.0/wiki/nodes/{nodeId}?operatorId=
x-acs-dingtalk-access-token:
5. 通过文档链接查询节点信息
用户提供了文档 URL(如
https://alidocs.dingtalk.com/i/nodes/Xxx...
)时:
POST https://api.dingtalk.com/v2.0/wiki/nodes/queryByUrl?operatorId=
x-acs-dingtalk-access-token:
Content-Type: application/json
{
"url": "https://alidocs.dingtalk.com/i/nodes/",
"operatorId": ""
}
返回节点信息,其中
nodeId
可作为后续内容读取的
docKey
。
6. 创建文档
在指定知识库下新建文档:
POST https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs
x-acs-dingtalk-access-token:
Content-Type: application/json
{
"operatorId": "",
"docType": "DOC",
"name": "<文档标题>"
}
返回字段:
字段
说明
nodeId
知识库节点 ID(用于删除)
docKey
文档内容 Key(用于内容读写,≠ nodeId)
workspaceId
实际所在知识库 ID(可能与请求的不同,删除时须使用此值)
url
文档访问链接
docType
固定填
"DOC"
(ALIDOC 富文本格式)。
10. 删除文档
DELETE https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}?operatorId=
x-acs-dingtalk-access-token:
workspaceId
和
nodeId
均从创建文档的响应中获取。成功返回
200 {}
。
7. 读取文档正文内容(Block 结构)
用户想看文档里写了什么内容时,使用
docKey
读取文档 Block:
GET https://api.dingtalk.com/v1.0/doc/suites/documents/{docKey}/blocks?operatorId=
x-acs-dingtalk-access-token:
docKey
的来源:
通过 wiki nodes 接口查到的节点:
docKey = nodeId
(同一个值)
通过创建文档接口新建的文档:使用响应中的
docKey
字段(
不是
nodeId
)
所需权限:
Storage.File.Read
返回示例:
{
"result"
:
{
"data"
:
[
{
"blockType"
:
"heading"
,
"heading"
:
{
"level"
:
"heading-2"
,
"text"
:
"快速开始"
}
,
"index"
:
0
,
"id"
:
"xxx"
}
,
{
"blockType"
:
"paragraph"
,
"paragraph"
:
{
"text"
:
"正文内容..."
}
,
"index"
:
1
,
"id"
:
"yyy"
}
,
{
"blockType"
:
"unknown"
,
"index"
:
2
,
"id"
:
"zzz"
}
]
}
,
"success"
:
true
}
blockType
枚举:
heading
、
paragraph
、
unorderedList
、
orderedList
、
table
、
blockquote
、
unknown
(代码块/图片等富文本暂未解析)。
将各 block 的文本提取后按 index 顺序拼接,即可重建文档文字内容。
docKey
即通过 wiki nodes 接口获取的
nodeId
,是同一个值。
8. 写入/覆盖文档正文内容
用户想修改文档内容时:
POST https://api.dingtalk.com/v1.0/doc/suites/documents/{docKey}/overwriteContent
x-acs-dingtalk-access-token:
Content-Type: application/json
{
"operatorId": "",
"docContent": "# 新标题\n\n新的正文内容,支持 Markdown 格式。",
"contentType": "markdown"
}
⚠️ 写入操作会
覆盖
原有内容,执行前请与用户确认或先读取备份。
9. 文档成员管理
添加文档成员:
POST https://api.dingtalk.com/v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}/members
x-acs-dingtalk-access-token:
Content-Type: application/json
{
"operatorId": "",
"members": [
{ "id": "", "roleType": "editor" }
]
}
roleType
可选:
viewer
(只读)、
editor
(可编辑)
典型场景
"读取文档 X 的内容"
若用户提供了 URL,调用
POST /v2.0/wiki/nodes/queryByUrl
获取
nodeId
否则通过
GET /v2.0/wiki/nodes?parentNodeId=...
遍历查找目标文档
用
nodeId
作为
docKey
,调用
GET /v1.0/doc/suites/documents/{docKey}/blocks
将 block 文本按 index 顺序拼接后展示给用户
"把文档 X 的内容改成……"
先读取原内容,告知用户将被覆盖,请求确认
调用
POST /v1.0/doc/suites/documents/{docKey}/overwriteContent
,传入新内容
告知写入成功
"帮我在钉钉创建一个文档"
询问放到哪个知识库(列出知识库或让用户说名称)
通过
GET /v2.0/wiki/workspaces
找到对应
workspaceId
调用
POST /v1.0/doc/workspaces/{workspaceId}/docs
,
docType: ALIDOC
返回文档链接给用户
"查看知识库 X 下有哪些文档"
通过
GET /v2.0/wiki/workspaces
找到
workspaceId
和
rootNodeId
调用
GET /v2.0/wiki/nodes?parentNodeId={rootNodeId}&operatorId=...
整理成目录树展示
"把用户 xxx 加到文档 Y"
确认文档的
nodeId
和所在
workspaceId
调用
POST /v1.0/doc/workspaces/{workspaceId}/docs/{nodeId}/members
,指定
roleType
错误处理
HTTP 状态码
错误码
含义
处理方式
400
MissingoperatorId
operatorId 未传
补充 operatorId(unionId)
400
paramError
参数类型错误
operatorId 必须是 unionId,不是 userId
401
—
token 过期
重新获取 accessToken 后重试
403
Forbidden.AccessDenied.AccessTokenPermissionDenied
应用缺少权限
错误信息中有
requiredScopes
,提示用户在开放平台开通对应权限
404
InvalidAction.NotFound
接口路径不存在
检查版本号(v1.0/v2.0)和路径是否正确
429
—
触发限流
等待 1 秒后重试
发生错误时,将响应体中的
code
和
message
展示给用户辅助排查。
所需应用权限
功能
权限 scope
查询知识库/节点
Wiki.Node.Read
读取文档正文
Storage.File.Read
写入文档正文
Storage.File.Write
创建/删除文档
Storage.File.Write
查询用户 unionId(获取 operatorId)
Contact.User.Read