错误
所有错误响应都用同一个信封:
{ "error": { "code": "not_found", "message": "agent 00000000-... not found" } }
code 是一个稳定的、机器可读的字符串,你可以直接对它分支。它刻意不是 HTTP 状态码数字:
状态码已经在状态行里了,而且它无法区分两个共用同一状态码的不同失败。422 同时覆盖了
请求体格式错误和幂等键被复用,这两者需要不同的处理。
message 是给人和日志看的。不要解析它。
错误码
| HTTP | code |
含义 | 该怎么办 |
|---|---|---|---|
| 400 | bad_request |
被我们或上游供应商拒绝。message 里带着供应商自己的原文。 | 读 message。供应商的容量与策略错误都落在这里。 |
| 401 | unauthorized |
凭据缺失、格式错误、无效或已撤销。 | 检查 bearer。已撤销的密钥与错误的密钥不可区分,这是有意的。 |
| 403 | forbidden |
已认证,但无权限。 | 最常见的是:用 API 密钥去签发 API 密钥。请改用控制台会话。 |
| 404 | not_found |
在你的组织里没有这个资源。 | 属于另一个租户的资源同样返回 404 —— 不透露存在性。 |
| 405 | method_not_allowed |
动词用错了。 | — |
| 409 | idempotency_in_progress |
带这个 Idempotency-Key 的请求仍在执行。 |
短暂退避后重试。不要改动请求体。 |
| 422 | validation_error |
请求体校验失败。 | 修正请求。 |
| 422 | idempotency_key_reuse |
同一个键,不同的请求体。 | 一个键只绑定一个请求。换一个新键。 |
| 422 | idempotency_key_invalid |
键格式错误。 | — |
| 429 | rate_limited |
该组织超过每分钟 120 次请求。 | 退避。按组织计,所以多申请几把密钥没有用。 |
| 500 | internal_error |
我们的问题。 | 退避重试。若持续存在请反馈。 |
| 503 | unavailable |
某个必需的子系统未启用。 | 付费接口未配置时 x402 路由返回它 —— 关闭,而不是免费。 |
402 不是错误
/x402/* 路由上的 402 是协议在正常工作。它携带的是可签名的支付条款,不是一次失败。
见 x402。
几个值得注意的行为
404 隐藏存在性。 请求一个属于另一个组织的 agent,返回的 404 和请求一个从未存在过的 agent 完全一样。这是有意为之:区分这两者就等于让任何人都能枚举其他租户的资源。
已撤销和无效是同一个 401。 出于同样的理由。
上游错误不做洗白。 当邮件供应商拒绝创建收件箱时,你会拿到 400 和供应商的原始信息。
另一种做法 —— 把它吞掉并存下一个失效地址 —— 会在更晚、更远、更难诊断的地方失败。
记账从不导致请求失败。 如果一笔支付已经结算但账本写入失败,请求仍然返回 200。
动作已经发生,钱已经动了;此时报告失败才是错误的答案。
安全地重试
非幂等的 POST 接受 Idempotency-Key 头。重试时发送同一个键,你会拿到第一次请求的响应,
而不是第二个 agent、第二个钱包或第二笔支付。
curl -X POST $BASE/agents \
-H "Authorization: Bearer $INCARNA_KEY" \
-H 'Idempotency-Key: 8f14e45f-ea6a-4f7e-9c1b-1a2b3c4d5e6f' \
-H 'Content-Type: application/json' \
-d '{"name":"..."}'
几条值得知道的规则:
- 记录 24 小时后过期。
- 一个键绑定到它第一次见到的请求体。用不同的请求体复用它会得到
422 idempotency_key_reuse,而不是悄悄重放。 - 并发在数据库层面解决,所以两个同时发起的重试不可能都执行工作。输的那个拿到
409 idempotency_in_progress。
degraded 不是错误
处于 degraded 的 agent 和其他任何 agent 一样返回 200。它表示某个开通任务用完了重试 ——
可恢复,而且会自行恢复。见生命周期。