Doco 开放 API v1
Doco 开放 API 面向 Agent、脚本和第三方应用,用于管理知识库、文件夹、文档正文、块、附件、关系、概念、摘要和多语言文档。
- API 根地址:
{DOCO_ORIGIN}/api/v1 - 鉴权方式:
Authorization: Bearer <API_TOKEN> - OpenAPI 3.1 规范:
{DOCO_ORIGIN}/api/openapi.json - 本文 Markdown 原文:
{DOCO_ORIGIN}/api-docs.md
将
{DOCO_ORIGIN}替换为 Doco 实例的后端入口地址;生产环境使用https://api.doco.showme.talk。 注意:API 只在把/api/*反代到后端的入口上可用。托管在 Cloudflare Pages 等静态平台上的 前端页面域名(如doco.showme.talk)对所有路径返回 SPA 的 HTML 回退页,不能当 API 地址用。 判别方法:GET {DOCO_ORIGIN}/api/v1/me返回 JSON(401或带data)即正确;返回<!doctype html>说明打到了静态前端,请换用后端入口域名(见部署文档的 DNS 与 Caddy 章节)。 开放 API 使用 Bearer Token;浏览器页面使用的 Session Cookie 不能调用/api/v1/*。
快速开始
0. Agent 一条命令接入(推荐)
给 Claude Code / Cursor 等 Agent 接入 Doco,不需要手动复制 Token:
# 安装 CLI 并登录(浏览器设备授权,像 GitHub CLI 一样)
npm i -g doco-agent-cli
doco login
# 注册 MCP server,Agent 即获得 29 个文档读写、结构检索、关系遍历、分层摘要、显式概念、多语言与变更感知工具
claude mcp add doco -- npx -y --package doco-agent-cli doco mcp
Cursor:在 MCP 设置粘贴 { "doco": { "command": "npx", "args": ["-y", "--package", "doco-agent-cli", "doco", "mcp"] } }。
终端与脚本场景直接用 CLI(doco docs|blocks|edit,全局 --json)或裸 REST。
doco login 走下文「设备授权流程」,默认签发 read_write,网页确认时可降级为只读。
1. 创建 Token
登录 Doco 后,在右上角账户菜单中打开「API 管理」,创建只读或读写 Token。完整 Token 只显示一次,请勿提交到代码仓库或日志。
2. 验证身份
export DOCO_BASE_URL="https://api.doco.showme.talk"
export DOCO_API_TOKEN="doco_tok_xxx_secret"
curl --fail-with-body \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/me"
成功响应统一包含 data 和 request_id:
{
"data": {
"user": {
"id": "user_123",
"email": "agent@example.com",
"name": "Agent User"
},
"scopes": ["documents:read", "documents:write"],
"token_id": "tok_01..."
},
"request_id": "req_01..."
}
3. 创建知识库和文档
多语言文档
普通文档默认保持单语言;人工激活后才创建 doco://docset/{id}。每个语言版本拥有独立 doc_*、YDoc、稳定块 ID 和 ETag:
curl -X POST "$DOCO_BASE_URL/api/v1/documents/doc_123/localization/activate" \
-H "Authorization: Bearer $DOCO_TOKEN" -H 'Content-Type: application/json' \
-H 'Idempotency-Key: activate-doc-123' \
-d '{"source_locale":"zh-CN","target_locales":["en-US"],"create_mode":"copy_structure"}'
curl "$DOCO_BASE_URL/api/v1/documents/doc_123/localizations" -H "Authorization: Bearer $DOCO_TOKEN"
curl "$DOCO_BASE_URL/api/v1/documents/doc_123/read?locale=en-US" -H "Authorization: Bearer $DOCO_TOKEN"
读取返回 requested_locale、resolved_locale 和 fallback_used;写入始终使用具体 doc_*,继续先读 ETag、再使用 If-Match。块级翻译状态通过 /translation-units?locale=en-US 读取,人工完成后用 review_status=current|ignored 标记,冲突不会被静默覆盖。Search v2 支持 locale=en-US 或 locale=all,SSE 支持 document_set_id 与 locale 过滤,CLI/MCP 提供对应语言参数和翻译单元工具。
多语言文档集、语言版本和翻译单元是同一份权威结构的派生视图:doco://doc/{id} 仍指向具体语言版本,doco://docset/{id} 指向语言版本集合。GET /documents 默认只列出普通文档和源语言版本;需要同步翻译版本时使用 include_variants=true 或指定 locale。
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/document-sets/{id} |
documents:read |
读取文档集、源语言、回退语言和全部语言版本 |
GET |
/documents/{id}/localizations |
documents:read |
列出文档集及语言版本 |
POST |
/documents/{id}/localization/activate |
documents:write |
激活多语言并创建目标语言版本 |
POST |
/documents/{id}/localizations |
documents:write |
增加一个目标语言版本 |
PATCH |
/documents/{id}/localizations/{locale} |
documents:write |
修改语言版本标题或工作流状态 |
DELETE |
/documents/{id}/localizations/{locale} |
documents:write |
删除目标语言版本 |
POST |
/documents/{id}/localization/deactivate |
documents:write |
停用尚无目标语言版本的文档集 |
GET |
/documents/{id}/translation-units?locale=en-US |
documents:read |
查看翻译单元、目标块和新鲜度 |
PATCH |
/documents/{id}/translation-units/{unitId} |
documents:write |
人工确认或忽略翻译单元,需目标文档 If-Match |
GET |
/documents/{id}/localization-audit |
documents:read |
查看语言版本和翻译审核审计记录 |
POST |
/documents/{id}/translation-jobs |
documents:write |
创建异步机器翻译候选,返回 202 |
GET |
/translation-jobs/{id} |
documents:read |
查询候选任务状态、结果和用量成本 |
POST |
/translation-jobs/{id}/apply |
documents:write |
选择性应用候选,必须带目标文档 If-Match |
机器翻译候选与安全应用
机器翻译只生成候选,不会直接修改目标 YDoc。任务创建是异步的,目标文档必须已经存在;可指定最多 100 个翻译单元,不指定时默认处理 missing 或 source_changed 单元。任务受工作区每日字符额度限制,并记录输入字符、输出字符和估算成本。
# 1. 创建机器翻译候选(返回 202,data.status 初始为 pending)
curl --fail-with-body -X POST \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: translate-doc-123-en-us" \
-d '{"target_locale":"en-US","unit_ids":["tu_01JXYZ..."]}' \
"$DOCO_BASE_URL/api/v1/documents/doc_123/translation-jobs"
# 2. 轮询任务,直到 succeeded、failed 或 obsolete
curl --fail-with-body \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/translation-jobs/tjob_01JXYZ..."
# 3. 读取目标文档最新 ETag 后,只应用选中的候选
TARGET_ETAG=$(curl -sSI \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/documents/doc_en_us/content" | awk -F': ' 'tolower($1)=="etag" {print $2}' | tr -d '\r')
curl --fail-with-body -X POST \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
-H "If-Match: $TARGET_ETAG" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: apply-translate-tjob-01" \
-d '{"unit_ids":["tu_01JXYZ..."]}' \
"$DOCO_BASE_URL/api/v1/translation-jobs/tjob_01JXYZ.../apply"
应用候选必须携带目标语言文档当前 If-Match;也可在请求体使用 base_target_version 作为备用形式。缺少版本返回 428,目标文档已变化返回 409。已人工修改、已确认或已冲突的单元不会被机器候选覆盖;源文档在任务期间变化时任务变为 obsolete,应重新生成。
KB_RESPONSE=$(curl --fail-with-body -sS \
-X POST \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-agent-kb-001" \
-d '{"name":"Agent 知识库"}' \
"$DOCO_BASE_URL/api/v1/knowledge-bases")
# 把下方 123 替换为上一步 data.id
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-agent-doc-001" \
-d '{
"title": "API 创建的文档",
"knowledge_base_id": 123,
"content": {
"format": "markdown",
"content": "# Hello Doco\n\n这篇文档由 API 创建。"
}
}' \
"$DOCO_BASE_URL/api/v1/documents"
鉴权与权限
Token 支持以下 scopes:
| Scope | 能力 |
|---|---|
documents:read |
读取文档、正文和块 |
documents:write |
创建、修改、移动和删除文档与块 |
knowledge-bases:read |
读取知识库、文件夹和目录树 |
knowledge-bases:write |
创建、修改和删除知识库与文件夹 |
attachments:read |
下载附件和读取附件元数据 |
attachments:write |
上传和删除附件 |
concepts:read |
枚举显式概念、按别名解析、遍历关系和查看候选 |
concepts:write |
创建/编辑/合并概念、添加证据和关系、提取及审核确定性候选 |
summaries:generate |
触发可产生外部成本的异步模型摘要;确定性摘要读取不需要此 scope |
每个请求都应携带:
Authorization: Bearer doco_tok_xxx_secret
Token 缺失或失效返回 401,scope 不足返回 403。为避免越权泄露,不属于当前账户的资源通常返回 404。
设备授权流程(CLI / Agent 登录)
无需手动复制 Token 的登录方式(RFC 8628 风格,doco login 即走此流程):
# 1. 申请设备码(无需鉴权)
curl -X POST "$DOCO_BASE_URL/api/v1/auth/device/code" \
-H "Content-Type: application/json" \
-d '{"client_name":"我的脚本","scopes":"read_write"}'
# → device_code、user_code、verification_uri_complete、expires_in、interval
# 2. 用户在浏览器打开 verification_uri_complete 并确认(可选降级为只读)
# 3. 按 interval 轮询换取 Token(无需鉴权;POST,凭据不走 URL)
curl -X POST "$DOCO_BASE_URL/api/v1/auth/device/token" \
-H "Content-Type: application/json" \
-d '{"device_code":"<device_code>"}'
用户确认前轮询返回 400 authorization_pending;轮询过快返回 400 slow_down(附 Retry-After);
授权码过期返回 400 expired_token;用户拒绝返回 400 access_denied。
确认后轮询返回标准 doco_tok_ Token(read_write 或网页端降级的 read_only),每个授权码只能换取一次,
默认 90 天有效(服务端 DOCO_DEVICE_TOKEN_TTL_DAYS 可调),到期后重新 doco login。
通用约定
响应结构
单资源成功响应:
{
"data": {},
"request_id": "req_01..."
}
列表成功响应:
{
"data": [],
"page": {
"cursor": null,
"next_cursor": null,
"has_more": false
},
"request_id": "req_01..."
}
错误响应:
{
"error": {
"type": "request_error",
"code": "invalid_request",
"message": "可读的错误信息",
"details": {}
},
"request_id": "req_01..."
}
请求追踪与限频
- 可传
X-Request-Id;服务端也会在响应中返回X-Request-Id。 - 限频信息通过
X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset返回。 - 超过限频返回
429,同时提供Retry-After。 - 限频窗口存于 SQLite,由多进程共享,服务重启不会清空窗口状态。
- 请为自动化客户端发送可识别的
User-Agent,推荐格式为Doco-Agent/<版本> (<客户端名>);Doco CLI 会发送doco-agent-cli <版本>。 服务端会把客户端名、版本和是否为 Agent 写入 API 审计日志,用于统计 Agent 流量占比。
分页
列表接口使用游标分页:
GET /api/v1/documents?limit=50&cursor=<next_cursor>
limit 范围为 1 到 100。继续请求时使用上一页 page.next_cursor。
幂等写入
创建资源、上传附件和批量操作支持:
Idempotency-Key: <最多 128 个字符的稳定键>
相同 Token、方法、路径和请求体使用相同键时会返回首次结果;同一个键配合不同请求体返回 409。
并发控制
正文和块读取响应会返回 ETag。替换整篇正文必须通过 If-Match 提交当前版本:
curl -i \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/documents/doc_123/content?format=markdown"
curl --fail-with-body \
-X PUT \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
-H 'If-Match: "sha256:从上一步响应取得"' \
-H "Content-Type: application/json" \
-d '{"format":"markdown","content":"# 新正文"}' \
"$DOCO_BASE_URL/api/v1/documents/doc_123/content"
缺少必须的 If-Match 返回 428,版本冲突返回 409。收到冲突后应重新读取正文、合并改动,再重试;不要盲目覆盖。
API 一览
身份
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/me |
任一有效 Token | 返回调用者和 Token scopes |
GET |
/me/quota |
任一有效 Token | 当前工作区配额自查:知识库数、文档与文件夹用量与上限、单文档字符上限等 |
文档变更推送
GET /events 使用 Server-Sent Events 持续推送当前用户有权访问的文档变更,
需要 documents:read scope。首次连接从当前时刻开始;断线后把最后处理成功的
事件 ID 作为 Last-Event-ID 传回即可续传:
curl -N \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/events"
curl -N \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
-H "Last-Event-ID: 42" \
"$DOCO_BASE_URL/api/v1/events"
文档事件包括:
document.createddocument.metadata.updateddocument.content.updateddocument.deleted
每条事件都有递增的 id,data 中包含 event_id、workspace_id、
document_id、created_at 和文档基本信息。服务端每 15 秒发送一次注释心跳,
并建议客户端断线 5 秒后重连。
事件默认保留 7 天,可通过 DOCO_EVENT_RETENTION_DAYS 调整。游标早于保留窗口时,
服务端发送 sync.required;此时先用
GET /documents?updated_since=<last_sync_time>&sort=updated_at 补齐,再使用
sync.required 的 resume_from 继续监听。Token 被撤销或过期后,流会发送
auth.revoked 并关闭(撤销/过期至多在 1 个心跳周期 DOCO_EVENT_HEARTBEAT_MS 内生效,默认上界 15 秒)。单 Token 默认最多 3 条并发流,可通过
DOCO_SSE_MAX_CONNECTIONS_PER_TOKEN 调整。
块级变更水位
GET /documents/{id}/changes 把开放 API 与浏览器 Hocuspocus/Yjs 写入统一为
顶层稳定块的 added、removed、modified、moved 变更。首次不传 after,
响应返回当前 manifest 和不透明 cursor;保存该游标,后续原样传回:
curl --fail-with-body \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/documents/doc_123/changes"
curl --fail-with-body --get \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
--data-urlencode "after=<上次返回的 cursor>" \
"$DOCO_BASE_URL/api/v1/documents/doc_123/changes"
每条变更包含块 ID、类型、文本摘录、新旧位置、来源版本和对应游标。freshness=current
且 complete=true 才表示从给定游标到当前水位连续完整。派生链失败、重建或游标早于保留窗口时,
服务端返回 sync_required=true;此时重新读取正文并获取新基线,禁止把不完整结果当成穷举。
变更集每篇文档默认保留 1000 个水位,可通过 DOCO_CHANGE_RETENTION 调整。
显式关系与反向链接
正文中的 Markdown 链接可直接使用稳定 Doco URI:
[部署依据](doco://doc/doc_123#block=block_01JXYZ0123456789ABCDEFGHJK)
每次正文持久化都会对该文档的内链做全量、幂等重抽,形成 refers_to 关系和反向链接。
需要表达明确语义时,可通过 POST /relations 创建 supports、depends_on、contradicts
等注册谓词;所有关系都记录来源块、来源版本、创建者和锚文本。
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/relation-types |
documents:read |
列出注册谓词 |
GET |
/documents/{id}/relations?direction=both |
documents:read |
查询正向与反向关系,可按 predicate 过滤 |
POST |
/relations |
documents:write |
从稳定来源块创建显式关系,建议带 Idempotency-Key |
DELETE |
/relations/{id} |
documents:write |
删除手工关系;内联关系需编辑正文链接 |
{
"source_document_id": "doc_source",
"source_block_id": "block_01JXYZ0123456789ABCDEFGHJK",
"target_uri": "doco://doc/doc_target#block=block_01JABC0123456789ABCDEFGHJK",
"predicate": "depends_on",
"anchor_text": "发布流程依赖回滚预案"
}
目标块或文档删除后,关系不会静默消失,而会分别返回 dangling_target_block 或
dangling_target_document。调用方还必须检查 projection_freshness;为 stale 时,正文仍是权威事实,
但关系视图不能当作完整结果。反向查询始终经过当前用户权限过滤。
知识库
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/knowledge-bases |
knowledge-bases:read |
列出知识库 |
POST |
/knowledge-bases |
knowledge-bases:write |
创建知识库 |
GET |
/knowledge-bases/{id} |
knowledge-bases:read |
获取知识库 |
PATCH |
/knowledge-bases/{id} |
knowledge-bases:write |
重命名知识库 |
DELETE |
/knowledge-bases/{id} |
knowledge-bases:write |
删除知识库及其内容,正文需传 confirm_id |
GET |
/knowledge-bases/{id}/tree |
knowledge-bases:read |
获取完整文件夹与文档树 |
GET |
/knowledge-bases/{id}/export |
knowledge-bases:read + documents:read |
导出 ZIP |
创建知识库:
{ "name": "产品知识库" }
删除知识库:
{ "confirm_id": "kb_123" }
文件夹
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
POST |
/folders |
knowledge-bases:write |
创建文件夹 |
GET |
/folders/{id} |
knowledge-bases:read |
获取文件夹 |
PATCH |
/folders/{id} |
knowledge-bases:write |
重命名或移动文件夹 |
DELETE |
/folders/{id} |
knowledge-bases:write |
删除文件夹及其内容 |
GET |
/folders/{id}/children |
knowledge-bases:read + documents:read |
获取直接子文件夹和文档 |
创建或移动文件夹:
{
"name": "接口设计",
"knowledge_base_id": 123,
"parent_id": null
}
文档
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/documents |
documents:read |
搜索或筛选文档 |
POST |
/documents |
documents:write |
创建文档,可同时写入正文 |
GET |
/documents/{id} |
documents:read |
获取文档元数据 |
PATCH |
/documents/{id} |
documents:write |
修改标题、位置和设置 |
DELETE |
/documents/{id} |
documents:write |
删除文档、正文和附件 |
GET |
/documents/{id}/path |
documents:read |
获取知识库与文件夹路径 |
文档列表支持:
GET /documents?q=关键词&knowledge_base_id=123&folder_id=456&limit=50
增量同步:
GET /documents?updated_since=2026-07-27T00:00:00Z&sort=updated_at
updated_since:ISO 8601 时间或毫秒时间戳,仅返回updated_at不早于该时刻的文档(含边界,客户端按id去重,宁重不漏)。无时区后缀的 ISO 串统一按 UTC 解释,建议显式带Z(如2026-07-27T00:00:00Z)。sort=updated_at:按更新时间倒序(默认按id升序);该排序下的分页游标与默认排序不通用,混用返回400。
语言筛选:
GET /documents?locale=en-US
GET /documents?include_variants=true&knowledge_base_id=123
文档元数据会额外返回 document_set_id、document_set_uri、locale、variant_role、
workflow_status、translation_freshness、translated_from_document_id 和
translated_from_version;单语言文档对应字段为 null 或 current。
创建文档请求:
{
"title": "接口设计",
"knowledge_base_id": 123,
"folder_id": null,
"document_type": "document",
"heading_numbered": false,
"background_color": "#faf9f5",
"collapsed_block_ids": [],
"content": {
"format": "markdown",
"content": "# 接口设计\n\n正文"
}
}
document_type 可为 document 或 spreadsheet。
带新鲜度证明的全文搜索
GET /search/v2 使用 SQLite FTS5 同时检索标题与正文,并返回目录路径、标题路径、相邻块上下文、
命中次数、BM25 分数解释和搜索投影水位。索引跟随开放 API、浏览器协同写入和回滚同步更新;
只有 indexed_version 与正文 source_version 一致的结果才会进入 v2 响应。
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/search/v2?q={关键词} |
documents:read |
topk 快速取前 N 条,或 exhaustive + cursor 完整遍历;include_summary=true 附文档摘要 |
GET |
/search?q={关键词} |
documents:read |
兼容版搜索,仅返回块 ID 与摘要 |
curl --fail-with-body \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/search/v2?q=部署流程&mode=exhaustive&knowledge_base_id=123&limit=20"
v2 响应中的 projection.complete=true 才表示当前可见范围内的搜索投影全部追上正文水位;
为 false 时会返回陈旧文档数量与 ID,不能根据本次结果断言“知识不存在”。正文命中结果包含:
{
"document_id": "doc_123",
"title": "生产发布手册",
"knowledge_base_id": 123,
"folder_id": 456,
"document_type": "document",
"block_id": "block_01JXYZ0123456789ABCDEFGHJK",
"document_uri": "doco://doc/doc_123",
"path_text": "运维知识库 / 发布 / 生产发布手册",
"heading_path": ["发布", "部署流程"],
"matched_in": "content",
"context": { "before": "…", "match": "部署流程分为构建、灰度与回滚三步", "after": "…" },
"source_version": "sha256:…",
"indexed_version": "sha256:…",
"freshness": "current",
"updated_at": 1785236508000
}
标题命中时 matched_in 为 title,block_id 为 null。同一文档多个块命中时可能返回
多条结果;这让 Agent 能直接选择目标段落,而不必先抓取整篇正文。
分层摘要
摘要是带来源版本的派生视图,不是正文真相。章节、文档、文件夹和知识库均可读取摘要;未配置模型时
仍会返回标题、outline 与首段组成的确定性 fallback。freshness=stale 必须被显式处理,不能把旧摘要
当作当前事实;pinned=true 只阻止自动覆盖,不隐藏来源变化。
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/documents/{id}/summary |
documents:read |
文档摘要;传 block_id 读取章节摘要,传 query 返回不持久化的 query-focused 摘要 |
PUT |
/documents/{id}/summary |
documents:write |
保存人工摘要;必须携当前摘要 ETag 与 base_source_version |
POST |
/documents/{id}/summary/rebuild |
documents:write |
确定性立即重建;generator=model 另需 summaries:generate,返回异步 job |
GET/PUT/POST |
/folders/{id}/summary[\/rebuild] |
knowledge-bases:read/write |
文件夹摘要与重建 |
GET/PUT/POST |
/knowledge-bases/{id}/summary[\/rebuild] |
knowledge-bases:read/write |
知识库摘要与重建 |
GET |
/summary-jobs/{id} |
documents:read |
查询模型任务;来源变化或运行期被钉住时状态为 obsolete |
curl --fail-with-body \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/documents/doc_123/summary?query=部署风险"
响应包含 source_version、current_source_version、source_block_ids、source_document_ids、
coverage、generator、status、freshness、pinned 和 summary_version。人工保存采用摘要版本与
来源版本双重保护;模型任务在提交结果前再次校验两者,过期结果不会覆盖当前摘要。
显式概念与候选治理
显式概念使用稳定 doco://concept/{id} 身份,支持名称、别名、描述、按 BCP 47 语言区分的标签、可选规范文档、来源块、
alias_of/broader_than/related_to 关系和 [valid_from, valid_to) 时间有效性。显式清单与候选严格分离:
GET /concepts 的 complete=true 可以声明显式概念枚举完整;候选只说明当前确定性抽取器发现了什么,
未经审核不会进入显式知识。
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET/POST |
/concepts |
concepts:read/write |
按知识库完整枚举或创建显式概念;q 同时匹配规范名和别名 |
GET/PATCH |
/concepts/{id} |
concepts:read/write |
读取或用 ETag 修改概念;已合并旧 ID 继续解析到规范概念 |
POST |
/concepts/{id}/sources |
concepts:write |
添加带文档、块和来源版本的证据 |
POST |
/concepts/{id}/relations |
concepts:write |
添加少量注册概念关系,并拒绝自环与 broader_than 环 |
POST |
/concepts/{id}/merge |
concepts:write |
合并后保留旧 ID、别名、关系和审计历史;同时保护源/目标双版本 |
GET |
/concepts/{id}/traverse?at={毫秒}&direction={方向}&predicate={谓词} |
concepts:read |
按时间、方向和注册谓词遍历概念关系 |
GET/POST |
/concept-candidates[\/extract] |
concepts:read/write |
按需从文档标题和显式链接锚文本生成候选;不调用模型 |
POST |
/concept-candidates/{id}/accept |
concepts:write |
来源仍为 current 时新建概念或并入已有概念 |
POST |
/concept-candidates/{id}/reject |
concepts:write |
保留拒绝记录,同一 fingerprint 不反复出现 |
概念 PATCH、添加来源/关系及合并必须携 If-Match;候选接受/拒绝必须携候选 ETag。
缺失返回 428,冲突或证据已变化返回 409。证据响应显式给出 source_version、当前版本、
freshness 与 dangling 状态。对同一来源再次添加证据会在原关系上更新来源版本;重新提取仍为
pending 的同 fingerprint 候选时,也会刷新候选的来源版本并在响应中计入 refreshed,避免过期证据
形成无法审核的死路。概念候选首版只有确定性标题/链接来源;模型抽取、Embedding、GraphRAG、
复杂本体和图数据库均不属于本阶段能力。
概念的主名称保持向后兼容;多语言展示名称和描述放在 labels 中。创建或修改概念时可传入多个语言标签,同一概念每个 locale 只能有一个标签:
{
"name": "发布流程",
"labels": [
{ "locale": "zh-CN", "name": "发布流程", "description": "生产发布的标准步骤" },
{ "locale": "en-US", "name": "Release Process", "description": "Standard production release steps", "origin": "manual" }
]
}
读取概念时,data.labels 返回 locale、name、description、origin、创建者和时间戳。修改标签与修改概念本体一样需要读取概念 ETag 并通过 If-Match 提交;冲突返回 409。
正文
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/documents/{id}/content |
documents:read |
读取正文 |
GET |
/documents/{id}/outline |
documents:read |
读取标题层级、稳定块 ID 与章节块区间 |
GET |
/documents/{id}/read |
documents:read |
按 around / token 预算局部读取并用 cursor 续读 |
PUT |
/documents/{id}/content |
documents:write |
替换整篇正文,必须传 If-Match |
长文档先调 /outline,再以 around=<block_id> 读取目标附近,或用 max_tokens 从头分页。
局部读取支持 markdown、tiptap-json、plain-text、outline 四种 view。next_cursor 绑定正文版本;
正文发生变化后旧游标返回 409 read_cursor_stale。若单个块本身超过预算,服务端仍返回该块并设置
budget_exceeded=true,不会制造无法前进的空页。
读取时可通过 format 选择:
tiptap-json:无损标准格式,适合结构化编辑。markdown:便于 Agent 阅读与生成,复杂节点可能返回降级警告。html:经过服务端清洗的 HTML。
Markdown 锚点往返:format=markdown&annotate=anchors 会在每个顶层块前注入锚点注释(标题块附 slug):
<!--@block=block_01JXK3... #部署流程-->
# 部署流程
<!--@block=block_01JXP9...-->
第一段。
把这份 Markdown 改完后以 PUT /content(format: markdown)整篇写回:带锚点的块保留原块 ID,
未标注的新块获得新 ID,重复锚点返回 422。这样 Agent 用最母语的 Markdown 读写,也能保持块级寻址不漂移。
注意:锚点归属于其后第一个块——在某个块之前插入新内容时,把新内容写在锚点注释之前。
写入 Markdown:
{
"format": "markdown",
"content": "# 标题\n\n- 第一项\n- 第二项"
}
写入 Tiptap JSON:
{
"format": "tiptap-json",
"document": {
"type": "doc",
"content": [
{
"type": "paragraph",
"attrs": { "id": "block_01JXYZ0123456789ABCDEFGHJK" },
"content": [{ "type": "text", "text": "Hello" }]
}
]
}
}
服务端会为缺少 ID 的块补充稳定 ID。块 ID 格式为 block_<ULID>。
版本与回滚
每次开放 API 修改正文或块之前,Doco 都会保存当前 YDoc 的精确快照。浏览器中的协同编辑不会创建这些版本;
它们专门用于 Agent 或脚本写错后的恢复。服务端默认保留每篇文档最近 20 份,可通过
DOCO_VERSION_RETENTION 调整。
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/documents/{id}/versions |
documents:read |
按 seq 从新到旧列出版本;返回时间、创建者和字节数,不返回快照 blob |
POST |
/documents/{id}/versions/{seq}/rollback |
documents:write |
恢复指定版本;必须传当前 If-Match,回滚前也会保存当前版本 |
列出版本:
curl --fail-with-body \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/documents/doc_123/versions"
回滚前先读取当前 ETag,再提交目标 seq:
CURRENT_ETAG=$(curl -sSI \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
"$DOCO_BASE_URL/api/v1/documents/doc_123/content" \
| awk 'tolower($1) == "etag:" { print $2 }' | tr -d '\r')
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
-H "If-Match: $CURRENT_ETAG" \
"$DOCO_BASE_URL/api/v1/documents/doc_123/versions/3/rollback"
缺少 If-Match 返回 428,当前版本已变化返回 409,目标 seq 不存在返回 404。回滚成功后响应
ETag 和 data.version 都是恢复操作完成后的新版本;它不要求与历史快照的旧 ETag 相同。
块
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
GET |
/documents/{id}/blocks |
documents:read |
获取顶层块;recursive=true 返回所有层级 |
GET |
/documents/{id}/blocks/{blockId} |
documents:read |
获取指定块 |
POST |
/documents/{id}/blocks |
documents:write |
插入一个或多个块 |
PATCH |
/documents/{id}/blocks/{blockId} |
documents:write |
替换节点、属性或内容 |
DELETE |
/documents/{id}/blocks/{blockId} |
documents:write |
删除块 |
POST |
/documents/{id}/batch |
documents:write |
在单个 Yjs 事务内执行最多 100 个操作 |
插入块时,position 必须且只能指定一种定位方式:document_start、document_end、before_block_id、after_block_id、parent_block_id 或 after_heading。
{
"position": { "document_end": true },
"nodes": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "追加内容" }]
}
]
}
after_heading 按标题文本定位(告诉它章节名就够了):服务端在顶层标题块中精确匹配(忽略大小写与多余空白),
退化为首个包含匹配,插入到该标题之后;无匹配返回 422 heading_not_found。批量操作 insert 同样支持。
{
"position": { "after_heading": "部署流程" },
"nodes": [{ "type": "paragraph", "content": [{ "type": "text", "text": "插在小节开头" }] }]
}
批量操作:
{
"base_version": "sha256:当前版本",
"operations": [
{
"op": "insert",
"position": { "document_end": true },
"nodes": [{ "type": "paragraph", "content": [{ "type": "text", "text": "新增段落" }] }]
},
{
"op": "delete",
"block_id": "block_01JXYZ0123456789ABCDEFGHJK"
}
]
}
批量操作支持 insert、delete 和 replace。replace 需提交 block_id 与完整 node。整个批次要么全部成功,要么全部失败;修改单个块的属性或内容可使用块 PATCH 接口。
附件
| 方法 | 路径 | Scope | 说明 |
|---|---|---|---|
POST |
/attachments |
attachments:write |
以 multipart/form-data 上传附件 |
GET |
/attachments/{id} |
attachments:read |
下载附件 |
GET |
/attachments/{id}/metadata |
attachments:read |
读取附件元数据 |
DELETE |
/attachments/{id} |
attachments:write + documents:write |
删除附件;被正文引用时需 force=true |
curl --fail-with-body \
-X POST \
-H "Authorization: Bearer $DOCO_API_TOKEN" \
-H "Idempotency-Key: upload-cover-001" \
-F "document_id=doc_123" \
-F "file=@cover.png" \
"$DOCO_BASE_URL/api/v1/attachments"
正文图片节点应保存 attachmentId。服务端会把 src 规范化为 /api/v1/attachments/{id}。强制删除被引用附件时,服务端也会移除相应图片节点。
Agent 使用建议
- 启动时先调用
/me,确认 Token 有效及 scopes 足够;需要自查限额时用/me/quota。 - 已知内容关键词时优先用
/search/v2(MCP:doco_search_v2),先检查projection.complete,再依据路径、标题路径、前后文与块 ID 定位;不完整结果不能证明“知识不存在”。 - 陌生长文档先读
/documents/{id}/outline,再用/documents/{id}/read按around或 token 预算局部读取;旧游标返回read_cursor_stale时从新版本重读。 - 整篇阅读使用
format=markdown;需要无损结构化修改时使用tiptap-json或块 API。 - 整篇 Markdown 读写用
annotate=anchors往返,未改动的块凭锚点保留原 ID;只改局部时优先块 API 或after_heading定位。 - 修改前保存
ETag;遇到409重新读取并合并,不要覆盖他人的更新。 - 大范围修改前可先确认
/versions正常记录;写错后用最新 ETag 回滚,不要绕过并发检查。 - 对可重试的创建、上传和批量请求使用稳定的
Idempotency-Key。 - 记录
request_id,排查问题时可关联服务端审计日志。 - 读取列表直到
has_more=false;增量同步用GET /documents?updated_since=…&sort=updated_at或块变更游标,不要全量爬库。
机器可读规范
完整字段、Schema、状态码和约束以运行时 OpenAPI 3.1(当前版本 1.9.0)文档为准:
curl --fail-with-body "$DOCO_BASE_URL/api/openapi.json"
如果本文与机器可读规范不一致,应以 /api/openapi.json 和实际响应为准,并提交文档修正。