REST API 总览¶
QuantumAtlas server 的所有 HTTP endpoint。auth 模型详见 概念/鉴权。
交互式 API 文档(Swagger UI)¶
server 内嵌了一份由 swaggo 从代码注解自动生成的
OpenAPI spec,挂在 /swagger(如 https://quantum-atlas.ai/swagger/),可在浏览器里
直接浏览/点测每个 endpoint:
GET /swagger/index.html— Swagger UI 页面(公开,无需 auth)。GET /swagger/doc.json— 原始 OpenAPI 2.0 JSON,可喂给 Postman / openapi-generator 等。
写口在 UI 里点 Authorize 填 Bearer <PAT 或 session token> 即可带鉴权调用。
历史镜像说明:这份 spec 也嵌进了文档站:API Explorer 页可在浏览器里 展开浏览全部 endpoint 的参数 / schema(静态镜像,在线点测仍用上面的
/swagger)。
上段保留的是旧版文档镜像的说明。当前 Sphinx 站点不运行旧版镜像 hook,API Explorer
改为提供原生 OpenAPI spec 下载与当前实例 /swagger/ 的普通链接;本页的静态表格
仍可直接阅读。Swagger UI 本身不是只读界面,真实 API 调用须由用户主动选择操作,
并通过服务端鉴权。
spec 怎么来的(维护者须知)¶
PocketBase 的路由是匿名闭包,没有可挂 doc 注释的具名 handler,所以注解集中写在
internal/routes/openapi.go
的一组 no-op stub 函数上(每个 endpoint 一个),general info 写在
cmd/qatlasd/main.go 的 func main() 之上。生成产物落在
internal/apidocs/(被 main.go blank-import,编译进二进制)。
改完注解后重新生成:
go tool swag init -g main.go -d ./cmd/qatlasd,./internal/routes \
-o internal/apidocs --parseInternal --parseDepth 1
git diff -- internal/apidocs
swag CLI 通过 go.mod 的 tool 指令钉版本(go tool swag),生成是确定性的。CI
(.github/workflows/go.yml)执行相同命令,并用 git diff --exit-code 与包含未跟踪文件的状态检查防漂移。
应提交 internal/apidocs/ 的生成源码;不能只修改注解而忘记同步 spec。
警告:注解里的 path / 参数 / 响应是手写声明,不是从真实闭包反射出来的——swaggo 在任何 非 net/http-mux 风格路由上都这样。它和真实行为的一致性靠 code review + 这份手维护的 Markdown 表交叉验证,不是自动保证。
鉴权端点(需要 PAT 或 session token)¶
Papers¶
Method |
Path |
鉴权 |
用途 |
|---|---|---|---|
|
|
|
论文分页列表。过滤参数: |
|
|
|
论文详情(registry 记录 + 资产行)。 |
|
|
|
批量(≤200 条)解析 |
|
|
|
论文资产统计( |
|
|
|
列等待 MinerU 解析的论文 |
|
|
|
上传 PDF,见 Upload API |
|
|
|
上传完整受支持 Middle + Markdown ZIP;所有原名/字节保留,重传新 revision;MD-only 拒收 |
|
|
|
申请 MinerU 处理 lease(响应字段含 |
|
|
|
释放 MinerU lease |
|
|
|
申请 MinerU 处理 lease |
|
|
|
释放 MinerU lease |
|
|
|
PAPER_ACCESS · 所选完整不可变 bundle 的原始 Markdown 字节;format=link 仅返回同源认证成员 locator。无 pin 未就绪时 202 →纯 read/status 轮询→带 source/revision 重取;旧 MD/JSON/images 忽略 |
|
|
|
PAPER_ACCESS · 与 read/status 共享完整包/冻结来源 readiness 纯轮询,不启动下载/解析;顶层 source_id/revision/state/ready(兼容 flags 可附加) |
|
|
|
PAPER_ACCESS · 鉴权固定来源 PDF 字节,source_id 或语义 version=vN 互斥;完整 SHA headers,Range 同样鉴权;无需先枚举 source 或 MinerU token。format=link 仅返回同源认证 locator |
|
|
|
PAPER_ACCESS · PDF 来源就绪纯探测,不解析;若含 pdf_url 仅是同源固定 source 的认证 locator,不暴露对象桶 |
|
|
|
PAPER_ACCESS · 从完整 bundle 核验原名/字节后组装图片 ZIP;无 pin 未就绪可202,纯 read/status 轮询后重取固定 source/revision;无 NAS presign |
| GET | /api/papers/{id}/read | papers:read | PAPER_ACCESS · Middle 派生 Markdown JSON 信封;source_id/revision/page/block/cursor/limit,1-based定位、预算1..100000默认30000;truncated/next_request.cursor 固定全部身份 |
| GET | /api/papers/{id}/read/status | papers:read | PAPER_ACCESS · 纯轮询;ready须完整包+冻结PDF核验,顶层source_id/revision;pending202 + Operation-Location/Retry-After,不创建任务 |
| GET | /api/papers/{id}/parses/{revision}/manifest | papers:read | PAPER_ACCESS · 已发布完整包清单,source PDF与每成员SHA/原path;不以旧Middle代替 readiness |
| GET | /api/papers/{id}/parses/{revision}/files/{original_relative_path} | papers:read | PAPER_ACCESS · 所有生产者成员原名/原字节,含嵌套目录及全部JSON,不重命名/重序列化 |
| GET | /api/papers/{id}/parses/{revision}/json | papers:read | PAPER_ACCESS · 原始Middle字节固定artifact SHA;历史评论仍用,不是read派生JSON,也不是完整包ready证据 |
完整身份、原件、懒迁移、上传与错误规则见论文内容契约。当前新内容不运行自动 nightly/boot/入库转换;只有显式内容访问懒解析,管理员显式 RunNow/batch 另按授权。
papers:write隐式含papers:read。PAPER_ACCESS 及 raw JSON/block 原件/完整包成员/images/figures 均由 YAML
paper_access.enabled控制 (默认 OFF;关闭 404,无存储/解析副作用;qa_、别名、Range、历史 pin 不绕过)。开启等于自愿承担对外重分发 PDF / markdown 字节(或 RAG snippet 形式的派生片段)的合规义务,见 License & Attribution · 论文访问开关。 公共quantum-atlas.ai部署默认 OFF。
{id_or_doi}接受qa_、明确/裸arXiv或DOI别名。规范作品ID不等于版本pin; source_id或version=vN是确切来源选择,revision/cursor固定解析。先解析明确pin, 不按旧“DOI总优先/最高版本”规则替换。默认来源歧义应显式pin;未知论文/来源、 关开关和后端不可用分别报告,不将旧MD/JSON/images当新ready。
Search¶
Method |
Path |
鉴权 |
用途 |
|---|---|---|---|
|
|
|
多 provider 论文搜索。body 为 SearchEntry JSON,engine fan-out 到 |
|
|
|
逐平台原始搜索。body |
|
|
session only |
backend 目录(SPA 渲染成 checkbox 选择器):静态表( |
|
|
|
计量 + LLM 总结的 agentic 搜索(qatlas-search 微服务)。body 额外接受 |
语义向量检索不在 /api/search 的 provider 列表里:它由独立的 qatlas-rag 微服务
提供,经 qatlas-search 的 fan-out 接入(POST /api/search/agentic 路径),
qatlasd 在论文 ready 时通过 rag.remote 配置段向 qatlas-rag 推送索引构建。
/api/search/multi 与 /api/search/backends 同样依赖 search.remote 启用。
Web 的普通搜索和 Agentic 搜索会将完整的裸 DOI、doi:... 或
http(s)://doi.org/... / http(s)://dx.doi.org/... 识别为 doi 字段,
不再同时发送 text。DOI 链接的路径解码一次,查询参数/fragment 不作为 DOI;
包含 DOI 的一句话仍按关键词搜索。远程 DOI 精确检索请选择 OpenAlex 或
Semantic Scholar,其他来源可能不支持;不会替用户改变所选来源。自定义评分仍使用
其显式文本主题与规则流程。DOI 搜索只检索/登记元数据,不自动下载论文。
Personal Search Keys(个人第三方搜索 key)¶
每个用户可存自己的第三方搜索 API key(IEEE / Scopus / Tavily 等 key 后端),
POST /api/search/multi 调用时自动解密注入。存储是 AES-256-GCM 加密的
search_api_keys PocketBase collection(owner-only 规则、仅服务端写入);
加密密钥由 system PAT token 做域分离 SHA-256 派生——轮换 system PAT 会使
已存 key 全部失效(表现为解密失败,用户重新录入即可),无独立配置键。
Method |
Path |
鉴权 |
用途 |
|---|---|---|---|
|
|
session only |
当前用户的 key 清单: |
|
|
session only |
upsert 一个 key(body |
|
|
session only |
删除;不存在时 opaque 404 |
session-only(非 scopeGuard)的理由同
/api/pat:这是浏览器 dashboard 里的 凭据管理,泄露的 PAT 不应能读走或替换 owner 的第三方 key。
Robust Downloader¶
详见 Robust Downloader(策略阶梯 / 配置 / downloaderproxy / probe)。端点本身:
Method |
Path |
鉴权 |
用途 |
|---|---|---|---|
|
|
|
提交一批标识符(DOI / arXiv id / 论文 URL,body |
|
|
|
job 快照: |
SPA 页面 /$lang/downloader:粘贴标识符 → 客户端解析预览徽章 → 提交 →
逐 job 进度 + 策略轨迹渲染(挂在 downloader 插件上)。
Plugins¶
Plugins share one manifest/capability model with two orthogonal axes
(kind × transport). The first-party theorems plugin is
kind=builtin (compiled into qatlasd, in-process); third-party plugins are
kind=external and connect to the RPC listener over transport=socket, or are
spawned by the host over transport=stdio.
Method |
Path |
鉴权 |
用途 |
|---|---|---|---|
|
|
|
列出发现的插件清单与状态( |
|
|
|
运行时启用已配置插件 |
|
|
|
运行时禁用已配置插件 |
配置驱动的远程微服务也会出现在插件清单里:search-remote(qatlas-search)
与 rag-remote(qatlas-rag)在各自配置段启用后注册,qatlasd 启动后探测
它们的 /healthz 并标记 connected / disconnected。
External plugins do not call host capabilities over HTTP. They connect to the
WebSocket JSON-RPC service at QATLAS_RPC_WS_BIND. initialize params contain
id, secret, and abi_version; after the handshake the same connection can
call the host-shared capabilities papers/getMarkdown, papers/getMeta,
papers/getCitedRefs, and events/publish. (The host core carries no
plugin-domain methods — ADR 0003.)
Theorems(builtin 插件,read-through 一个 Lean-content git checkout)¶
theorems builtin 插件把上游 Lean-content 仓库(QATLAS_THEOREMS_DIR)的
artifacts/registry.json + artifacts/audit_records/certified.json + Lean 源
read-through 暴露出来。它不持有 PocketBase collection(ADR 0004:已证 Theorem
是从 git read-through,不落库)。
Method |
Path |
鉴权 |
用途 |
|---|---|---|---|
|
|
|
列已证 Theorem(支持 |
|
|
|
列 theorem family 定义(过滤下拉数据源) |
|
|
|
聚合计数(by_kind / by_family / by_audit_status / sorry_free / certified) |
|
|
|
取单个 Theorem(完整 registry 条目 + audit verdict) |
|
|
|
按需取该 Theorem 的 Lean 源文件 |
|
|
|
theorems git 状态 |
|
|
|
触发服务端 theorems git fast-forward pull,随后同步 reload 内存缓存 |
theorems:write隐式含theorems:read。/api/<id>/sync/{pull,status}是平台为 pull 类插件挂的统一形状(ADR 0002/0003)。
PAT 管理(只接受 session token,PAT auth 被拒)¶
Method |
Path |
鉴权 |
用途 |
|---|---|---|---|
|
|
session only |
创建 PAT,返回明文(一次) |
|
|
session only |
列当前用户的 PAT(无明文) |
|
|
session only |
撤销 |
OAuth Device Flow(qatlas auth login 后端)¶
RFC 8628 device authorization grant。CLI 没有浏览器 / session,所以由用户在浏览器里登录 + 输 user_code 授权,CLI 轮询拿 minted PAT。前两个端点匿名(CLI 调),后三个 session-only(PAT auth 被拒,理由同 /api/pat:泄露的 PAT 不应能自我繁殖)。
Method |
Path |
鉴权 |
用途 |
|---|---|---|---|
|
|
匿名 |
RFC 8628 §3.1,CLI 启动 flow。body: |
|
|
匿名 |
RFC 8628 §3.4 + §3.5。CLI 用 |
|
|
session only |
SPA 的 |
|
|
session only |
body |
|
|
session only |
body |
完整 device-flow 概念背景见 概念 · 鉴权 · OAuth Device Flow;schema 详见
/swagger/index.html。
Admin(管理端:Asset Browser + 获取失败)¶
Admin 端点全部走 adminGuard:session token + admin 白名单(auth.admin_logins
等,PAT 一律拒绝——管理员是人)。Asset Browser 是运维通道,独立于
paper_access.enabled(不分发义务:admin 面板不算对外重分发 lane)。
Admin Asset Browser(/api/admin/assets/*)¶
SPA 页面 /$lang/admin/assets:防抖搜索 → 论文卡片 → 可展开资产表,每行
Preview(PDF viewer / Markdown 与 LaTeX 渲染预览,可切换原文)、Download、Copy Presigned URL + Copy S3 Key。
{kind} 取 pdf 或 markdown(别名 md)。
Method |
Path |
用途 |
|---|---|---|
|
|
资产清单:每个 asset 的 S3 object key / size / sha256 / content-type |
|
|
单资产详情; |
|
|
代理流式下载( |
|
|
代理流式预览( |
|
|
presigned S3 URL JSON——浏览器直连 S3,绕过 qatlasd 代理。TTL 1 分钟–24 小时(默认 1h),超出区间截断 |
|
|
批量资产清单(≤50 篇) |
|
|
流式 ZIP 打包下载(≤20 篇) |
|
|
按 title / DOI / arXiv ID(ILIKE)找有资产的论文 |
presign 仅 S3 后端支持(
presign_supported字段);dev 的 LocalStore 回落代理流。
获取失败与补救¶
Method |
Path |
用途 |
|---|---|---|
|
|
持久化的 PDF 抓取失败记录:阶段、原因、重试计数( |
管理员 SPA 的失败表对每行渲染 DOI 直达链接 + 内联 PDF 上传(人工取回 PDF
对着 DOI 直接 POST /api/papers/{doi}/upload-pdf),即时闭合获取状态并触发
MinerU——这是 Robust Downloader 的失败兜底闭环,见
Robust Downloader · 管理员补救流。
端点详解:选粹¶
GET /api/health¶
返回 PocketBase envelope 形状:
{
"code": 200,
"message": "API is healthy.",
"data": {
"status": "healthy",
"version": "0.2.8",
"uptime_seconds": 12345,
"time": "2026-05-29T03:00:00Z",
"checks": {
"rawstore": {
"status": "ok",
"backend": "s3",
"endpoint": "http://<rustfs-internal-host>:9000",
"bucket": "qatlas-raw",
"latency_ms": 12
},
"postgres": {
"status": "ok",
"backend": "postgres",
"latency_ms": 8
}
}
}
}
data.status是聚合状态:healthy(全部 ok 或 not_configured)/degraded(任一 error)code永远 200(即使 degraded)—— 别让上层 LB / Caddy 把整条链路 trip 成 downmessage在 degraded 时变"Dependency degraded.",方便 log scraper每个 probe 5 秒超时(
probeTimeout),三个并行执行PostgreSQL / S3 不配置时返回
"status": "not_configured",不下拉聚合等级
POST /api/papers/{arxiv_id}/upload-pdf¶
完整流程详见 Upload API。要点:
multipart form 字段:
pdf(必)query 参数:
expected_sha256=<hex>(强烈推荐) /overwrite=true状态码:
201 Created— 写了新对象200 OK— 全部 unchanged 短路,零写入409 Conflict— 已冻结别名不同SHA,即使 overwrite=true;body含已有/新SHA,禁止替换来源400 Bad Request— sha256 mismatch / 损坏的 multipart / PDF header 不对等
并发安全(S3 conditional PUT
If-None-Match),多 client 同字节并发只产生 1 个 201 + 其余 200
历史兼容 LRO 示例(旧缓存状态字段)¶
以下图/示例保留旧接口的历史状态字段,不作为新不可变完整包/readiness或覆盖写规则。 当前PDF按gate鉴权交付,无解析token也可读;正文/图片/figures可202,纯read/status 核验完整包后state=cached/ready=true才固定source/revision重取。新信封、headers与 懒迁移/严格上传请以论文内容契约和Upload API为准。 YAML paper_access.enabled取代旧环境变量;format=link仅同源认证locator,无NAS presign。
仅在 QATLAS_PAPER_ACCESS_ENABLED=true 时注册。两类资源
(markdown 与 pdf)共享同一套异步 + 进度可拉的协议,让 agent
方可以放心并发调用同一篇论文:
第一次 GET 立即返回,不阻塞 fetch / convert 完成;
N 个并发调用被 server-side dedupe 成 1 次 fetch + 1 次 convert,所有调用方 观察同一 Job snapshot;
…/status端点是 side-effect-free 的,poll 多少次都不会再触发后端任务。
状态机¶
cached ────────────────────────────────────────────┐
GET /md ── miss ──┬→ queued ──→ fetching ──→ converting ──→ done ──────────────┤
│ (silent fetch from arxiv.org, then MinerU convert) │
│ │
└→ failed(retryable | fatal | quota_exhausted, │
phase ∈ {error_fetching, error_converting}) │
└→ not_in_arxiv (404) │
注:这是历史状态图;PDF410停用阶段已被当前固定来源鉴权交付取代。
已存PDF无推理依赖,原件未就绪与完整包ready按新契约区分。
触发 GET(202 / 200 / 404)¶
curl -i https://<server>/api/papers/quant-ph/9508027v2/markdown \
-H "Authorization: Bearer $QATLAS_TOKEN"
# 缓存命中
HTTP/1.1 200 OK
Content-Type: text/markdown; charset=utf-8
Content-Length: 76382
…markdown bytes…
# 缓存未命中(首次或仍在跑)
HTTP/1.1 202 Accepted
Operation-Location: /api/papers/quant-ph/9508027v2/markdown/status
Retry-After: 5
{
"arxiv_id": "quant-ph/9508027v2",
"state": "queued",
"phase": "fetching_pdf",
"pdf_ready": false,
"md_ready": false,
"submitted_at": "2026-06-05T03:00:00Z",
"detail": "conversion in progress",
"operation": {
"status_url": "/api/papers/quant-ph/9508027v2/markdown/status",
"next_poll_after_iso": "2026-06-05T03:00:05Z"
}
}
合规:内容访问开关(QATLAS_PAPER_ACCESS_ENABLED)¶
是否对外分发论文内容由这个服务端开关决定(合规落点,ADR 0011),也决定上面这些 PAPER_ACCESS 路由是否注册:
开关 |
markdown / json |
说明 |
|
|---|---|---|---|
关(默认,公共 quantum-atlas.ai) |
arxiv 论文 → |
不给 |
不再分发;canonical 源自己分发。(当前实现:端点整体不注册 → 404;arxiv 直链是 ADR 0011 既定 OFF 态,跟踪 #8) |
开(operator 显式接受衍生作品分发义务) |
发(默认直链,见下) |
发(默认字节流,见下) |
字节流 vs 直链(?format=,ADR 0011)¶
开关允许发之后,用什么形式发是与合规无关的传输选择,按 kind 有默认值,并可用 ?format=link|bytes 逐请求复写:
资产 |
默认 |
复写 |
说明 |
|---|---|---|---|
|
字节流 |
|
MinerU 派生的小文本,默认内联串出 |
|
字节流 |
|
MinerU 产出的图片打包 zip,显式请求才返回 |
后端无法 presign(dev 的 LocalStore)时,link 请求自动回落字节流。直链响应形如:
curl -i "https://<server>/api/papers/quant-ph/9508027v2/markdown?format=link" \
-H "Authorization: ******"
HTTP/1.1 200 OK
Content-Type: application/json
{
"arxiv_id": "quant-ph/9508027v2",
"format": "link",
"markdown_url": "https://raw.quantum-atlas.ai/qatlas-pdf/9508/9508027v2.md?X-Amz-…",
"expires_in": 86400
}
进度查询(status;存储不可用时 503)¶
curl https://<server>/api/papers/quant-ph/9508027v2/markdown/status \
-H "Authorization: Bearer $QATLAS_TOKEN"
各状态对应 body(关键字段加粗):
// 已就绪
{
"arxiv_id": "quant-ph/9508027v2",
"state": "cached",
"phase": "ready",
"pdf_ready": true,
"md_ready": true,
"markdown_url": "/api/papers/quant-ph/9508027v2/markdown"
}
// 正在 fetch PDF
{
"arxiv_id": "quant-ph/9508027v2",
"state": "running",
"phase": "fetching_pdf",
"pdf_ready": false,
"md_ready": false,
"submitted_at": "...",
"started_at": "...",
"fetch": {
"started_at": "...",
"bytes_received": 1234567,
"bytes_total": 4503234,
"attempts": 1
}
}
// PDF 已保存,MinerU 在跑(pdf_ready=true 告诉 agent: PDF 没白下)
{
"arxiv_id": "quant-ph/9508027v2",
"state": "running",
"phase": "converting_md",
"pdf_ready": true,
"md_ready": false,
"fetch": {"started_at":"...", "completed_at":"...", "bytes_received":4503234, "sha256":"...", "attempts":1},
"convert": {"started_at":"...", "mineru_task_id":"task-abc", "stage":"running", "polled_count":7},
"queue": {"running_count":4, "max_concurrent":4, "eta_basis":"observed_avg_of_15_jobs", "avg_duration_seconds":180}
}
// MinerU slot 不够,排队中(agent 看 queue.position / eta_seconds 决定退避)
{
"arxiv_id": "2501.00010v1",
"state": "queued",
"phase": "converting_md",
"pdf_ready": true,
"md_ready": false,
"submitted_at": "...",
"queue": {
"position": 3,
"ahead_of_me": 2,
"running_count": 4,
"max_concurrent": 4,
"eta_seconds": 540,
"avg_duration_seconds": 180,
"eta_basis": "observed_avg_of_18_jobs"
}
}
// MinerU 配额耗尽(PDF 已抓取但不再对外交付;等配额恢复后取 markdown)
{
"arxiv_id": "quant-ph/9508027v2",
"state": "cooldown",
"phase": "error_converting",
"kind": "daily_limit",
"pdf_ready": true,
"md_ready": false,
"detail": "server quota exhausted: all 3 MinerU API keys have reached today's daily limit — service resumes at 2026-06-06T00:01:00+08:00",
"retry_after": 1717603260,
"retry_after_iso": "2026-06-06T00:01:00+08:00"
}
// arxiv 没这篇(fatal,agent 应放弃)
{
"arxiv_id": "quant-ph/9999999v9",
"state": "failed",
"phase": "error_fetching",
"kind": "fatal",
"pdf_ready": false,
"md_ready": false,
"detail": "fetch from arxiv: arxiv: paper not found"
}
Agent 决策三元组¶
任何 status 响应都带 state + pdf_ready + md_ready,从这三个字段即可
判断「该不该等」「等什么」:
|
|
|
agent 行动 |
|---|---|---|---|
|
是 |
是 |
直接 GET 资源拿字节 |
|
否 |
否 |
等 |
|
是 |
否 |
等 MinerU 收尾,之后 GET |
|
是 |
否 |
到 |
|
否 |
否 |
永久失败,放弃 |
|
否 / 是 |
否 |
到 |
DOI 入口¶
{id_or_doi} 路径段头部如果匹配 ^10\.\d{4,9}/(IANA DOI 前缀),server
自动经 OpenAlex 反查 → canonical arxiv id → 走同一套 handler。
# DOI → arxiv → 同一个 LRO 流程
curl -i https://<server>/api/papers/10.1103/PhysRevLett.103.150502/markdown \
-H "Authorization: Bearer $QATLAS_TOKEN"
自动默认值(server 应用的隐式推断)¶
server 接受三类"不完整"的 id 输入,并自动补齐到 canonical 形态:
输入形态 |
例子 |
server 推断 |
默认值出处 |
|---|---|---|---|
DOI |
|
OpenAlex 反查 → arxiv canonical |
OpenAlex API |
无版本 arxiv id |
|
加 |
|
bare old-style(无 category) |
|
加 |
|
每一次应用默认值,server 都会在响应里通过两条平行通道告知 caller:
HTTP headers(永远存在,包括字节流响应):
X-QAtlas-Requested-Id: 9508027
X-QAtlas-Resolved-Id: quant-ph/9508027v2
X-QAtlas-Defaults-Applied: version=v2 (no version specified; latest published version used); category=quant-ph (no category prefix; server default per docs/reference/arxiv-ids.md §3.1)
JSON body 字段(仅 JSON 响应;与 header 同步):
{
"arxiv_id": "quant-ph/9508027v2",
"requested_id": "9508027",
"resolved_id": "quant-ph/9508027v2",
"defaults_applied": [
"version=v2 (no version specified; latest published version used)",
"category=quant-ph (no category prefix; server default per docs/reference/arxiv-ids.md §3.1)"
],
"state": "queued",
...
}
输入本身就是 canonical(带 vN + 带 category)时,三个字段全部省略,body 保持精简。
Agent / client 集成建议:
把
X-QAtlas-Defaults-Applied渲染成一行Note: …输出,避免用户因为"我请求的是 0811.3171 但你给我 v3" 产生疑惑;requested_id跟resolved_id不一致时可作为缓存键的二次校验;不要硬解析 defaults_applied 的字符串内容(它是 human-readable,未来会有微调);要程序化判断"应用了什么默认"请用
resolved_id != requested_id加上 path 前缀启发式。
DOI 路径特有错误:
状态 |
含义 |
|---|---|
|
DOI 格式不合规(长度 > 256 / 含控制字符 / 前缀不匹配) |
|
OpenAlex 找不到该 DOI,或该 work 没有 arxiv 表现(OA |
|
DOI 解出 arxiv id,但 arxiv abs 页 404(论文被撤稿 / 从未索引) |
|
OpenAlex 上游错误 / arxiv version 解析上游错误(连接 / 5xx 重试用尽) |
|
|
|
arxiv version 解析 rate-limited — 稍后重试 |
POST /api/pat(session only)¶
{
"name": "ci-upload",
"description": "...",
"scopes": ["papers:write"],
"expires_in_days": 365
}
约束:
name必填,≤80 字符description≤200 字符expires_in_days必填,1–365scopes必须是/api/pat/scopes返回词表里的;空集合也接受(这个 PAT 啥都干不了)
响应(plaintext 只出现这一次):
{
"id": "abc123",
"name": "ci-upload",
"prefix": "qat_AB",
"plaintext": "qat_ABXXXXX...XXXXX",
"description": "",
"scopes": ["papers:write"],
"expires_at": "2027-05-29 03:00:00.000Z",
"created": "2026-05-29 03:00:00.000Z"
}
错误响应规范¶
绝大多数错误都是 {"detail": "<message>"},例外是:
端点 |
特殊形状 |
|---|---|
|
|
|
|
|
|
|
|
速率限制¶
PocketBase 自带 collection 级 throttle。/api/pat 还挂了自定义 rate-limit 规则(默认在启动时 pat.EnsureDefaults 装),防止暴力 mint。常见限制:
/api/patPOST: 10/minute per user/api/health: 无限其他写口: 60/minute per user
具体规则在 PocketBase Settings → _pb_users_auth_ 等 collection 的 throttle 字段。
PocketBase 原生 collection API¶
除了上面 /api/* 自定义 endpoint,PocketBase 自己暴露 /api/collections/<name>/records/... 的 CRUD API。详见 PocketBase 文档。业务上几乎不用——所有暴露给用户的能力都通过自定义 /api/... endpoint 走。
例外:SPA 直接用 PocketBase JS SDK 做 OAuth 登录、读 users 自身记录等。
资产读取与存储故障¶
注册表的 has_md / has_pdf 及资产路径表示已登记的内容,不是实时存储健康探针。
Markdown 资产读取,以及单篇 PDF/Markdown status 的存储探测最多等待
10 秒;更短的请求 deadline 优先。存储 Stat、Get 或首字节读取失败时返回
HTTP 503、Retry-After: 5 和以下结构,详细后端错误留在服务端日志:
{"code":"asset_store_unavailable","detail":"asset storage temporarily unavailable; retry shortly","kind":"retryable","retryable":true,"state":"unavailable"}
失败不计为缓存 miss,不启动替代下载或 MinerU 转换,不清空登记资产。批量 status
保持逐项结果:受影响项的 state=unavailable 并携带 error,就绪布尔值不能单独
解释为资产缺失。流已开始后发生的失败只能中断传输,客户端应丢弃不完整内容并重试。
旧 paper_assets 不会随升级自动回填为不可变 source / parse revision;原始资产
路径保留,列出 originals 只读,可能得到空列表。需要固定版本时按贡献流程登记
实际 PDF 和解析产物,并核对 source/revision ID 与 SHA-256。