同一操作,四种客户端:浏览器、curl、MCP、pdfx
同一份合并有四种调用方式:浏览器表单、curl 打 REST、MCP 端点与本地或 --cloud 的 pdfx CLI,共享一个 op 与一套契约。

四种客户端看起来像四套产品,其实共享一份目录。拿 合并 这个具体作业来说:按上传顺序把 PDF 合起来,不把页面栅格化成图。目录里其他工具(压缩、OCR、转换等)都是同一个模式,一个 op id、四种调用方式。
浏览器
打开 /zh/merge,按想要的顺序加文件,点处理,下载。目录工具不需要账号。页面上的“Call this from code”区块,用表单发送的同一批字段拼出 curl,所以 UI 与 HTTP 契约始终对齐。
那块内容不是营销文案,它由门户表单自己用的工具定义生成,参数一改名,两处会同时变。表单接受可选 sortType=byFileName,curl 示例就带同一个字段。
curl / REST
curl -fsS -X POST "$API_BASE/api/v1/general/merge-pdfs" \
-F "[email protected]" \
-F "[email protected]" \
-o merged.pdf
匿名调目录工具不需要 Key。来自 开发者 的 Key 给自动化一个稳定身份,也给自托管服务器一道门闸。OpenAPI 在 /v1/openapi.json。
多步作业交给 POST /api/v1/pipeline,它在 steps 字段里接受有序 op,比如先合并、再加水印、再压缩。重试不能重复跑同一份工作时,发 Idempotency-Key;成功重放会带回 Idempotency-Replayed。失败用 application/problem+json,码是稳定的,如 rate_limited、bad_request(开发者错误)。
托管响应还会带 X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset,HTTP 429 另含 Retry-After。把这些头当作现场预算,别背博客里那个数字(匿名限流)。
MCP
会说 Model Context Protocol 的 Agent 连到 /mcp,把合并发现成一个工具,用与 REST 相同的 API Key 身份调用。文档见 开发者 MCP。
MCP 不是第二份目录,它是叠在 OpenAPI 已列出的同一批操作之上的发现与调用协议。某个 op 在 MCP 里缺失,那是服务端缺陷,不是另有一条产品路线图。想在客户端连接前先拿一份散文式工具索引,把 MCP 和 /llms.txt 搭配用。
pdfx CLI
本地 pdf-core:
pdfx merge a.pdf b.pdf -o merged.pdf
或者直接打门户用的那台服务器:
pdfx --cloud --api-base "$API_BASE" --api-key "$KEY" merge a.pdf b.pdf -o merged.pdf
本地模式从不上传;云模式用与 curl 相同的 multipart 形状请求你的 base URL。dist/skills/pdf-toolbox/SKILL.md 下的编码 Agent skill 记录了同一套 merge 与 pipeline 形状,免得 Agent 再造一份 OpenAPI。
经上面任一客户端做 OCR,返回的仍是 /api/v1/misc/ocr-pdf 的 Markdown,不是隐藏文本层。这属于共享契约的一部分:只在某个客户端改返回类型,就会破坏“同一 op”的承诺。压缩仍由 /api/v1/misc/compress-pdf 做流重压缩,它与合并是不同的 op,同样有这四种到达方式。
为什么一致性重要
要是浏览器合并与 API 合并哪天分了叉,自动化会悄悄退化,而演示页看着仍然正常。一个 op、四种句柄,正是重点。门户是个便利客户端,不是合并、压缩或 OCR 的第二套实现。
这也是不做桌面分叉的原因:第五棵 UI 树换个二进制名字,又会把同一种漂移问题复制一份。更完整的框架见 为 AI Agent 而建,不只为浏览器,桌面这个决定的来龙去脉见 为什么我们不做桌面应用,把 API 立到自己网络上见 自托管。