Paperclip API 总览:base URL、三种令牌与七个错误码分别代表什么
想用脚本或者自己的 Agent 去驱动 Paperclip,第一步往往不是写业务逻辑,而是被两个很基础的问题卡住:请求到底打到哪个地址,以及 Authorization 头里该放什么。这两件事官方分别写在 API Overview 和 Authentication 两篇文档里,合起来看才是完整的一张图。
麻烦在于,Paperclip 的调用方不止一种。有的是跑在里面的 Agent,有的是坐在浏览器前面的董事会操作者(board operator),还有的是外部脚本。这三类的身份来源不一样,令牌的生命周期也不一样。如果按照”拿个 API key 一把梭”的惯性去理解,很容易在运行期拿着一个短命令牌当长期凭据用,或者反过来给一个只跑一次的任务配了一把长期密钥。
这篇按官方文档的口径把 API 层的约定过一遍:地址、鉴权、请求约定、响应与错误码、公司作用域。文档没写的地方(比如具体的分页参数名、生产环境限流的具体阈值),我会直接标出来,不替官方补。
请求打到哪:base URL 与 /api 前缀
官方给出的默认 base URL 是:
http://localhost:3100/api
两个信息点。一是默认端口 3100,二是所有端点都带 /api 前缀。也就是说文档里写成 POST /api/agents/{agentId}/keys 的路径,是已经含前缀的完整路径,你在拼 URL 的时候不要再加一层。
这里值得注意的是 localhost。文档给的是默认值,属于本地部署的形态;换成容器、换成远程部署之后地址当然会变,但 API Overview 这一篇没有讨论非本地场景下 base URL 怎么确定,这属于部署侧的话题。要决定自己该跑哪种形态,去看三种部署模式怎么选那篇,本文只讨论接口层。
Authorization 头里放什么:三类令牌
官方的措辞很直接:所有请求都需要 Authorization 头,格式是标准的 Bearer:
Authorization: Bearer <token>
<token> 可以是三类中的任意一类:
| 令牌类型 | 官方描述 | 典型持有者 |
|---|---|---|
| Agent API keys | 为 Agent 创建的长效密钥(long-lived) | 需要持久访问权限的 Agent |
| Agent run JWTs | 心跳期间注入的短效令牌,走 PAPERCLIP_API_KEY | 每次运行中的 Agent |
| User session cookies | 用户会话 cookie | 用 Web UI 的 board operator |
三者的差别不是”哪个更安全”,而是生命周期和注入方式完全不同。下面分开说。
运行期 JWT:官方推荐给 Agent 用的那一种
Authentication 文档把 Run JWT 明确标为「Recommended for agents」。机制是:心跳期间,Agent 会通过环境变量 PAPERCLIP_API_KEY 拿到一个短效 JWT,直接放进 Authorization 头即可:
Authorization: Bearer <PAPERCLIP_API_KEY>
关键的一句在后面——这个 JWT 的作用域被限定在该 Agent 和当前这次运行上。理解这句话,很多设计上的疑问就解开了:它天然不适合被缓存、被写进配置文件、被跨运行复用,因为换一次运行它的作用域就不对了。写 Agent 代码的时候,正确的做法是每次运行从环境变量里读,而不是启动时读一次存成全局常量。
这个环境变量名容易踩坑:叫 PAPERCLIP_API_KEY,但装的是短效 JWT,不是上面那类长效 API key。名字里带 API_KEY 三个字,实际语义是运行期令牌。看到有人把它当长期密钥存下来,多半就是被这个命名误导的。
至于心跳本身怎么运转、Agent 卡住时从哪一头查,那是另一条线,可以看Agent 不干活时的心跳与看门狗排查。
长效 Agent API key:只在创建那一刻能看到全值
需要持久访问的 Agent,可以创建长效密钥,接口是:
POST /api/agents/{agentId}/keys
官方对这个接口写了一条很重要的性质:返回的 key 应当被妥善存储,因为密钥在服务端是哈希存储的(hashed at rest),只有创建那一刻能看到完整值。
这条性质决定了操作流程。它意味着没有”再查一次密钥”这种补救路径——文档里没有提供任何取回明文的接口。创建密钥的那一次响应就是唯一的机会,要么当场写进你的密钥管理系统,要么就只能作废重建。做自动化脚本的时候,不要把创建密钥和存储密钥拆成两个可能失败的独立步骤中间还夹着别的逻辑;创建成功后第一件事就是落到安全存储里。密钥该往哪存,展开在密钥管理与 AWS provider。
会话 cookie:Web UI 侧的那条路
board operator 走的是另一套。官方分了两种模式:
- Local Trusted Mode:不需要鉴权。所有请求都被当作本地的 board operator 处理。
- Authenticated Mode:board operator 通过 Better Auth 会话(基于 cookie)鉴权,Web UI 自动处理登录/登出流程。
Local Trusted Mode 这条”不需要鉴权”的设定,读的时候要留意它的适用前提——它是把请求方直接信任为本地操作者。文档没有说明这个模式下如何限制来源,也没有讨论把它暴露到局域网或公网时的风险控制,所以不要从”本地默认无鉴权”推出”随便暴露也没事”这类结论。
让 Agent 确认自己是谁
还有一个自查接口:
GET /api/agents/me
返回该 Agent 的记录,官方列出的字段包括:ID、所属公司、角色、指挥链(chain of command)、预算。
这个接口在调试期很有用。令牌错了、作用域不对、公司挂错了,这些问题在业务接口上通常表现为一个含糊的 403 或 404,而 GET /api/agents/me 能直接把”服务端认为你是谁”打印出来。接自建 Agent 的时候,把它当作连通性冒烟测试的第一条请求是划算的。
请求怎么组:JSON、companyId 与运行审计头
Overview 里关于请求格式给了三条约定:
- 所有请求体都是 JSON,
Content-Type: application/json。 - 公司作用域的端点,路径里要带
:companyId。 - 心跳期间,所有会产生写操作的请求都要带
X-Paperclip-Run-Id头,用途是运行审计追踪(run audit trail)。
第三条最容易被漏掉,因为漏了大概率不会立刻报错——它是审计用的,不是鉴权用的。但对一个把多个 Agent 的行为都记进活动日志的系统来说,这个头就是”这条写操作属于哪一次运行”的唯一线索。自己写适配器或者写脚本的时候,建议在 HTTP 客户端层面统一注入,而不是逐个接口手动加,否则总会漏掉几个。
响应与错误码:哪几个要求你别重试
响应格式很简单:全部返回 JSON,成功时直接返回实体本身(不是包一层 data),失败时返回:
{
"error": "Human-readable error message"
}
也就是说错误体里只有一个人类可读的字符串,官方文档没有给出机器可判别的错误码字段。要区分错误类型,只能靠 HTTP 状态码。官方给的七个状态码,连带处置建议如下:
| 状态码 | 含义 | 官方给的处置 |
|---|---|---|
400 | 校验错误 | 对照期望字段检查请求体 |
401 | 未认证 | API key 缺失或无效 |
403 | 未授权 | 你没有执行该操作的权限 |
404 | 未找到 | 实体不存在,或不在你所属的公司内 |
409 | 冲突 | 任务已被另一个 Agent 占有,换一个任务,不要重试 |
422 | 语义违规 | 非法的状态流转(例如 backlog 直接到 done) |
500 | 服务端错误 | 瞬时故障,在任务下留一条评论然后继续 |
这张表里有两条是明显超出常规 HTTP 语义的,属于 Paperclip 自己的行为约定,写客户端时必须照做:
409 明确要求不要重试。 常规的重试中间件遇到 409 往往会退避重试,但在这里 409 的含义是”这个任务已经被别的 Agent 领走了”,重试再多次结果也一样,只会空转并且加剧竞争。正确反应是换一个任务。如果你用了通用重试库,记得把 409 从可重试状态码里剔掉。
500 要求留评论再继续,而不是崩掉。 官方把 500 定性为瞬时故障(transient failure),并且给了一个具体动作:在任务上评论一条,然后往下走。这个设计意图不难体会——Agent 是异步长跑的,遇到服务端抖动就整个终止的话,人工介入的成本更高;留下一条评论,至少在活动日志和任务页面上留了痕迹。
另外两个容易混的是 403 和 404。按官方描述,404 的含义里包含了”实体不在你所属的公司内”,也就是跨公司访问的一部分情况会以 404 形式出现,而不是 403。这是个刻意的信息隐藏设计:不让调用方通过错误码差异去探测别的公司里有没有某个 ID。
422 值得单独提一句,它管的是状态机而不是字段格式。官方举的例子是 backlog 直接跳到 done——请求体本身合法,字段也都对,但这个状态流转在语义上不被允许。这类错误改请求体是没用的,得回去看任务流转规则。issues 和 agents 这两组接口的具体字段与状态,在issues 与 agents 两组核心接口那篇展开。
公司边界是硬约束
Authentication 文档最后一节把作用域规则写得很清楚:所有实体都归属于某个公司,API 会强制执行公司边界。
- Agent 只能访问自己所属公司内的实体;
- board operator 可以访问自己作为成员的所有公司;
- 跨公司访问一律返回
403。
这条规则和上面 404 的描述放在一起看,会发现两处口径需要注意:Overview 里说 404 用于”实体不存在或不在你的公司内”,Authentication 里说跨公司访问被拒绝返回 403。官方文档在这两处的表述不完全重合,具体某个请求落到 403 还是 404,文档没有给出统一的判定规则。写客户端时保守一点的做法是两个都当作”这个 ID 我碰不到”来处理,不要基于状态码差异去反推实体是否存在。
分页与限流:文档给到哪一步
这两块官方写得比较克制,照实转述:
分页:列表端点在适用时支持标准的分页查询参数。排序规则是——issues 按优先级排序,其他实体按创建时间排序。至于参数名叫什么、默认每页多少条、有没有游标,这一篇没有写。
限流:本地部署不强制任何限流;生产部署可能在基础设施层面加上限流。注意这句话的措辞是”可能”(may add),也就是说限流不是 Paperclip 应用层的能力,而是留给你自己在反向代理或云厂商侧做的事。文档没有给出任何具体的速率阈值,看到任何”Paperclip 每分钟支持多少请求”的说法,都不是出自这两篇官方文档。
什么时候这两篇不够用
把上面的内容落到实处,还有几个缺口需要提前知道:
没有正式的 OpenAPI 规格。这两篇是叙述式文档,给的是约定和代表性端点,不是可以直接生成客户端的机器可读规格。要拿到完整端点清单,得逐篇去看 API 分组文档。
错误体里没有结构化错误码。只有一个 error 字符串。想按错误原因做精细分支,只能靠状态码加字符串匹配,而字符串是人类可读的、随时可能改。别把业务逻辑挂在错误文案上。
令牌的生命周期数字没写。Run JWT 只说了”短效”(short-lived),具体多长时间过期、过期后怎么续、Agent 侧要不要处理续期,这两篇都没有说明。长效 API key 也没有写有没有过期时间、能不能设置到期。
没有撤销与轮换的说明。文档给了创建密钥的接口 POST /api/agents/{agentId}/keys,但没有提到删除、撤销或者列出已有密钥的接口。密钥泄露之后怎么止血,这两篇没有覆盖。
Local Trusted Mode 的边界没有细说。“不需要鉴权、一律当作本地 board operator”是一句结论,但什么条件下这个模式生效、怎么切换到 Authenticated Mode、切换后已有的 Agent 密钥是否受影响,都不在这两篇的范围内。
真要接 API,建议的顺序是:先用 GET /api/agents/me 打通鉴权,确认服务端认得你的身份和公司;再挑一个只读的列表端点验证 companyId 拼法;最后才上写操作,同时把 X-Paperclip-Run-Id 和 409/500 的处置逻辑一起补齐。把这三步走完,剩下的就是各个业务分组的字段细节了。
延伸阅读
- 从头读起:Paperclip 是什么:一个自己不跑 Agent 的控制平面,怎么管住一整家 AI 公司
- 本专题共 40 篇,完整分组目录见专题页
- Paperclip 的 issues 与 agents 接口怎么用:字段、状态机与 409/400 的真实含义
- Paperclip 的目标与项目层怎么组织:goals、projects 与 workspaces 三层接口拆解
本文依据 Paperclip 官方仓库(github.com/paperclipai/paperclip,MIT 协议)的 docs/ 用户文档
与 doc/ 下的规范、运维与连接器手册整理,核对日 2026-08-17。
我们没有部署或运行过 Paperclip,因此不涉及界面外观与操作手感;
部分规范文档描述的是目标架构而非当前实现,文中已就地标注,不构成对实际行为的保证。
请以仓库最新内容为准。