Papra 26.7.1 REST API 使用详解

Papra 26.7.1 REST API 使用详解:认证方式与 API Key 权限模型、组织/文档/标签/分享链接全域端点 curl 实测回显、错误码速查与分页过滤约定

PapraPapraREST API2026/10/31

Papra 26.7.1 REST API 使用详解

目标:讲清 Papra 26.7.1 的 REST API——认证方式、公开端点清单、核心域(组织/文档/标签/分享链接/API Key)的逐接口调用方法与实测响应回显、错误码速查与通用分页过滤约定。全部请求与回显均在 2026-10-31 于真实实例逐条实测采集,长响应标注节选。

一、环境信息

项说明(实测环境)
操作系统Ubuntu 22.04
Papra26.7.1(PAPRA_VERSION=26.7.1,构建日期 2026-10-22)
实例基址http://192.168.1.200:8134
演示账号admin@papra.local / q1w2e3r4
实验组织org_tqq4cqy4g6ui3iwvitpgucgh(首个注册账号自动创建的默认组织)
客户端工具curl 7.81+ / python3 标准库
测试文档设备验收报告(txt)、会议纪要(txt)、中文扫描件(png)、采购合同(pdf)等 5 份

💡 说明:回执中的令牌、ID 均来自本次实测实验实例(收工后已下线),仅作格式演示,不含可用凭据。

二、前置条件

2.1 端点与客户端工具

  • API 前缀:/api,全部为 REST over HTTP/JSON;与服务同端口同源(官方云端为 https://api.papra.app/api,自托管即实例自身地址)。
  • 客户端任选:curl(本文全部示例)、任意 HTTP 客户端、官方 SDK(TypeScript/JavaScript,见官方仓库 packages/)。
  • 无需额外安装依赖,一个可用的 Papra 实例加 curl 即可复现本文全部操作。

2.2 创建 API Key(实测)

API Key 在界面"设置 → API 密钥"中创建,也可用登录会话调 POST /api/api-keys 创建(见 5.5 节)。创建时勾选权限(可全选),返回值中 token 字段仅此一次展示。

本文实测创建的全量权限 Key(名称 doc-test-key)与只读 Key(名称 ro-key,仅 tags:read,见 4.3)均已实际生成并用于后续验证。示例统一以环境变量引用:

export PAPRA_API_KEY='<你的 API Key token>'
export BASE=http://192.168.1.200:8134
export ORG=org_tqq4cqy4g6ui3iwvitpgucgh

三、API 总览

3.1 协议与版本

  • 纯 REST:JSON 请求体(必须显式带 Content-Type: application/json,见 5.2 三个反例实测)+ 文件上传用 multipart/form-data。
  • URL 内实体 ID 带类型前缀与固定格式,不符合直接 400(不进入业务逻辑):
实体ID 格式(正则)示例
组织org_ + 24 位小写字母数字org_tqq4cqy4g6ui3iwvitpgucgh
文档doc_ + 24 位小写字母数字doc_h0a5nz9a6kj6w4zz18cmcl1j
标签tag_ + 24 位tag_b0m8vaz0ai0tch6jjaa8dvyn
API Keyak_ + 24 位ak_bvu4nz6jhykpnmujnll6a95w
分享链接(管理标识)dsl_ + 24 位dsl_sl6agu1c0pkszq7ky5hlqqf4
分享链接(公开令牌)64 位字母数字(/^[a-z0-9]{64}$/i)m63JZGZTMudE…(64 位)

3.2 认证方式

两种认证并存,覆盖不同接口族:

方式适用接口族用法
API Key(Bearer)业务 API(组织/文档/标签/自定义属性等)请求头 Authorization: Bearer <token>
登录会话better-auth 认证端点(/api/auth/*)、API Key 管理(/api/api-keys)、恢复回收站文档等仅界面可用的操作注册/登录后以 Cookie 会话维持

⚠️ 注意:权限不足的响应是 401 而非 403(实测,见 4.3);两类端点的错误体形态不同——业务 API 为 {"error":{"message","code"}},better-auth 端点为 {"message","code"}(无外层 error)。

3.3 官方公开端点清单(26.7.1,官方 API Endpoints 页 2026-10-31 查询)

域端点
API KeyGET /api/api-keys/current
组织GET/POST /api/organizations、GET/PUT/DELETE /api/organizations/:organizationId
文档POST /api/organizations/:organizationId/documents(上传)、GET …/documents(列表)、GET …/documents/deleted(回收站)、GET …/documents/:documentId、DELETE …/documents/:documentId、GET …/documents/:documentId/file、GET …/documents/statistics、PATCH …/documents/:documentId、POST …/documents/:documentId/reprocess、GET …/documents/:documentId/activity
标签POST/GET /api/organizations/:organizationId/tags、PATCH/DELETE …/tags/:tagId、POST …/documents/:documentId/tags(打标)、DELETE …/documents/:documentId/tags/:tagId(摘标)
打标规则POST …/tagging-rules/:tagRuleId/apply
自定义属性…/custom-property-definitions 增删改查全套、…/documents/:documentId/custom-properties 读写清

💡 说明:实测发现 26.7.1 还有官方端点页未登记的接口族可用——分享链接(POST …/documents/:documentId/share-links、GET /api/share-links/:token/document、POST /api/share-links/:token/verify、DELETE …/share-links/:shareLinkId,见 5.4)以及批量操作(documents/batch/tags、documents/batch/trash)等。官方页未列的接口属未承诺范围,升级时可能变动,生产调用建议以公开清单为界。

四、认证与首次调用

4.1 无令牌调用(实测 401)

curl -s -w '\nHTTP=%{http_code}\n' "${BASE}/api/organizations"

实测回显(HTTP 401):

{"error":{"message":"Unauthorized","code":"auth.unauthorized"}}
HTTP=401

令牌前缀错误(如去掉 ppapi_ 前缀或拼接多余空格)同样返回上述 401,服务端不区分"缺失"与"无效"。

4.2 首次调用:校验当前 Key(实测)

curl -s -w '\nHTTP=%{http_code}\n' -H "Authorization: Bearer ${PAPRA_API_KEY}" \
  "${BASE}/api/api-keys/current"

实测回显(HTTP 200):

{"apiKey":{"id":"ak_bvu4nz6jhykpnmujnll6a95w","name":"doc-test-key","permissions":["organizations:read","organizations:create","documents:create","documents:read","documents:update","documents:delete","tags:create","tags:read","tags:update","tags:delete","custom-properties:read","custom-properties:create"]}}

4.3 API Key 权限模型(实测)

权限共 12 项(organizations/documents/tags/custom-properties 各域的 read/create/update/delete)。最小权限实测:用仅含 tags:read 的只读 Key 调删除文档接口:

curl -s -w '\nHTTP=%{http_code}\n' -X DELETE -H "Authorization: Bearer ${PAPRA_RO_KEY}" \
  "${BASE}/api/organizations/${ORG}/documents/doc_h0a5nz9a6kj6w4zz18cmcl1j"

实测回显(HTTP 401):

{"error":{"message":"Unauthorized","code":"auth.unauthorized"}}
HTTP=401

⚠️ 注意:权限不足与未认证同样返回 401 auth.unauthorized,不是 403。排障时看到 401 先核对 Key 权限清单(GET /api/api-keys/current 的 permissions 数组),再怀疑令牌本身。同一只读 Key 调 GET …/tags 正常返回 200,证明 401 确由权限缺失触发。

五、各 API 域详解

5.1 组织域(organizations)

列出组织(需 organizations:read):

curl -s -H "Authorization: Bearer ${PAPRA_API_KEY}" "${BASE}/api/organizations"

实测回显(HTTP 200):

{"organizations":[{"id":"org_tqq4cqy4g6ui3iwvitpgucgh","name":"默认组织","customerId":null,"deletedBy":null,"createdAt":"2026-10-31T01:58:56.988Z","updatedAt":"2026-10-31T01:58:56.988Z","deletedAt":null,"scheduledPurgeAt":null}]}

其余组织接口:POST /api/organizations({"name":"…"},3–50 字符)、GET /api/organizations/:organizationId、PUT /api/organizations/:organizationId(改名)、DELETE /api/organizations/:organizationId(软删,204;到期由定时任务清除,保留期见配置篇 5.11)。

5.2 文档域(documents)

上传文档(POST …/documents,multipart/form-data,文件放 file 字段;需 documents:create):

curl -s -w '\nHTTP=%{http_code}\n' -H "Authorization: Bearer ${PAPRA_API_KEY}" \
  -F 'file=@/opt/papra-samples/设备验收报告-02.txt' \
  "${BASE}/api/organizations/${ORG}/documents"

实测回显(HTTP 200,注意 content 为空——提取是异步的):

{"document":{"id":"doc_h0a5nz9a6kj6w4zz18cmcl1j","createdAt":"2026-10-31T02:48:24.606Z","updatedAt":"2026-10-31T02:48:24.608Z","organizationId":"org_tqq4cqy4g6ui3iwvitpgucgh","createdBy":"usr_v63ltj99aqbnbjq8jitdemkl","originalName":"设备验收报告-02.txt","originalSize":175,"originalSha256Hash":"b2f7b670c5c731819542fed25f84919a3fe48a4aab4770374548e622323eeb90","name":"设备验收报告-02.txt","mimeType":"text/plain","content":"","documentDate":null,"notes":null,"deletedAt":null,"deletedBy":null,"isDeleted":false}}

同内容去重:把同一文件改名再传,实测 HTTP 409:

{"error":{"message":"Document already exists.","code":"document.already_exists"}}

💡 说明:去重按 originalSha256Hash 全组织比对,与文件名无关。这实现了官方 Architecture 页的 Document Deduplication 承诺(同内容只存一份原文件)。

获取文档详情(上传后稍等片刻回读,content 已被异步提取填充,HTTP 200):

{"document":{"id":"doc_h0a5nz9a6kj6w4zz18cmcl1j",…,"updatedAt":"2026-10-31T02:48:24.619Z","originalName":"设备验收报告-02.txt","originalSize":175,"originalSha256Hash":"b2f7b6…eeb90","name":"设备验收报告-02.txt","mimeType":"text/plain","content":"设备验收报告(YS-2026-042)\n\n设备名称:高速扫描仪 SC-50\n序列号:SN20261031A\n验收结论:通过,同意入库归档。\n抽检样本:30/30 合格。\n","documentDate":null,"notes":null,"deletedAt":null,"deletedBy":null,"isDeleted":false,"tags":[],"customProperties":[]}}

下载原文件(GET …/documents/:documentId/file,返回 application/octet-stream,内容与原件逐字节一致):

curl -s -H "Authorization: Bearer ${PAPRA_API_KEY}" \
  "${BASE}/api/organizations/${ORG}/documents/doc_h0a5nz9a6kj6w4zz18cmcl1j/file"

实测回显(HTTP 200,响应体即原文):

设备验收报告(YS-2026-042)

设备名称:高速扫描仪 SC-50
序列号:SN20261031A
验收结论:通过,同意入库归档。
抽检样本:30/30 合格。

更新文档(PATCH …/documents/:documentId,需 documents:update;必须显式带 JSON 头):

curl -s -X PATCH -H "Authorization: Bearer ${PAPRA_API_KEY}" \
  -H 'Content-Type: application/json' \
  -d '{"name":"设备验收报告-YS-2026-042.txt","content":"设备验收报告 YS-2026-042:高速扫描仪 SC-50,序列号 SN20261031A,验收通过。"}' \
  "${BASE}/api/organizations/${ORG}/documents/doc_h0a5nz9a6kj6w4zz18cmcl1j"

实测回显(HTTP 200,updatedAt 随之刷新):

{"document":{"id":"doc_h0a5nz9a6kj6w4zz18cmcl1j",…,"updatedAt":"2026-10-31T02:50:48.573Z","originalName":"设备验收报告-02.txt","name":"设备验收报告-YS-2026-042.txt","mimeType":"text/plain","content":"设备验收报告 YS-2026-042:高速扫描仪 SC-50,序列号 SN20261031A,验收通过。",…}}

缺 JSON 头的反例:同样的 PATCH 体不带 Content-Type: application/json(或写成 text/plain),实测 HTTP 400:

{"error":{"message":"Invalid request body","code":"server.invalid_request.body","details":[{"path":null,"message":"At least one of 'name', 'content', 'documentDate' or 'notes' must be provided"}]}}

错误细节极具迷惑性(提示"至少要提供一个字段",实际字段已带)——根因是请求体未被按 JSON 解析。所有 JSON 端点必须显式声明 Content-Type: application/json。

删除文档(进回收站)(DELETE …/documents/:documentId,需 documents:delete):

curl -s -o /dev/null -w '%{http_code}\n' -X DELETE -H "Authorization: Bearer ${PAPRA_API_KEY}" \
  "${BASE}/api/organizations/${ORG}/documents/doc_h0a5nz9a6kj6w4zz18cmcl1j"

实测回显(HTTP 204,空响应体):

204

回收站列表(GET …/documents/deleted,isDeleted:true 并带删除人与删除时间):

{"documents":[{"id":"doc_h0a5nz9a6kj6w4zz18cmcl1j",…,"deletedAt":"2026-10-31T02:48:24.663Z","deletedBy":"usr_v63ltj99aqbnbjq8jitdemkl","isDeleted":true}],"documentsCount":1}

⚠️ 注意:回收站恢复(界面的"还原")在 26.7.1 不对 API Key 开放——全量权限 Key 调恢复操作实测仍返回 401 auth.unauthorized(恢复仅登录会话可用);超过保留期(默认 30 天)由定时任务硬删除,届时不可恢复。

组织文档统计(GET …/documents/statistics):

{"organizationStats":{"documentsCount":5,"documentsSize":236910,"deletedDocumentsCount":0,"deletedDocumentsSize":0,"totalDocumentsCount":5,"totalDocumentsSize":236910}}

ID 校验的两级错误(排障常用):

  • 格式不合规(doc_nonexistent123,不满足 doc_[a-z0-9]{24})→ HTTP 400,根本不查库:
{"error":{"message":"Invalid URL parameters","code":"server.invalid_request.params","details":[{"path":"documentId","message":"Invalid format: Expected /^doc_[a-z0-9]{24}$/ but received \"doc_nonexistent123\""}]}}
  • 格式合规但不存在 → HTTP 404:
{"error":{"message":"Document not found.","code":"document.not_found"}}

重新处理与活动记录:POST …/documents/:documentId/reprocess 对已入库文档按当前实例配置重新提取内容(默认限流 100 次/小时/组织,关闭时 403 document.reprocessing_disabled、超限 429 server.too_many_requests 并附 Retry-After;相关参数见配置篇 5.3);GET …/documents/:documentId/activity 返回文档操作时间线。

5.3 标签域(tags)

创建标签(POST …/tags,JSON 体):

curl -s -H "Authorization: Bearer ${PAPRA_API_KEY}" \
  -H 'Content-Type: application/json' \
  -d '{"name":"合同","color":"#2563EB","description":"采购与合同类文件"}' \
  "${BASE}/api/organizations/${ORG}/tags"

实测回显(HTTP 200):

{"tag":{"id":"tag_b0m8vaz0ai0tch6jjaa8dvyn","createdAt":"2026-10-31T02:01:41.147Z","updatedAt":"2026-10-31T02:01:41.147Z","organizationId":"org_tqq4cqy4g6ui3iwvitpgucgh","name":"合同","normalizedName":"合同","color":"#2563EB","description":"采购与合同类文件"}}

⚠️ 注意:Content-Type 三态实测——官方 API 文档写"form-data",但 26.7.1 实际只接受 application/json:JSON 体返回 200,multipart/form-data 与 application/x-www-form-urlencoded 均实测返回 HTTP 415:

{"error":{"message":"Unsupported Content-Type","code":"api.unsupported-content-type"}}

官方文档此处滞后,以实测为准。

normalizedName 字段做归一化(英文转小写):实测创建 测试A 后回读详情 normalizedName":"测试a"(同归一化名视为重复)。

给文档打标签(POST …/documents/:documentId/tags,JSON 体 {"tagId":"…"}):

curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  -H "Authorization: Bearer ${PAPRA_API_KEY}" -H 'Content-Type: application/json' \
  -d '{"tagId":"tag_edh08smsg3u783yiz58tcxr7"}' \
  "${BASE}/api/organizations/${ORG}/documents/doc_h0a5nz9a6kj6w4zz18cmcl1j/tags"

实测回显(HTTP 204);回读文档详情确认标签已挂载(tags 数组,长响应节选):

{"document":{…,"isDeleted":true,"tags":[{"id":"tag_edh08smsg3u783yiz58tcxr7","createdAt":"2026-10-31T02:49:54.799Z","updatedAt":"2026-10-31T02:49:54.799Z","organizationId":"org_tqq4cqy4g6ui3iwvitpgucgh","name":"测试A","normalizedName":"测试a","color":"#DC2626","description":null}],"customProperties":[]}}

标签列表(GET …/tags,含 documentsCount 使用计数):

{"tags":[{"id":"tag_b0m8vaz0ai0tch6jjaa8dvyn",…,"name":"合同","normalizedName":"合同","color":"#2563EB","description":"采购与合同类文件","documentsCount":1}]}

标签更新/删除(PATCH/DELETE …/tags/:tagId)与摘标(DELETE …/documents/:documentId/tags/:tagId)形态同上:成功 200/204。

5.4 分享链接域(share-links,官方端点页未登记)

创建分享链接(POST …/documents/:documentId/share-links):

带密码(JSON 体 {"password":"share1234","expiresAt":null})实测回显(HTTP 201):

{"shareLink":{"id":"dsl_sl6agu1c0pkszq7ky5hlqqf4","createdAt":"2026-10-31T02:50:48.621Z","updatedAt":"2026-10-31T02:50:48.621Z","organizationId":"org_tqq4cqy4g6ui3iwvitpgucgh","documentId":"doc_h0a5nz9a6kj6w4zz18cmcl1j","createdBy":"usr_v63ltj99aqbnbjq8jitdemkl","token":"m63JZGZTMudEREqSB8PWSacgw8r7eZT2ZkDv0fdso1wHn7p28rDeStnymlP7wa6l","expiresAt":null,"isEnabled":true,"lastAccessedAt":null,"isPasswordProtected":true,"url":"http://192.168.1.200:8134/share/m63JZGZTMudEREqSB8PWSacgw8r7eZT2ZkDv0fdso1wHn7p28rDeStnymlP7wa6l"}}

空 JSON 体 {} 实测创建无密码链接(HTTP 201,isPasswordProtected:false,其余结构同上)。

💡 说明:一条链接有两个标识,用途严格区分——id(dsl_ 前缀)用于管理操作(删除、列表);token(64 位)用于公开访问(拼进 /share/<token> 页面地址与公开 API)。二者不可混用,混用即 400(见下)。

公开访问无密码链接(无需任何认证):

curl -s "${BASE}/api/share-links/${TOKEN64}/document"

实测回显(HTTP 200,元信息);GET /api/share-links/${TOKEN64}/document/file 同理返回 application/octet-stream 原文件:

{"document":{"name":"会议纪要.txt","size":481,"mimeType":"text/plain"}}

密码保护链接的验证流程(实测三步):

  1. 取受保护文档不带令牌 → HTTP 401:
{"error":{"message":"A valid access token is required to access this share link.","code":"share_link.password_required"}}
  1. POST /api/share-links/:token/verify,JSON 体 {"password":"share1234"} → HTTP 200,签发短时 JWT(有效期默认 15 分钟,见配置篇 5.10):
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaGFyZUxpbmtJZCI6ImRzbF9zbDZhZ3UxYzBwa3N6cTdreTVobHFxZjQiLCJleHAiOjE3OTM0MTU5NDgsImlhdCI6MTc5MzQxNTA0OH0.4ezF07aHCopisN47IyaqtvKoPhFAwshx1wGVq_MdmXs"}
  1. 用标准 Bearer 头携带该 accessToken 再次公开访问 → HTTP 200 返回文档元信息与原文件:
curl -s -H "Authorization: Bearer ${SHARE_ACCESS_TOKEN}" \
  "${BASE}/api/share-links/${TOKEN64}/document"

密码验证的错误分支(均实测):

请求状态码回显
密码错误401{"error":{"message":"Invalid password.","code":"share_link.invalid_password"}}
verify 缺请求体400{"message":"Expected \"password\" but received undefined",…}
verify 空密码400{"message":"Invalid length: Expected >=1 but received 0",…}
用 dsl_ 管理标识当 token 调 verify400{"error":{"message":"Invalid URL parameters","code":"server.invalid_request.params","details":[{"path":"shareLinkToken","message":"Invalid format: Expected /^[a-z0-9]{64}$/i but received \"dsl_gs46c0yj9sc900k8uqzmv3ra\""}]}}

删除分享链接(管理操作,用 dsl_ 标识;HTTP 204):

curl -s -o /dev/null -w '%{http_code}\n' -X DELETE \
  -H "Authorization: Bearer ${PAPRA_API_KEY}" \
  "${BASE}/api/organizations/${ORG}/share-links/dsl_sl6agu1c0pkszq7ky5hlqqf4"

实测回显:

204

单文档链接数量上限、令牌时效与三条访问限流的参数化控制见配置篇 5.10。

5.5 API Key 管理域(仅登录会话可用)

API Key 的创建/列表/删除不开放给 API Key 自身,须用登录会话(POST /api/auth/sign-in/email 取 Cookie,回执见部署篇 4.5)。实测用会话创建只读 Key:

curl -s -b /tmp/papra-cookies.txt -H 'Content-Type: application/json' \
  -d '{"name":"ro-key","permissions":["tags:read"]}' \
  "${BASE}/api/api-keys"

实测回显(HTTP 200,token 仅此一次返回):

{"apiKey":{"id":"ak_wyjst8nhj1bwjh18y7igq93n","createdAt":"2026-10-31T02:50:48.765Z","updatedAt":"2026-10-31T02:50:48.765Z","name":"ro-key","keyHash":"wVc574qQCqaN0I5xKJY7Mt83O_tc7NVBY6Tp81ErB00","prefix":"ppapi_lE0oF","userId":"usr_v63ltj99aqbnbjq8jitdemkl","lastUsedAt":null,"expiresAt":null,"permissions":["tags:read"],"allOrganizations":true},"token":"ppapi_lE0oFJxiGmq6EB3NS3CYoRSIMlOlJqJHDk172r6qPFSVnGOQLMdx5FihmmiTHgUT"}

GET /api/api-keys 列出全部 Key(含 keyHash、prefix、lastUsedAt),DELETE /api/api-keys/:id 吊销(HTTP 204,实测吊销后该 Key 立即失效)。

5.6 自定义属性与打标规则(官方公开域)

  • 自定义属性:POST/GET …/custom-property-definitions 定义(含类型),PATCH/DELETE …/custom-property-definitions/:id 维护,GET …/documents/:documentId/custom-properties 读取,POST/DELETE …/documents/:documentId/custom-properties/:definitionId 写入/清除值。每组织定义数上限默认 100(配置篇 5.11)。
  • 打标规则:POST …/tagging-rules/:tagRuleId/apply 把既有规则补打到存量文档(规则本身在界面维护)。自动打标的 AI 参数见配置篇 5.12。

六、分页/过滤/排序通用约定

GET …/documents 列表接口的通用查询参数(实测验证):

参数默认取值实测要点
pageIndex0从 0 起的页序号与 pageSize 配合翻页
pageSize100每页条数响应只含当前页 + 总数
searchQuery—关键字或高级过滤表达式同时命中 name 与提取后的 content(含 PDF OCR 文本):实测搜"扫描仪"同时命中会议纪要(正文提及)与采购合同(PDF 扫描内容提及)两份文档
sortFieldcreatedAtcreatedAt / updatedAt / name / documentDate实测 sortField=name&sortOrder=asc 按名称正序返回
sortOrderdescasc / desc—

响应统一为 {"documents":[…],"documentsCount":总数} 结构;回收站列表(GET …/documents/deleted)同构。

💡 说明:高级过滤表达式(field:value 组合等)语法见官方 Guides → Advanced Search;本文实测的关键字检索已覆盖 content 全文与 OCR 内容两条链路。

七、常见问题

Q1:请求返回 401,令牌明明没写错? 按顺序核对三点:① 是否带了 Authorization: Bearer 前缀(实测缺前缀即 401 auth.unauthorized);② 该 Key 的权限是否覆盖目标接口——权限不足也是 401,不是 403(实测仅 tags:read 的 Key 调删除文档返回 401);③ 恢复回收站文档等界面专属操作不接受 API Key,须用登录会话。

Q2:JSON 接口反复报 400 "At least one of … must be provided" 或 415? 请求体没有被按 JSON 解析。所有 JSON 端点必须显式带 Content-Type: application/json(实测 PATCH 缺头报 400 server.invalid_request.body、建标签用 form-data 报 415 api.unsupported-content-type)。官方 API 文档中建标签写 form-data 属滞后信息,以实测为准。

Q3:上传返回 409 Document already exists? 同组织内已存在相同 SHA256 内容的文档(改名无效)。确认是否真要重复归档;同一份内容本就只存一份(官方去重架构),也可给旧文档打标/补备注后复用。

Q4:URL 里的 ID 报 400 Invalid URL parameters? ID 格式不满足对应正则(如文档必须 doc_ + 24 位小写字母数字)。先从列表接口取真实 ID,不要手拼。

Q5:上传成功但 content 一直是空? 内容提取是异步的:上传回执里 content 为空串,稍后回读详情即有值(实测 175 字节 txt 上传后 11 秒内完成,见 5.2 回显的 updatedAt 变化)。扫描件无内容时检查 OCR 语言配置(配置篇 5.3/5.5)与 DOCUMENTS_CONTENT_EXTRACTION_ENABLED 开关。

Q6:分享链接访问 401? 区分两种 401:share_link.password_required 是受保护链接未带 accessToken——走 verify 换 JWT 后以 Authorization: Bearer <accessToken> 访问(注意不是自定义头);share_link.invalid_password 是密码错误。另外 verify 只认 64 位 token,dsl_ 管理标识会直接 400。

Q7:better-auth 端点的错误为什么没有 error 包裹层? 两类端点错误形态不同:业务 API 为 {"error":{"message","code"}};/api/auth/* 等认证端点为 {"message","code"}(如 EMAIL_PASSWORD_SIGN_UP_DISABLED,实测回显见配置篇 3.3)。写统一错误处理时两类都要兼容。

八、参考链接

本专栏姊妹篇:《Papra 26.7.1 介绍与选型指南》、《Papra 26.7.1 Docker Compose 部署指南》、《Papra 26.7.1 配置参数详解》。

Papra 26.7.1 REST API 使用详解 - Quzuo