DocoDocoOpen API v1

Hook up your agent with one command

Run doco login first to finish browser authorization, then register the MCP server — your agent immediately gets 29 tools for document read/write, search, relations, summaries, concepts, multilingual content, and change awareness.

claude mcp add doco -- npx -y --package doco-agent-cli doco mcp

Developer resources

Machine contracts for agents and API tools to read directly.

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"

成功响应统一包含 datarequest_id

{
  "data": {
    "user": {
      "id": "user_123",
      "email": "[email protected]",
      "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_localeresolved_localefallback_used;写入始终使用具体 doc_*,继续先读 ETag、再使用 If-Match。块级翻译状态通过 /translation-units?locale=en-US 读取,人工完成后用 review_status=current|ignored 标记,冲突不会被静默覆盖。Search v2 支持 locale=en-USlocale=all,SSE 支持 document_set_idlocale 过滤,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 个翻译单元,不指定时默认处理 missingsource_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-LimitX-RateLimit-RemainingX-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 范围为 1100。继续请求时使用上一页 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.created
  • document.metadata.updated
  • document.content.updated
  • document.deleted

每条事件都有递增的 iddata 中包含 event_idworkspace_iddocument_idcreated_at 和文档基本信息。服务端每 15 秒发送一次注释心跳, 并建议客户端断线 5 秒后重连。

事件默认保留 7 天,可通过 DOCO_EVENT_RETENTION_DAYS 调整。游标早于保留窗口时, 服务端发送 sync.required;此时先用 GET /documents?updated_since=<last_sync_time>&sort=updated_at 补齐,再使用 sync.requiredresume_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 写入统一为 顶层稳定块的 addedremovedmodifiedmoved 变更。首次不传 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=currentcomplete=true 才表示从给定游标到当前水位连续完整。派生链失败、重建或游标早于保留窗口时, 服务端返回 sync_required=true;此时重新读取正文并获取新基线,禁止把不完整结果当成穷举。 变更集每篇文档默认保留 1000 个水位,可通过 DOCO_CHANGE_RETENTION 调整。

显式关系与反向链接

正文中的 Markdown 链接可直接使用稳定 Doco URI:

[部署依据](doco://doc/doc_123#block=block_01JXYZ0123456789ABCDEFGHJK)

每次正文持久化都会对该文档的内链做全量、幂等重抽,形成 refers_to 关系和反向链接。 需要表达明确语义时,可通过 POST /relations 创建 supportsdepends_oncontradicts 等注册谓词;所有关系都记录来源块、来源版本、创建者和锚文本。

方法 路径 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_blockdangling_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_iddocument_set_urilocalevariant_roleworkflow_statustranslation_freshnesstranslated_from_document_idtranslated_from_version;单语言文档对应字段为 nullcurrent

创建文档请求:

{
  "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 可为 documentspreadsheet

带新鲜度证明的全文搜索

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_intitleblock_idnull。同一文档多个块命中时可能返回 多条结果;这让 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_versioncurrent_source_versionsource_block_idssource_document_idscoveragegeneratorstatusfreshnesspinnedsummary_version。人工保存采用摘要版本与 来源版本双重保护;模型任务在提交结果前再次校验两者,过期结果不会覆盖当前摘要。

显式概念与候选治理

显式概念使用稳定 doco://concept/{id} 身份,支持名称、别名、描述、按 BCP 47 语言区分的标签、可选规范文档、来源块、 alias_of/broader_than/related_to 关系和 [valid_from, valid_to) 时间有效性。显式清单与候选严格分离: GET /conceptscomplete=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 返回 localenamedescriptionorigin、创建者和时间戳。修改标签与修改概念本体一样需要读取概念 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 从头分页。 局部读取支持 markdowntiptap-jsonplain-textoutline 四种 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 /contentformat: 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。回滚成功后响应 ETagdata.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_startdocument_endbefore_block_idafter_block_idparent_block_idafter_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"
    }
  ]
}

批量操作支持 insertdeletereplacereplace 需提交 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 "[email protected]" \
  "$DOCO_BASE_URL/api/v1/attachments"

正文图片节点应保存 attachmentId。服务端会把 src 规范化为 /api/v1/attachments/{id}。强制删除被引用附件时,服务端也会移除相应图片节点。

Agent 使用建议

  1. 启动时先调用 /me,确认 Token 有效及 scopes 足够;需要自查限额时用 /me/quota
  2. 已知内容关键词时优先用 /search/v2(MCP:doco_search_v2),先检查 projection.complete,再依据路径、标题路径、前后文与块 ID 定位;不完整结果不能证明“知识不存在”。
  3. 陌生长文档先读 /documents/{id}/outline,再用 /documents/{id}/readaround 或 token 预算局部读取;旧游标返回 read_cursor_stale 时从新版本重读。
  4. 整篇阅读使用 format=markdown;需要无损结构化修改时使用 tiptap-json 或块 API。
  5. 整篇 Markdown 读写用 annotate=anchors 往返,未改动的块凭锚点保留原 ID;只改局部时优先块 API 或 after_heading 定位。
  6. 修改前保存 ETag;遇到 409 重新读取并合并,不要覆盖他人的更新。
  7. 大范围修改前可先确认 /versions 正常记录;写错后用最新 ETag 回滚,不要绕过并发检查。
  8. 对可重试的创建、上传和批量请求使用稳定的 Idempotency-Key
  9. 记录 request_id,排查问题时可关联服务端审计日志。
  10. 读取列表直到 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 和实际响应为准,并提交文档修正。