Documize 5.14 REST API 使用详解
Documize Community 5.14.0 REST API 全链实测:Basic 换 JWT、空间/文档/页面/附件/用户/标签分类 CRUD、全文检索与索引重建、空间与文档权限、HTML 导出,含 ?type=saved、page.id、全量更新、中文检索等排障实录与社区版停更风险分析。
Documize 5.14 REST API 使用详解
目标:把 Documize Community 5.14.0 的
/api接口按「认证 → 空间 → 文档 → 页面 → 附件 → 用户 → 标签分类 → 搜索索引 → 权限 → 导出」的顺序完整走一遍。每个端点都给出可直接执行的 curl 命令与本次环境的真实回显,并沉淀 405 路由、?type=saved、page.id、documentId校验、中文检索等排障实录。
一、环境信息
| 项目 | 值 |
|---|---|
| 操作系统 | Ubuntu 24.04 LTS(x86_64) |
| Documize | Community v5.14.0(build 1758297927) |
| 数据库 | PostgreSQL 16.15(本机 5432) |
| 服务地址 | http://127.0.0.1:8139 |
| API 前缀 | /api |
| 演示管理员 | admin@quzuo.local / q1w2e3r4 |
| 演示普通用户 | apitester2@quzuo.local / q1w2e3r4 |
| 认证方式 | HTTP Basic 换 JWT,后续 Authorization: Bearer <token> |
| 实测日期 | 2026-11-02 |
💡 说明:本文所有请求都在同一台 Ubuntu 24.04 服务器上对本机 8139 端口执行,回显逐行照抄。文中出现的
dbk开头 ID 是本次实测真实生成的对象 ID,读者在自己环境执行时以各接口返回的 ID 为准。
二、前置条件
2.1 服务已启动并可访问
本文不重复安装步骤,但要求 8139 上已有正常模式的 Documize 进程,且数据库已完成 setup。先确认版本接口:
curl -s http://127.0.0.1:8139/api/public/version -w " [HTTP %{http_code}]\n"
实测回显:
5.14.0 [HTTP 200]
若返回为空或连接失败,说明进程未启动或端口不是 8139;安装与首次 setup 见 《Ubuntu 24.04 裸机安装 Documize 5.14 与 PostgreSQL 16 实战》。
2.2 演示账号与工具
本次环境的组织、管理员账号由 setup 向导创建:管理员邮箱 admin@quzuo.local,密码 q1w2e3r4。API 调试只需要 curl 与 python3(用于解析 JSON 与拼接 ID),不需要额外 SDK。
为避免每次请求都重复写长令牌,先在当前 shell 里设置两个变量并保持会话不关闭:
export BASE=http://127.0.0.1:8139
curl -s -u ":admin@quzuo.local:q1w2e3r4" -X POST $BASE/api/public/authenticate -o login.json -w "HTTP %{http_code}\n"
export TOKEN=$(python3 -c "import json;print(json.load(open('login.json'))['token'])")
实测回显:
HTTP 200
令牌长度可用下面的命令确认:
python3 -c "import json;print('TOKEN_LEN',len(json.load(open('login.json'))['token']))"
实测回显:
TOKEN_LEN 243
⚠️ 注意:
export只在当前 shell 有效;换终端或重新登录后要重新设置TOKEN。令牌的签名密钥由DOCUMIZESALT决定,salt 不固定时进程重启会使旧令牌全部失效。
三、API 总览与认证
本章先建立对路由分组的整体认识,再完成一次完整的「Basic 换 JWT → 携带 Bearer 访问私有接口 → 校验令牌」闭环。后续所有业务接口都依赖本章拿到的 TOKEN。
3.1 公共路由与私有路由
Documize 把 Web UI 与 API 放在同一进程同一端口,JSON 接口统一以 /api 开头,源码中分为 AddPublic 与 AddPrivate 两组:
| 分组 | 是否需要令牌 | 典型路径 | 用途 |
|---|---|---|---|
| 公共 | 否 | /api/public/version、/api/public/meta | 版本、站点元信息 |
| 公共 | 否 | /api/public/authenticate | Basic 认证换 JWT |
| 公共 | 否 | /api/public/validate | 校验令牌并返回当前用户 |
| 公共 | 否 | /api/public/attachment/{orgID}/{attachmentID}?token= | 附件下载(需查询参数令牌) |
| 私有 | 是 | /api/space、/api/documents、/api/users | 业务 CRUD |
| 私有 | 是 | /api/search、/api/global/search/reindex | 检索与索引重建 |
| 私有 | 是 | /api/export、/api/fetch | 导出与批量读取 |
💡 说明:公共路由并不等于「可以匿名读写」。附件下载虽然挂在
/api/public下,但仍要求token查询参数;无令牌访问会返回 403(见第七章)。
3.2 版本与站点元信息
公共元信息接口无需令牌,适合做健康检查与部署核对:
curl -s $BASE/api/public/meta -o meta.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('meta.json'));print({k:d[k] for k in ['orgId','title','message','authProvider','version']})"
实测回显:
HTTP 200
{'orgId': 'dbk3ulk0ptfkf0kvebc0', 'title': 'QuzuoLab', 'message': 'Knowledge Base', 'authProvider': 'documize', 'version': '5.14.0'}
返回字段中 orgId 是当前组织的内部 ID,后续权限接口需要它;authProvider 为 documize 表示使用本地账号体系,若接入 LDAP/Keycloak 会变成对应值。
3.3 登录换取 JWT
登录接口使用 HTTP Basic,用户名部分必须是 domain:email:password 三段,domain 可留空(即冒号前为空):
curl -s -u ":admin@quzuo.local:q1w2e3r4" -X POST $BASE/api/public/authenticate -o login.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('login.json'));print('TOKEN_LEN',len(d['token']));print('user',d['user']['email'],'admin',d['user']['admin'])"
实测回显:
HTTP 200
TOKEN_LEN 243
user admin@quzuo.local admin True
响应体同时返回 token 与 user 对象。user.admin=True 表示该账号是组织管理员;普通编辑器账号会是 admin=False(见第八章)。
⚠️ 注意:Basic 串里的第一个冒号用于分隔 domain,邮箱里的
@不影响解析,但密码中若出现冒号会被截断。示例密码q1w2e3r4不含冒号,可直接使用。
3.4 令牌校验与请求头
拿到令牌后,所有私有接口都要加请求头:
Authorization: Bearer <token>
校验令牌本身是否有效,可调用公共校验接口:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/public/validate -o valid.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('valid.json'));print('valid fields:',list(d))"
实测回显:
HTTP 200
valid fields: ['id', 'created', 'revised', 'firstname', 'lastname', 'email', 'initials', 'active', 'editor', 'admin', 'viewUsers', 'analytics', 'global', 'lastVersion', 'theme', 'locale', 'accounts', 'groups']
返回的是当前用户完整对象,字段清单可直接用来判断账号权限位:editor(可编辑内容)、admin(组织管理)、viewUsers(可查看用户列表)、analytics、global(全局管理员)。
3.5 JWT 载荷与 salt
Documize 的令牌是标准 JWT,签名算法 HS256。把载荷段 base64url 解码即可看到声明:
python3 - <<'EOF'
import json,base64
t=json.load(open('login.json'))['token']
p=t.split('.')[1]
p+='='*(-len(p)%4)
d=json.loads(base64.urlsafe_b64decode(p))
print(json.dumps(d,ensure_ascii=False,separators=(',',':')))
EOF
实测回显:
{"domain":"","exp":1825150428,"iss":"Documize","org":"dbk3ulk0ptfkf0kvebc0","sub":"webapp","user":"dbk3ulk0ptfkf0kvebcg"}
把 exp 换算成日期:
date -u -d @1825150428 '+%Y-%m-%dT%H:%M:%SZ'
实测回显:
2027-11-02T10:13:48Z
签发时间对应的查询日是 2026-11-02,因此令牌有效期约一年。org 与 user 是组织、用户内部 ID,服务端用它做租户隔离;sub=webapp、iss=Documize 是固定值。
💡 技术拓展:JWT 的签名算法不止本文用的 HS256。常见对比如下——
算法 密钥类型 特点 适用场景 HS256/HS384/HS512 共享密钥 对称签名,签发与验签同一密钥,性能好 单服务、可信内网 RS256/RS384/RS512 RSA 公私钥 私钥签发、公钥验签,可公开验签方 多服务、第三方接入 ES256/ES384 ECDSA 公私钥 密钥更短、签名更快 资源受限、移动端 PS256 RSA-PSS 填充方式更安全 高安全要求 本文实际采用 Documize 内置的 HS256:签名密钥就是启动配置里的
salt,所有实例必须共享同一个 salt 且连同一个数据库,否则一个实例签发的令牌无法被另一个实例接受。
3.6 认证与授权失败的三种表现
三类失败要区分清楚,排障时才不会把权限问题误判成登录问题:
| 场景 | 实测请求 | 返回 |
|---|---|---|
| 未带令牌 | GET /api/space | {"Error":"Unauthorized"} 401 |
| 令牌伪造 | Authorization: Bearer invalid.token.value | {"Error":"Unauthorized"} 401 |
| 密码错误 | Basic 密码改为 wrongpass | {"Error":"Unauthorized"} 401 |
| 已登录但无权限 | 普通用户 GET /api/users | {"Error":"Forbidden"} 403 |
无令牌访问空间列表的实测:
curl -s $BASE/api/space -w "HTTP %{http_code}\n"
实测回显:
{Error: 'Unauthorized'}HTTP 401
错误体是 Go 的 JSON 编码格式,{Error: 'Unauthorized'} 是服务端 response.WriteUnauthorizedError 的输出,不是标准 JSON 双引号格式,脚本解析时不能直接 json.loads。
四、空间 API
空间(Space)是文档的归属单元,也是权限的第一层边界。本章完成空间的新增、查询、改名、删除与负向验证,后续文档都必须挂在某个 spaceId 下。
4.1 列出可见空间
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/space -o spaces.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('spaces.json'));print('count',len(d));[print(x['id'],x['name'],'type',x['spaceType']) for x in d]"
实测回显:
HTTP 200
count 2
dbk3vk40ptfkf0kvebdg Engineering type 2
dbk59a40ptfvtanpkhlg Search Lab type 3
spaceType=2 是私有空间,3 是公开/共享空间;接口只返回当前用户有查看权限的空间。
4.2 创建空间
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/space \
-d '{"name":"API Lab","icon":"book"}' -o sp.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('sp.json'));print(d['id'],d['name'],'spaceType',d['spaceType'])"
export SP=$(python3 -c "import json;print(json.load(open('sp.json'))['id'])")
实测回显:
HTTP 200
dbk66as0ptfr4u8acbhg API Lab spaceType 2
请求体只需要 name;icon 是可选图标名,labelId 可绑定标签。创建成功后立即把 id 存入 SP,后面的文档创建要用它。
4.3 查询单个空间
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/space/$SP -w "\nHTTP %{http_code}\n"
实测回显:
{"id":"dbk66as0ptfr4u8acbhg","created":"2026-11-02T10:17:15.056197Z","revised":"2026-11-02T10:17:15.056197Z","name":"API Lab","desc":"","orgId":"dbk3ulk0ptfkf0kvebc0","userId":"dbk3ulk0ptfkf0kvebcg","labelId":"","spaceType":2,"countCategory":0,"countContent":0,"icon":"book","lifecycle":1,"likes":""}
HTTP 200
countContent 是该空间下文档数量,lifecycle=1 表示正常在线;orgId 与 userId 用于后续权限记录。
4.4 更新空间
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X PUT $BASE/api/space/$SP \
-d '{"name":"API Lab v2","icon":"book"}' -w "\nHTTP %{http_code}\n"
实测回显:
{"id":"dbk66as0ptfr4u8acbhg","created":"0001-01-01T00:00:00Z","revised":"0001-01-01T00:00:00Z","name":"API Lab v2","desc":"","orgId":"","userId":"","labelId":"","spaceType":0,"countCategory":0,"countContent":0,"icon":"book","lifecycle":0,"likes":""}
HTTP 200
⚠️ 注意:PUT 返回的是服务端接收到的请求模型,不是数据库里的最终对象,所以
created、orgId、spaceType等字段出现零值。判断是否改名成功要再发一次GET /api/space/$SP,不要看 PUT 的回显。
4.5 删除空间
curl -s -H "Authorization: Bearer $TOKEN" -X DELETE $BASE/api/space/$SP -w "HTTP %{http_code}\n"
实测回显:
{}HTTP 200
删除成功返回空对象 {} 与 200。空间内文档会随空间一起不可见,删除前先确认已备份或已迁移文档。
4.6 负向与边界
缺少 name:
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/space -d '{"icon":"book"}' -w "HTTP %{http_code}\n"
实测回显:
{Error: 'Missing data'}HTTP 400
查询不存在的空间 ID:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/space/doesnotexist -w "HTTP %{http_code}\n"
实测回显:
{Error: 'Internal server error'}HTTP 400
⚠️ 注意:不存在的空间返回 400 且文案是
Internal server error,不是 404。编写客户端时不要用「404 判断资源不存在」,应按 400 且响应体包含该文案处理。
五、文档 API
文档(Document)位于空间内,本身不含正文,正文按页面(Page)拆分。本章先解释为什么不能直接 POST /api/documents,再给出正确的模板路由创建方式与读改删。
5.1 创建路径探测
官方路由表里 documents 只有 GET,没有 POST。实测直接 POST 与几个常见猜测路径:
| 探测请求 | 实测结果 | 结论 |
|---|---|---|
POST /api/documents | 405 | 路由未注册 POST |
PUT /api/documents/{新ID} | 403 | 不允许客户端指定 ID |
GET /api/templates/{spaceID} | null + 200 | 该路径是「已保存模板列表」,不是文档列表 |
POST /api/import/folder/{spaceID} 空 body | 400 Missing data | 是导入接口,不是新建 |
GET /api/comment | 404 page not found | 社区版无此路由 |
直接 POST 的原始回显:
HTTP 405
💡 说明:Documize 新建文档走的是模板接口
POST /api/templates/{templateID}/folder/{spaceID}?type=saved,templateID=0表示空模板。这是源码路由AddPrivate(rt, "templates/{templateID}/folder/{spaceID}", []string{"POST", "OPTIONS"}, []string{"type", "saved"}, template.Use)的强制查询参数,漏掉会命中另一个模板路由而报 404。
5.2 通过模板路由创建文档
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: text/plain' \
-X POST "$BASE/api/templates/0/folder/$SP?type=saved" \
-d 'API Runbook' -o doc.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('doc.json'));print(d['id'],d['name'],'location',d['location'])"
export DOC=$(python3 -c "import json;print(json.load(open('doc.json'))['id'])")
实测回显:
HTTP 200
dbk66as0ptfr4u8acbig API Runbook location template-0
请求体是纯文本标题,不是 JSON;excerpt 默认填 Description,sequence=99999 表示排在最后。location=template-0 说明来自空模板。
5.3 列出与查询文档
按空间列出:
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/documents?space=$SP" -o docs.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('docs.json'));print('count',len(d));[print(x['id'],x['name'],'seq',x['sequence']) for x in d]"
实测回显:
HTTP 200
count 1
dbk66as0ptfr4u8acbig API Runbook seq 99999
按文档 ID 查询单个对象:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC -o doc_get.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('doc_get.json'));print('keys',list(d))"
实测回显:
HTTP 200
keys ['id', 'created', 'revised', 'orgId', 'spaceId', 'userId', 'job', 'location', 'name', 'excerpt', 'tags', 'template', 'protection', 'approval', 'lifecycle', 'versioned', 'versionId', 'versionOrder', 'sequence', 'groupId', 'category']
对象里没有 slug 字段;文档 slug 由服务端按标题生成,出现在检索结果中(见第十章)。
5.4 更新文档
更新接口的要求容易被误用:请求体必须是 GET 返回的完整文档对象,只传 name/excerpt/tags 会返回 200 但不生效。本节改用独立文档 Doc Update Lab 演示(空间 dbk67v40ptfr4u8acc2g、文档 dbk67v40ptfr4u8acc3g),下文 $DOC 指这份独立文档,避免污染前面 API Runbook 的演示数据。实测对比:
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X PUT $BASE/api/documents/$DOC \
-d '{"name":"Doc Update Lab local","excerpt":"partial","tags":"x","lifecycle":1}' \
-w "PUT partial HTTP %{http_code}\n"
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC -o doc_p.json -w "GET after partial HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('doc_p.json'));print('after partial:',d['name'],'|',d['excerpt'],'| tags',repr(d['tags']))"
实测回显:
{}PUT partial HTTP 200
GET after partial HTTP 200
after partial: Doc Update Lab | Description | tags ''
正确做法是先 GET、本地改字段、再整体 PUT:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC -o doc.json
python3 -c "
import json
d=json.load(open('doc.json'))
d['name']='Doc Update Lab v2'; d['excerpt']='API 实测文档'; d['tags']='api'
open('doc_put.json','w').write(json.dumps(d,ensure_ascii=False))
"
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X PUT $BASE/api/documents/$DOC --data-binary @doc_put.json -w "PUT full HTTP %{http_code}\n"
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC -o doc2.json -w "GET after full HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('doc2.json'));print('after full:',d['name'],'|',d['excerpt'],'| tags',repr(d['tags']))"
实测回显:
{}PUT full HTTP 200
GET after full HTTP 200
after full: Doc Update Lab v2 | API 实测文档 | tags 'api'
⚠️ 注意:PUT 只回
{},不会回文档对象。改完必须 GET 复核;这也是排查「接口 200 但内容没变」的第一手段。
5.5 删除文档
curl -s -H "Authorization: Bearer $TOKEN" -X DELETE $BASE/api/documents/$DOC -w "HTTP %{http_code}\n"
实测回显:
{}HTTP 200
删除文档会连带删除页面、修订、附件记录;附件二进制也会按存储策略清理(见第七章)。
六、页面 API
页面是文档正文的最小单位。社区版把页面拆成「Page 元数据 + Meta 原始内容」,新增/更新都需要 NewPage 包装结构,并且有严格的 documentId 与 page.id 校验。本章是全文排障点最密集的一章。
6.1 NewPage 请求结构
页面请求体不是扁平的 Page,而是两层:
{"page":{"orgId":"dbk3ulk0ptfkf0kvebc0","documentId":"dbk66as0ptfr4u8acbig","contentType":"text","pageType":"section","level":1,"sequence":1024,"title":"认证与令牌","body":"","status":0},"meta":{"documentId":"dbk66as0ptfr4u8acbig","rawBody":"第一步:用 Basic 认证换取 JWT 令牌","config":"{}"}}
字段含义:
| 字段 | 说明 | 注意点 |
|---|---|---|
page.documentId | 所属文档 | 必须与路由里的 {documentID} 完全一致 |
page.contentType | 渲染类型 | text/wysiwyg/markdown/code 等,决定 rawBody 的渲染方式 |
page.pageType | 页面类型 | 普通章节用 section |
page.level | 层级 | 1 为顶层,2 为子节 |
page.sequence | 排序值 | 数值越大越靠后 |
page.status | 发布状态 | 数值枚举,0=published |
meta.documentId | 所属文档 | 同样必须与路由一致 |
meta.rawBody | 原始内容 | 由服务端按 contentType 渲染成 page.body |
meta.config | section 配置 | 至少传 {} |
6.2 新增页面
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/documents/$DOC/pages \
-d "{\"page\":{\"orgId\":\"dbk3ulk0ptfkf0kvebc0\",\"documentId\":\"$DOC\",\"contentType\":\"text\",\"pageType\":\"section\",\"level\":1,\"sequence\":1024,\"title\":\"认证与令牌\",\"body\":\"\",\"status\":0},\"meta\":{\"documentId\":\"$DOC\",\"rawBody\":\"第一步:用 Basic 认证换取 JWT 令牌\",\"config\":\"{}\"}}" \
-o page.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('page.json'));print(d['id'],d['title'],'status',d['status'],'revisions',d['revisions'])"
export PAGE=$(python3 -c "import json;print(json.load(open('page.json'))['id'])")
实测回显:
HTTP 200
dbk66as0ptfr4u8acbj0 认证与令牌 status 0 revisions 0
status 回显为 0,说明已发布。即使请求里传了其他数值,只要文档未开启审核保护,服务端也会强制写成 0。
新增一个 wysiwyg 页面验证 HTML 渲染:
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/documents/$DOC/pages \
-d "{\"page\":{\"orgId\":\"dbk3ulk0ptfkf0kvebc0\",\"documentId\":\"$DOC\",\"contentType\":\"wysiwyg\",\"pageType\":\"section\",\"level\":1,\"sequence\":2048,\"title\":\"空间管理\",\"body\":\"\",\"status\":0},\"meta\":{\"documentId\":\"$DOC\",\"rawBody\":\"<h2>空间管理</h2><p>POST /api/space 创建空间</p>\",\"config\":\"{}\"}}" \
-o page2.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('page2.json'));print(d['id'],d['title'],'body',repr(d['body'][:60]))"
实测回显:
HTTP 200
dbk66as0ptfr4u8acbjg 空间管理 body '<h2>空间管理</h2><p>POST /api/space 创建空间</p>'
⚠️ 注意:
rawBody是 HTML 片段,服务端渲染后写入page.body。若把 HTML 塞进text类型,页面只会按纯文本展示,标签会被转义。
6.3 列表、单页与 meta
列出文档下所有页面:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC/pages -o pages.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('pages.json'));print('count',len(d));[print(x['id'],x['title'],x['contentType'],'level',x['level'],'seq',x['sequence']) for x in d]"
实测回显:
HTTP 200
count 2
dbk66as0ptfr4u8acbj0 认证与令牌 text level 1 seq 1024
dbk66as0ptfr4u8acbjg 空间管理 wysiwyg level 1 seq 2048
读取单页对象与原始内容:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC/pages/$PAGE -w "\nHTTP %{http_code}\n"
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC/pages/$PAGE/meta -w "\nHTTP %{http_code}\n"
实测回显:
{"id":"dbk66as0ptfr4u8acbj0","created":"2026-11-02T10:17:15.220617Z","revised":"2026-11-02T10:17:15.220617Z","orgId":"dbk3ulk0ptfkf0kvebc0","documentId":"dbk66as0ptfr4u8acbig","userId":"dbk3ulk0ptfkf0kvebcg","contentType":"text","pageType":"section","blockId":"","level":1,"sequence":1024,"numbering":"","title":"认证与令牌","body":"","revisions":0,"status":0,"relativeId":""}
HTTP 200
{"id":4,"created":"2026-11-02T10:17:15.220617Z","revised":"2026-11-02T10:17:15.220617Z","orgId":"dbk3ulk0ptfkf0kvebc0","userId":"dbk3ulk0ptfkf0kvebcg","documentId":"dbk66as0ptfr4u8acbig","pageId":"dbk66as0ptfr4u8acbj0","rawBody":"第一步:用 Basic 认证换取 JWT 令牌","config":"{}","externalSource":false}
HTTP 200
meta.id 是自增数字主键,page.id 是业务引用 ID,两者不是同一个东西;后续更新页面必须用 page.id。
6.4 更新页面
更新请求同样使用 NewPage,但 page.id 必须等于路由里的页面 ID,且 documentId 必须等于路由里的文档 ID。本节与 6.5 改用独立文档 Page Lab 演示(空间 dbk66lc0ptfr4u8acbmg、文档 dbk66lc0ptfr4u8acbng、页面 dbk66lc0ptfr4u8acbo0),下文 $DOC/$PAGE 指这份独立文档与页面:
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X PUT $BASE/api/documents/$DOC/pages/$PAGE \
-d "{\"page\":{\"id\":\"$PAGE\",\"orgId\":\"dbk3ulk0ptfkf0kvebc0\",\"documentId\":\"$DOC\",\"contentType\":\"text\",\"pageType\":\"section\",\"level\":1,\"sequence\":1024,\"title\":\"配置说明\",\"body\":\"\",\"status\":0},\"meta\":{\"documentId\":\"$DOC\",\"pageId\":\"$PAGE\",\"rawBody\":\"原始内容:端口默认 8139\",\"config\":\"{}\"}}" \
-o page_up.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('page_up.json'));print(d['id'],d['title'],'revisions',d['revisions'])"
实测回显:
HTTP 200
dbk66lc0ptfr4u8acbo0 配置说明 revisions 1
revisions 从 0 变成 1,说明本次更新生成了修订。只有 status=0(published)且文档生命周期为 live 时才记修订,草稿状态会跳过。
⚠️ 排障实录:更新页面时漏传
page.id,路由校验不通过,服务端在 400 分支里对一个 nil 错误取err.Error(),直接触发空指针 panic,客户端收到的是空响应(curl 显示HTTP 000),服务端日志出现http: panic serving 127.0.0.1:45950: runtime error: invalid memory address or nil pointer dereference。修正方法:page.id与路由页面 ID 保持一致,page.documentId/meta.documentId与路由文档 ID 保持一致。
6.5 修订列表与回滚
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC/pages/$PAGE/revisions -o rev.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('rev.json'));print('revisions',len(d));[print(x['id'],x['title'],'email',x['email']) for x in d]"
实测回显:
HTTP 200
revisions 1
dbk66lc0ptfr4u8acbog 配置说明 email admin@quzuo.local
回滚到指定修订:
export REV=$(python3 -c "import json;print(json.load(open('rev.json'))[0]['id'])")
curl -s -H "Authorization: Bearer $TOKEN" -X POST $BASE/api/documents/$DOC/pages/$PAGE/revisions/$REV -o rollback.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('rollback.json'));print(d['id'],d['title'],'revisions',d['revisions'])"
实测回显:
HTTP 200
dbk66lc0ptfr4u8acbo0 配置说明 revisions 1
回滚后用 meta 接口确认内容已恢复:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC/pages/$PAGE/meta -w "\nHTTP %{http_code}\n"
实测回显:
{"id":6,"created":"2026-11-02T10:17:57.836626Z","revised":"2026-11-02T10:17:57.960498Z","orgId":"dbk3ulk0ptfkf0kvebc0","userId":"dbk3ulk0ptfkf0kvebcg","documentId":"dbk66lc0ptfr4u8acbng","pageId":"dbk66lc0ptfr4u8acbo0","rawBody":"原始内容:端口默认 8139","config":"{}","externalSource":false}
HTTP 200
rawBody 回到更新前的「原始内容:端口默认 8139」,回滚闭环完成。
6.6 页面层级与排序
层级与排序接口都接收数组,是因为 UI 可以一次拖拽多个页面。本节与 6.7 改用独立文档 Tree Lab 演示(空间 dbk679c0ptfr4u8acbsg、文档 dbk679c0ptfr4u8acbtg),先建三页再调整,下文 $PAGE 指其中的「第二节」页面 dbk679c0ptfr4u8acbug:
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/documents/$DOC/pages/level \
-d "[{\"pageId\":\"$PAGE\",\"level\":2}]" -w "HTTP %{http_code}\n"
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/documents/$DOC/pages/sequence \
-d "[{\"pageId\":\"$PAGE\",\"sequence\":1536}]" -w "HTTP %{http_code}\n"
实测回显:
{}HTTP 200
{}HTTP 200
复核页面树的编号变化:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC/pages -o pages2.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('pages2.json'));[print(x['title'],'level',x['level'],'seq',x['sequence'],'numbering',x['numbering']) for x in d]"
实测回显:
HTTP 200
第一节 level 1 seq 1024 numbering 1
第二节 level 2 seq 1536 numbering 1.1
第三节 level 1 seq 3072 numbering 2
numbering 由服务端按层级重新计算:level=2 的页面挂在第一节下显示为 1.1,后续顶层自动变成 2。
6.7 删除页面
curl -s -H "Authorization: Bearer $TOKEN" -X DELETE $BASE/api/documents/$DOC/pages/$PAGE -w "HTTP %{http_code}\n"
实测回显:
{}HTTP 200
删除后列表只剩两个页面:
count 2 ['第一节', '第三节']
⚠️ 注意:同路径的
DELETE /api/documents/{documentID}/pages不带页面 ID 时是批量删除接口,误调用会清空整篇文档的页面,脚本里务必带{pageID}。
七、附件 API
附件挂在文档上,并可通过 page 查询参数关联到具体页面。上传用 multipart,下载走公共路由但必须带令牌。
7.1 上传附件
echo "documize api test file" > att_test.txt
curl -s -H "Authorization: Bearer $TOKEN" \
-X POST "$BASE/api/documents/$DOC/attachments?page=$PAGE" \
-F "attachment=@att_test.txt" -w "HTTP %{http_code}\n"
实测回显:
HTTP 200
body: [{}]
上传成功返回空对象;multipart 字段名必须是 attachment,page 查询参数可留空表示只挂文档不挂页面。
7.2 附件列表
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC/attachments -o atts.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('atts.json'));print('count',len(d));[print(x['id'],x['filename'],x['extension'],'pageId',x['pageId']) for x in d]"
实测回显:
HTTP 200
count 1
dbk66as0ptfr4u8acbk0 att_test.txt txt pageId dbk66as0ptfr4u8acbj0
pageId 为空说明上传时没带 page 参数;带 ?page= 时会记录到对应页面。
7.3 公共下载
下载路径是 /api/public/attachment/{orgID}/{attachmentID},令牌放在 token 查询参数里,不是请求头:
export ORG=$(python3 -c "import json;print(json.load(open('sp.json'))['orgId'])")
curl -s "$BASE/api/public/attachment/$ORG/$ATT?token=$TOKEN" -w "HTTP %{http_code}\n"
实测回显:
documize api test file
HTTP 200
不带令牌:
curl -s "$BASE/api/public/attachment/$ORG/$ATT" -w "HTTP %{http_code}\n"
实测回显:
{Error: 'Forbidden'}HTTP 403
⚠️ 注意:下载链接里的
orgID是组织 ID,不是空间 ID;两个都从dbk开头,混淆后会得到 404 或空文件。先用/api/public/meta的orgId核对。
7.4 删除附件
curl -s -H "Authorization: Bearer $TOKEN" \
-X DELETE $BASE/api/documents/$DOC/attachments/$ATT -w "HTTP %{http_code}\n"
实测回显:
{}HTTP 200
删除后列表返回 [],说明附件记录与文件引用已清理。
八、用户 API
用户接口分两层权限:组织管理员可以创建、列出、改密;普通编辑器只能读自己,列表与创建都会被 403。
8.1 新建用户
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/users \
-d '{"firstname":"Api","lastname":"Tester","email":"apitester2@quzuo.local","editor":true}' \
-o user.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('user.json'));print(d['id'],d['email'],'editor',d['editor'],'admin',d['admin'])"
export NUID=$(python3 -c "import json;print(json.load(open('user.json'))['id'])")
实测回显:
HTTP 200
dbk66as0ptfr4u8acbkg apitester2@quzuo.local editor True admin False
⚠️ 注意:创建用户时请求里的密码字段不生效,服务端会随机生成初始密码。要让新用户能登录,必须再用管理员代设密码接口。
8.2 管理员代设密码
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: text/plain' \
-X POST $BASE/api/users/$NUID/password -d 'q1w2e3r4' -w "HTTP %{http_code}\n"
实测回显:
{}HTTP 200
请求体是纯文本新密码,不是 JSON;该接口只允许管理员或本人调用。
8.3 新用户登录验证
curl -s -u ":apitester2@quzuo.local:q1w2e3r4" -X POST $BASE/api/public/authenticate -o ulogin.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('ulogin.json'));print(d['user']['id'],d['user']['email'],'admin',d['user']['admin'])"
export UTOKEN=$(python3 -c "import json;print(json.load(open('ulogin.json'))['token'])")
实测回显:
HTTP 200
dbk66as0ptfr4u8acbkg apitester2@quzuo.local admin False
8.4 权限边界
普通用户访问用户列表:
curl -s -H "Authorization: Bearer $UTOKEN" $BASE/api/users -w "HTTP %{http_code}\n"
实测回显:
{Error: 'Forbidden'}HTTP 403
管理员访问同一接口返回 200 与用户数组。GET /api/users/{userID} 对普通用户只允许查询自己的 ID,查他人会返回 400。
九、标签与分类 API
标签(Label)是跨空间的横向聚合,分类(Category)是空间内的二级归类。两者都在创建时容易写错 JSON 键名。
9.1 新建与列出标签
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/label -d '{"name":"API 测试二","color":"#280A42"}' \
-o label.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('label.json'));print(d['id'],d['name'],d['color'])"
export LABEL=$(python3 -c "import json;print(json.load(open('label.json'))['id'])")
实测回显:
HTTP 200
dbk66as0ptfr4u8acblg API 测试二 #280A42
列出全部标签:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/label -o labels.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('labels.json'));print('count',len(d));[print(x['id'],x['name']) for x in d]"
实测回显:
HTTP 200
count 3
dbk65q40ptfr4u8acbgg API 测试
dbk66as0ptfr4u8acblg API 测试二
dbk58q40ptfvtanpkhj0 运维
9.2 新建与列出分类
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/category -d "{\"spaceId\":\"$SP\",\"category\":\"API 分类\"}" \
-o cat.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('cat.json'));print(d['id'],d['category'],d['isDefault'])"
实测回显:
HTTP 200
dbk66b40ptfr4u8acbm0 API 分类 False
按空间列出:
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/category/space/$SP?filter=all" -w "\nHTTP %{http_code}\n"
实测回显:
[{"id":"dbk66b40ptfr4u8acbm0","created":"2026-11-02T10:17:16.067459Z","revised":"2026-11-02T10:17:16.067459Z","orgId":"dbk3ulk0ptfkf0kvebc0","spaceId":"dbk66as0ptfr4u8acbhg","category":"API 分类","isDefault":false}]
HTTP 200
⚠️ 注意:分类名称的 JSON 键是
category,不是name。写成{"name":"任意名称"}不会报错,但会创建一个空名称分类。?filter=all用于返回全部分类,不带时只返回当前用户可见的分类。
十、搜索与索引重建
Documize 不内置搜索引擎,检索完全依赖数据库全文索引表。写操作会异步把文档/页面写入索引,重建接口用于全量刷新。
10.1 关键词检索
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/search \
-d '{"keywords":"Runbook","doc":true,"content":true,"tag":true}' \
-o search.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('search.json'));print('hits',len(d));[print(x['document'],x['documentSlug'],x['itemType'],x['excerpt']) for x in d]"
实测回显:
HTTP 200
hits 1
API Runbook api-runbook doc Description
请求体的四个布尔值分别控制是否检索文档标题、正文内容、标签;slog=true 可跳过搜索日志。返回里 documentSlug 是标题自动生成的 slug,可用于拼接前端路由。
10.2 中文关键词
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/search \
-d '{"keywords":"部署步骤","doc":true,"content":true,"tag":true}' \
-w "\nHTTP %{http_code}\n"
实测回显:
[]
HTTP 200
中文连续文本返回空数组,原因是 PostgreSQL 默认分词配置不切分中文。该问题属于数据库 FTS 能力,不是接口 bug;需要中文检索时可评估 zhparser 等扩展并调整 text search config。
10.3 索引状态与重建
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/global/search/status -w "\nHTTP %{http_code}\n"
curl -s -H "Authorization: Bearer $TOKEN" -X POST $BASE/api/global/search/reindex -w "\nHTTP %{http_code}\n"
实测回显:
{"entries":8}
HTTP 200
{}HTTP 200
entries 是当前索引表行数;重建接口只允许全局管理员(global=true)调用,普通用户返回 403:
curl -s -H "Authorization: Bearer $UTOKEN" -X POST $BASE/api/global/search/reindex -w "HTTP %{http_code}\n"
实测回显:
{Error: 'Forbidden'}HTTP 403
十一、权限 API
权限分空间级与文档级。空间权限决定能否看到/管理空间与批量操作文档;文档权限是在空间权限之上的额外收紧或放行(编辑、审批)。
11.1 读取空间权限
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/space/$SP/permissions -o sp_perm.json -w "HTTP %{http_code}\n"
python3 -c "import json;d=json.load(open('sp_perm.json'));print('records',len(d));[print(x['whoId'],x['name'],'view',x['spaceView'],'manage',x['spaceManage']) for x in d]"
实测回显:
HTTP 200
records 2
dbk3ulk0ptfkf0kvebcg Zuo San view True manage True
dbk66as0ptfr4u8acbkg Api Tester view True manage False
单条记录包含 12 个动作位:spaceView、spaceManage、spaceOwner、documentAdd、documentEdit、documentDelete、documentMove、documentCopy、documentTemplate、documentApprove、documentLifecycle、documentVersion。
11.2 写入空间权限
请求体是 SpaceRequestModel:Message 用于通知邮件文案,Permissions 是完整记录数组(每次提交都会覆盖旧集合):
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X PUT $BASE/api/space/$SP/permissions \
-d "{\"Message\":\"API permission test\",\"Permissions\":[{\"orgId\":\"$ORG\",\"spaceId\":\"$SP\",\"whoId\":\"dbk3ulk0ptfkf0kvebcg\",\"who\":\"user\",\"spaceView\":true,\"spaceManage\":true,\"spaceOwner\":true,\"documentAdd\":true,\"documentEdit\":true,\"documentDelete\":true,\"documentMove\":true,\"documentCopy\":true,\"documentTemplate\":true,\"documentApprove\":true,\"documentLifecycle\":true,\"documentVersion\":true,\"name\":\"Zuo San\"},{\"orgId\":\"$ORG\",\"spaceId\":\"$SP\",\"whoId\":\"$NUID\",\"who\":\"user\",\"spaceView\":true,\"spaceManage\":false,\"spaceOwner\":false,\"documentAdd\":false,\"documentEdit\":false,\"documentDelete\":false,\"documentMove\":false,\"documentCopy\":false,\"documentTemplate\":false,\"documentApprove\":false,\"documentLifecycle\":false,\"documentVersion\":false,\"name\":\"Api Tester\"}]}" \
-w "HTTP %{http_code}\n"
实测回显:
{}HTTP 200
PUT 只回空对象,必须 GET 复核。上面的复核命令会输出两行记录,证明管理员仍是 owner、普通用户只有查看权。
⚠️ 注意:
whoId必须是真实用户 ID。传空字符串或"0"会被服务端解释为「Everyone」并把权限授予全体成员,实测曾因此生成一条name: Everyone的记录。
11.3 文档权限
读取:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC/permissions -w "\nHTTP %{http_code}\n"
实测回显:
[]
HTTP 200
写入(数组,不是包装对象):
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X PUT $BASE/api/documents/$DOC/permissions \
-d "[{\"orgId\":\"$ORG\",\"documentId\":\"$DOC\",\"whoId\":\"$NUID\",\"who\":\"user\",\"documentRoleEdit\":true,\"documentRoleApprove\":false}]" \
-w "HTTP %{http_code}\n"
复核:
curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/documents/$DOC/permissions -w "\nHTTP %{http_code}\n"
实测回显:
[{"orgId":"dbk3ulk0ptfkf0kvebc0","documentId":"dbk66as0ptfr4u8acbig","whoId":"dbk66as0ptfr4u8acbkg","who":"user","documentRoleEdit":true,"documentRoleApprove":false}]
HTTP 200
文档权限只有两个动作位:编辑与审批。空间权限里已有 documentEdit=true 的人,仍可被文档级权限收紧。
11.4 普通用户的权限边界
普通用户尝试修改空间权限:
curl -s -H "Authorization: Bearer $UTOKEN" -H 'Content-Type: application/json' \
-X PUT $BASE/api/space/$SP/permissions -d '{"Message":"x","Permissions":[]}' -w "HTTP %{http_code}\n"
实测回显:
{Error: 'Forbidden'}HTTP 403
普通用户可以正常读取自己有权限的空间与文档,但所有管理类接口(用户列表、权限写入、索引重建)都会被 403 拦截。
十二、导出 API
导出接口把指定范围内容生成一个自包含 HTML 文件,适合离线归档与对外发布。请求体是 exportSpec:
| 字段 | 说明 | 取值 |
|---|---|---|
spaceId | 空间 ID | 导出空间/分类/文档时的上下文空间 |
filterType | 导出范围 | space / category / document |
data | 目标 ID 数组 | 空间模式填空间 ID;文档模式填文档 ID |
导出整个空间:
curl -s -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-X POST $BASE/api/export \
-d "{\"spaceId\":\"$SP\",\"filterType\":\"space\",\"data\":[\"$SP\"]}" \
-o export.html -w "HTTP %{http_code} bytes=%{size_download} type=%{content_type}\n"
python3 -c "
s=open('export.html',encoding='utf-8').read();print(s[:110]);print('contains API Runbook:', 'API Runbook' in s)"
实测回显:
HTTP 200 bytes=428938 type=text/html; charset=utf-8
<!DOCTYPE html><html<head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title>Do
contains API Runbook: True
💡 说明:导出文件包含内联 CSS 与目录,单文件即可在浏览器打开。空间没有任何可见文档时,正文里会出现
No documents found,仍返回 200。
十三、端点速查表
下表按本文实测顺序整理,是否实测 均指本次环境真实调用:
| 方法 | 路径 | 用途 | 实测 |
|---|---|---|---|
| GET | /api/public/version | 版本 | 200 |
| GET | /api/public/meta | 站点与组织元信息 | 200 |
| POST | /api/public/authenticate | Basic 换 JWT | 200 |
| GET | /api/public/validate | 校验令牌 | 200 |
| GET | /api/space | 可见空间列表 | 200 / 401 |
| POST | /api/space | 创建空间 | 200 / 400 |
| GET | /api/space/{spaceID} | 查询空间 | 200 / 400 |
| PUT | /api/space/{spaceID} | 更新空间 | 200 |
| DELETE | /api/space/{spaceID} | 删除空间 | 200 |
| GET | /api/documents?space={spaceID} | 空间内文档列表 | 200 |
| POST | /api/templates/{templateID}/folder/{spaceID}?type=saved | 创建文档 | 200 |
| GET | /api/documents/{documentID} | 查询文档 | 200 |
| PUT | /api/documents/{documentID} | 更新文档(全量对象) | 200 |
| DELETE | /api/documents/{documentID} | 删除文档 | 200 |
| GET | /api/documents/{documentID}/pages | 页面列表 | 200 |
| POST | /api/documents/{documentID}/pages | 新增页面 | 200 / 400 |
| GET | /api/documents/{documentID}/pages/{pageID} | 单页 | 200 |
| GET | /api/documents/{documentID}/pages/{pageID}/meta | 页面原始内容 | 200 |
| PUT | /api/documents/{documentID}/pages/{pageID} | 更新页面 | 200 |
| DELETE | /api/documents/{documentID}/pages/{pageID} | 删除页面 | 200 |
| POST | /api/documents/{documentID}/pages/level | 调整层级 | 200 |
| POST | /api/documents/{documentID}/pages/sequence | 调整顺序 | 200 |
| GET | /api/documents/{documentID}/pages/{pageID}/revisions | 修订列表 | 200 |
| POST | /api/documents/{documentID}/pages/{pageID}/revisions/{revisionID} | 回滚 | 200 |
| GET | /api/documents/{documentID}/attachments | 附件列表 | 200 |
| POST | /api/documents/{documentID}/attachments?page={pageID} | 上传附件 | 200 |
| DELETE | /api/documents/{documentID}/attachments/{attachmentID} | 删除附件 | 200 |
| GET | /api/public/attachment/{orgID}/{attachmentID}?token= | 下载附件 | 200 / 403 |
| POST | /api/users | 新建用户 | 200 / 403 |
| GET | /api/users | 用户列表 | 200 / 403 |
| POST | /api/users/{userID}/password | 代设密码 | 200 |
| POST | /api/label | 新建标签 | 200 |
| GET | /api/label | 标签列表 | 200 |
| POST | /api/category | 新建分类 | 200 |
| GET | /api/category/space/{spaceID}?filter=all | 分类列表 | 200 |
| POST | /api/search | 关键词检索 | 200 |
| GET | /api/global/search/status | 索引状态 | 200 |
| POST | /api/global/search/reindex | 重建索引 | 200 / 403 |
| GET | /api/space/{spaceID}/permissions | 空间权限读取 | 200 |
| PUT | /api/space/{spaceID}/permissions | 空间权限写入 | 200 / 403 |
| GET | /api/documents/{documentID}/permissions | 文档权限读取 | 200 |
| PUT | /api/documents/{documentID}/permissions | 文档权限写入 | 200 |
| POST | /api/export | 导出 HTML | 200 |
十四、常见错误与排障
14.1 401 与 403 混用
现象: 有的请求返回 401,有的返回 403。
定位: 401 表示「没有有效身份」(未带 token、token 伪造、密码错误、salt 变更导致旧 token 失效);403 表示「身份有效但无权限」(普通用户访问用户列表、非管理员调索引重建、无权限改空间权限)。
解决: 先看响应体:Unauthorized 查认证链路,Forbidden 查账号权限位与空间/文档权限记录。
14.2 POST /api/documents 返回 405
现象: 直接按直觉调用 POST /api/documents 得到 405。
原因: 路由表里 documents 只注册了 GET。
解决: 改用 POST /api/templates/0/folder/{spaceID}?type=saved,请求体为纯文本标题。
14.3 创建文档路由 404
现象: 调 POST /api/templates/0/folder/{spaceID} 不带查询参数,得到 404 page not found。
原因: 该路由要求查询参数 type=saved,不满足时会落到其他模板路由。
解决: URL 必须带 ?type=saved,注意 type 是固定值 saved,不是文档类型。
14.4 新增页面 400
现象: POST /api/documents/{documentID}/pages 返回 {"Error":"Bad Request"}。
原因: page.documentId 或 meta.documentId 与路由 {documentID} 不一致;字段缺失时也会走到同一个 400。
解决: 两处 documentId 都填路由里的文档 ID;orgId 从 /api/public/meta 或空间对象里取。
14.5 更新页面收到空响应(HTTP 000)
现象: PUT /api/documents/{documentID}/pages/{pageID} 没有返回码,curl 打印 HTTP 000,服务端日志出现 http: panic serving 127.0.0.1:45950: invalid memory address or nil pointer dereference。
原因: 请求体缺少 page.id,服务端校验 page.id != pageID 后在错误分支对 nil 错误取 err.Error(),触发 panic。
解决: page.id 必须等于路由里的 {pageID};同时确保 page.documentId/meta.documentId 等于 {documentID}。
14.6 更新文档 200 但内容没变
现象: PUT /api/documents/{id} 返回 200,再 GET 仍是旧值。
原因: 请求体只传了部分字段,服务端按完整文档对象覆盖,未传字段被写成零值后又按原逻辑回写,表现为无变化。
解决: 先 GET 完整对象,在本地修改目标字段后整体 PUT 回去(read-modify-write)。
14.7 分类名称为空
现象: 创建分类成功,但列表里 category 是空字符串。
原因: 请求体用了 name 键,服务端只认 category。
解决: 改为 {"spaceId":"<空间ID>","category":"分类名"}。
14.8 中文检索返回空数组
现象: 英文关键词能命中,中文关键词返回 []。
原因: PostgreSQL 默认全文分词不切分中文。
解决: 评估中文分词扩展并调整 FTS 配置,或在业务侧用英文别名/标签辅助检索。该限制同样记录在安装篇的常见问题中。
14.9 重启后所有令牌失效
现象: 进程重启后所有已登录用户请求都返回 401。
原因: 未固定 DOCUMIZESALT,进程每次启动随机生成盐,旧 JWT 验签失败。
解决: 在 systemd unit 或环境变量里固定 salt,并保证所有实例一致;配置优先级细节见 《Documize 5.14 配置参数详解(TOML/env/flags 三层优先级)》。
14.10 删除顺序导致残留
现象: 删了空间但附件仍占用存储,或删用户前忘了转移文档。
原因: 空间删除不级联清理数据库外部的附件对象;用户删除前应先转移其拥有的空间。
解决: 按「附件 → 页面 → 文档 → 空间 → 用户」的顺序清理,删库前先备份。
十五、社区版维护风险与 API 稳定性
这一节把选型风险落到接口层,避免把「接口还能用」误判成「项目在持续维护」。
| 风险项 | 事实依据(查询日期 2026-11-02) | 对 API 使用的影响 |
|---|---|---|
| 版本停更 | 社区版最新稳定版 v5.14.0 发布于 2025-09-19,已超过 13 个月无新稳定版 | 接口行为以 v5.14.0 为准,不会再有官方修复;遇 bug 需自行改源码 |
| 仓库活跃度下降 | 最近代码推送 2026-05-18,archived=false,open issues 48 | 社区答复与补丁速度有限,排障以本文与源码为主 |
| 功能阉割 | 审批流、版本管理、PDF 导出、审计日志等列在 Community+ 商业版 | 社区版 API 的 approval/lifecycle 字段存在,但完整工作流不可用 |
| 接口未版本化 | 路由固定 /api,无 /v1 前缀,无 OpenAPI 契约 | 升级前必须做回归测试,不能假定向后兼容 |
| 无商业 SLA | 社区版 AGPL v3,官方支持面向 Community+ | 生产使用需自建监控、备份与升级验证流程 |
⚠️ 注意:本文所有接口均为 v5.14.0 实测。社区版停更意味着这些行为会长期保持现状:既是「稳定」也是「不再变好」。若项目要求持续安全补丁与商业支持,应在选型阶段评估 Community+ 或替代产品;完整对比见 《Documize 5.14 介绍与选型指南》第七、八节。
十六、参考链接
- Documize 官方仓库(路由与领域源码):https://github.com/documize/community
- Documize v5.14.0 Releases:https://github.com/documize/community/releases/tag/v5.14.0
- PostgreSQL 16 全文检索文档:https://www.postgresql.org/docs/16/textsearch.html
- JWT RFC 7519:https://www.rfc-editor.org/rfc/rfc7519
- 站内安装篇:《Ubuntu 24.04 裸机安装 Documize 5.14 与 PostgreSQL 16 实战》
- 站内配置篇:《Documize 5.14 配置参数详解(TOML/env/flags 三层优先级)》
- 站内选型篇:《Documize 5.14 介绍与选型指南》