LLM API 错误码是一套很小的词汇表,而读懂它们与据此行动之间隔着一道大鸿沟。造成这道鸿沟的有两件事:同一个状态码在不同提供商那里可能意味着不同的东西;还有一类错误,无论什么策略都不值得重试。可行的做法是按层分诊:认证,权限与账号,端点与路径,容量与速率,然后再判断这个错误到底是否可重试。如果你还没有对端点成功完成过一次最小调用,先去做那件事,我们快速上手里的做法适用,因为每一张分诊表都假设你已经有一条可用的基线。
按层分诊:API 错误 401 与 403、404 及其余
按层而不是按数字来分类,会让你保持诚实,因为即便数字重复,修法也随层而异。下表覆盖你最早会遇到的这些错误码;每一条都是官方解读,依据 OpenAI 的错误码指南与 Anthropic 的错误参考。
| Code | Layer | First thing to check |
|---|---|---|
| 400 | Request | Malformed request or unsupported parameter value; OpenAI also returns it for a disallowed service tier, Anthropic as invalid_request_error |
| 401 | Authentication | The key itself: OpenAI distinguishes invalid authentication from an incorrect key, and Anthropic's authentication_error covers malformed, revoked, or expired keys |
| 402 | Account | Anthropic only: billing_error means the billing or payment information needs attention |
| 403 | Permission and region | OpenAI's known 403 is a country, region, or territory not supported; Anthropic's permission_error means the key lacks access to the resource, so check organization and workspace settings |
| 404 | Path | The endpoint path or resource identifier in the URL does not exist, per Anthropic's not_found_error |
| 409 | State | Anthropic's conflict_error: the request conflicts with the resource's current state, such as a concurrent modification; resolve, then retry |
有一个模式值得早点注意:在 OpenAI 兼容配置中,401 和 404 往往是配置问题而不是代码问题——密钥被截断,或者 base URL 丢了路径。这种失效模式在提供商切换指南里有单独的实战讲解,包括陈旧取值可能藏身的环境变量与设置界面。
容量层:OpenAI 的 429 限流子类型、503,以及 Anthropic 的 529 overloaded
这里错误词汇才真正显出厂商特色,因为同一个 429 状态承载着好几种不同情形。OpenAI 的文档把它拆成有名字的几种:rate limit reached for requests,告诉你要给请求限速,并在存在 Retry-After 头时遵循它;slow_down,即使你的流量还在名义限额内也可能出现,要求你遵循 Retry-After 头并更缓慢地爬坡;以及 credit_balance_exhausted,这根本不是限速问题,因为组织没有预付额度,必须充值。还有一个相关情形 organization_spend_limit_exceeded,在触及强制消费上限时触发。
Anthropic 的 429 拆法不同,其中一种变体值得单独点出:层级消费上限触发的 429 不带 retry-after 头,并且会持续失败直到访问恢复,所以一个天真的重试循环会对着它无限打转。该提供商还指出,Claude Code 工作区的消费上限可能以 429 出现,而相关情形则以 400 出现;用量陡增可能触发加速限制的 429——这本身就说明应当有意识地给流量爬坡。对长时间运行的请求,超时情形也有自己的归处,即 504 timeout_error。
服务端容量错误大多看起来一样,但要仔细读:OpenAI 的 503 以 service_unavailable_error 出现,code 为 server_is_overloaded,意味所请求的模型暂时过载,指引是存在 Retry-After 时遵循它,缺失时加大延迟。500 是通用服务器错误。Anthropic 的 500 是 api_error,用指数退避重试,若持续存在,该提供商要求你在联系支持时提供 request ID。然后是 529 overloaded_error,一个标准集合之外的状态码,表示 API 对全体用户暂时过载。非标准状态码恰恰是那些凭记忆写出的错误映射器会漏掉的;处理器必须显式点名它,否则它就落进 catch-all。
当容量成为最常失守的那一层时,战略问题变成重试该落在哪里:同一端点,还是备选端点。跨端点、跨提供商路由的架构取舍见我们的聚合与直连对比。
重试还是停止:决策表
在 API 错误处理中最有用的单一区分,是“稍后可能成功的错误”与“除非请求之外有东西改变、否则不会成功的错误”之间的差别。按两家提供商自己的指引,划分如下。
| Situation | Retry? |
|---|---|
| 429 with a Retry-After header | Yes, honoring the header |
| 503 or 529 (overload) | Yes, with backoff, honoring Retry-After when present |
| 500-class server errors | Yes, bounded, with backoff |
| Connection errors before a response | Yes, standard practice |
| Any 401 or 403 | No. Fix the key or the permission first |
| 404 and 409 | No. Fix the path or resolve the state conflict first |
| Billing and quota conditions, including credit_balance_exhausted and Anthropic's 402 and tier spend-cap 429 | No. The official line is blunt: retrying billing, spend, or quota errors will not restore access, and the spend-cap 429 keeps failing by design until access resumes |
无论哪家厂商,三条原则能让重试逻辑保持诚实。第一,带抖动的指数退避,让爆发不会同步。第二,API 响应带有 Retry-After 头时始终遵循它;把缺失该头当作需要更多耐心的理由,而不是更少。第三,只重试可安全重复的操作;对有副作用的操作,先让它幂等,再让它进入重试层。还要了解你的 SDK 已经做了什么:两大 SDK 都对一小部分错误内置了自动重试,带指数退避和可配置上限——这很有帮助,直到它悄悄把你并不知道正在发生的重试风暴翻了一倍。请弄清这个设置。
同一个问题,不同厂商给出不同错误码
按层而不是按数字分支,最实际的理由是业务层面的问题在不同提供商那里映射到不同错误码。账户余额耗尽,在 Anthropic 是 402 billing_error,在 OpenAI 是 credit_balance_exhausted 429。权限问题在 Anthropic 是 403 permission_error,而 OpenAI 那个独立的 403 是地理原因。速率压力在两家都可能是 429,但子类型不同,而 Anthropic 的一种 429 变体(消费上限)的行为与 OpenAI 限流器产出的任何东西都不一样,因为它根本不是限速信号。
这对你的代码有具体后果:维护一张自己的映射表,一层一行,写明落进该层的各厂商错误码,并按层路由处理。新增一家提供商或一个兼容端点时,你扩展这张表,而不是重写分支。把错误码当作方言,把层当作语法。
一个最小的错误处理骨架
把这件事处理好,不需要精巧的框架。你只需要每层一个处理器,并给出明确的处置方式:
- 认证与权限(401、403):不重试;快速失败,抛出“检查密钥或权限”,并记录端点与凭据来源,让修法一目了然。
- 账号与计费(402、余额与消费上限情形):不重试;告警给运维人员,因为这一类要等人来动手。
- 路径与状态(404、409):不重试;记录确切的 URL 与资源 ID,因为这一层几乎总是配置或并发 bug,本不该被重试成噪音。
- 容量(带 Retry-After 的 429、503、529、500 类):按上面三条原则重试;如果重试预算耗尽,降级到备用模型或端点,而不是把请求丢在地上。
- 未知:记录并告警,绝不静默吞掉。通用处理器应把无法识别的状态码(包括非标准状态码)当作独立一类。
有两个日志细节在每次事故复盘中都能回本。记录错误的结构化 type 与 code,而不只是状态行,因为两家提供商都在数字之上叠加了含义:OpenAI 要求你针对计费与限流情形查看 error.code,Anthropic 则指出 error.type 的取值可能随时间增加,这也是“真理由你的映射表而非记忆承载”的又一个理由。同时记录提供商返回的 request ID,连同端点与模型,因为它是把“某个东西失败了”变成具体时间线的唯一抓手。至于相邻的那一类——请求成功但响应形状不对,streaming 事件与工具调用是常见嫌疑——那一片在本文末尾有自己的清单;端点上下文本身(base URL、bearer 认证、模型列表)写在平台自己的文档。
接下来做什么
- 配置层的一个完整实例。实践中大多数 401 和 404 来自凭据与端点配置面, Cursor 自定义端点配置端到端走完一个真实案例,包含它自己的分诊表。
- 隔壁的形状层。当状态码没问题而响应行为异常时,兼容性检查清单把这个面拆成可测试的若干块。
读错误码、说出它属于哪一层、判断重试有没有可能帮上忙,然后才写那个分支。