429 Too Many Requests — 等你看见它的时候,自动重试已经用完了
- 先看有没有
retry-after:有 = 限速,退避和降并发有用;没有 = 配额耗尽,退避永远无效,只能加额度或换路径。 - 别手动重试——临时 429 在你看见之前已经被自动退避重试过了,能冒到你眼前就说明它不是临时的。
- 插件类客户端反复 429 而同一凭证 curl 正常时,主解是改用 OpenAI 兼容接口路径,调并发只是次解。
临时性的 429 是会被自动重试的——指数退避最多 10 次,而且响应带 retry-after 时会按它来等。所以真正能冒到你眼前的 429,多半已经挺过了这一轮。你再按一次回车,是在重跑一个刚刚被证伪了十次的假设。值得问的问题不是「我该等多久」,而是这个响应有没有 retry-after 头——因为就是这一个头,把两个共用同一个状态码的、完全不同的问题分开。
你看到的报错
API Error: 429 {"type":"error","error":{"type":"rate_limit_error","message":"..."}}
429 Too Many Requests
API Error: Request rejected (429) · this may be a temporary capacity issue. If it persists, check the status page.
API Error: Request rejected (429) · spend limit reached (daily; resets ... UTC)
前三条是限流。第四条根本不是限流——它是同一个状态码承载了相反的含义,而且所有对前三条有用的直觉,用在第四条上只会让事情更糟。
两种 429:共用一个数字,其余毫无共同点
| 限流 — 你发得太快了 | 耗尽 — 你的额度用完了 | |
|---|---|---|
retry-after 头 | 有 | 没有 |
| 错误类型 | rate_limit_error | rate_limit_error — 完全一样 |
| 能分辨的字段 | — | Messages API 上 error.details.error_code = enforced_spend_limit_reached |
| 重试提示头 | — | 网关的消费上限 429 会带 x-should-retry: false |
| 退避有用吗? | 有用 — 它就是为这个设计的 | 永远没用。连自动重试也一样失败,直到访问恢复 |
| 会自己好吗? | 会,几秒到几分钟 | 只在它写明的重置时间,或有人把上限调高 |
| 真正能解决的动作 | 降并发、多用缓存、把突发摊平 | 提高档位或上限,或者换一条路径 |
还有两种形态值得知道,因为它们从另一个方向打破「429 就是速率限制」的条件反射:
- 你自己给组织或工作区设的消费上限,回的是 HTTP 400
invalid_request_error,不是 429,信息以You have reached your specified API usage limits开头。很多人在搜一个自己压根没收到过的 429。 - 专门针对编码代理工作区的限额是单独判定的,它可能回一个带
retry-after的 429——所以retry-after告诉你的是「退避值得一试」,不是「你的配额还健康」。
读头,别读信息文案
信息文案是写给人看的,各家不一样。头才是真正的契约。
# <模型 id>:从 https://api.9coding.com/v1/models 里复制一个
curl -sS -D - -o /dev/null https://api.9coding.com/v1/messages \
-H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"<模型 id>","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}' \
| grep -iE 'http/|retry-after|x-should-retry|anthropic-ratelimit'
你要找的是:
| 响应头 | 它告诉你什么 |
|---|---|
retry-after | 要等多少秒。它出不出现本身就是诊断结论;提前重试只会继续失败 |
anthropic-ratelimit-requests-remaining | 还剩多少请求余量 |
anthropic-ratelimit-input-tokens-remaining | 输入 token 余量(按千取整) |
anthropic-ratelimit-output-tokens-remaining | 输出 token 余量 |
anthropic-ratelimit-*-reset | 该桶完全补满的时间,RFC 3339 格式 |
三种能省下真实时间的读法:
- 有
retry-after,某个*-remaining归零或接近零 → 普通限流。归零的那个告诉你撞的是哪条线,而这决定了哪种修法有用。输出 token 用完,不是靠少发请求能解决的。 - 没有
retry-after→ 现在就停止重试。等待改变不了任何事情。跳到第 5 步。 - 一个
anthropic-ratelimit-*头都没有 → 你的端点没有把它们透传出来。你还能用retry-after,但你失去了余量的观测能力。这件事,最好在事故之前就知道,而不是在事故之中。
有一个反直觉的性质:限额是持续补充的,不是按钟点重置的。一条按分钟表述的限制,可能在短得多的窗口上执行——每分钟 60 个请求,实际行为可能接近每秒 1 个。一次突发可以撞上一条你平均值上离得很远的线。如果用量曲线看着没问题却还在被限流,去看流量的形状,不要看总量。
按这个顺序排查
1 · 先判断你手上是哪一种 429#
跑上面那条查头的命令。下面所有分支都建立在这个判断上,而第 2 到 4 步用在「额度耗尽」上是纯粹的浪费。
2 · 别在内建重试之上再叠一层自己的重试#
如果你用 shell 循环包住了客户端,或者你正按着回车不放,你是在一个已经跑完的退避计划上再加延迟。更糟的是,你在往一个需要安静下来才能补满的桶里继续灌请求——重试本身变成了它一直空着的原因。
在做任何反应之前先分清两种显示状态:正在重试是一个带尝试次数的倒计时,它正在干活,别动它。没有倒计时直接打印出来的报错,才是重试预算用完之后的终态。
3 · 先降并发,再谈调高任何东西#
限流通常是被并行度塑造的,不是被总工作量塑造的。
export CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY=2
同样值得做的:少跑几个并行子代理;不要几个会话同时打同一份凭证;如果是在把一个新工作负载往上加量,慢慢加——流量的陡然阶跃会触发加速度限制,哪怕你的稳态用量离档位上限还很远。
4 · 不抬高限额,也能抬高实际吞吐#
这是全页最便宜的修法,也是几乎没人想到的那个。在多数模型上,从缓存读出来的 token 不计入每分钟输入 token 限额——只有未缓存的输入、以及正在写入缓存的部分才算。具体说:cache_read_input_tokens 在限流器眼里是免费的,而 input_tokens 和 cache_creation_input_tokens 不是。
这个差别很大。在每分钟 200 万输入 token 的限额下,如果缓存命中率是 80%,同样一条限额可以推过去大约每分钟 1000 万总输入 token。所以,把每轮都要重发的东西缓存起来——系统指令、工具定义、大段上下文文档、对话历史。一个中午被限流的工作负载,下午在同样的量下可能就很宽裕,而限额一个字都没改。
先看自己的数:如果一个长会话里 cache_read_input_tokens 接近零,这就是你手上最大的那个杠杆。
5 · 如果是额度耗尽,就别再排错了,去改点什么#
先确认客户端实际在用哪份凭证——最常见的意外是:它用的不是你刚充值的那份。
/status
然后:去服务商控制台看当前生效的限额和已用量,把档位或上限调上去。如果访问是被暂停到一个写明的重置时间,那个时间就是答案,你这边任何配置都挪不动它。
6 · 无人值守的场景要的是另一个设置,不是另一种心态#
CI 任务和长时间自主运行的会话里,跑到一半失败的代价比等待大得多,这时可以让客户端在容量类错误上一直重试下去,而不是用完默认预算就放弃。这个行为、它的默认尝试次数、它的上限,都在版本之间变过——请按你自己正在跑的那个客户端版本去查环境变量参考,不要从论坛帖子里抄一个数值。有一条性质一直没变,而且是最重要的那条:它在消费上限类的 429 上仍然立刻失败,因为对那种错误无限重试,只是一种更慢的失败方式。
一种和你的用量无关的 429
这里要单独点名一个模式,因为它会把人往错误的方向带上好几天。
插件类的客户端——浏览器翻译插件是最清楚的例子——发出去的默认 Prompt,对它的每一个用户都是逐字节相同的。成千上万个安装,产生出一股看起来像单一来源的请求流:同样的系统提示词、同样的形状、同样的节奏,在页面加载时成簇地涌过去。在上游看来,这样的流量很容易被判定成「同一个行为体在绕开按账号计的额度」,于是回你一个 429——而它跟你的用量毫无关系。
破绽在于它不像限流。你自己的量小得可怜。降并发毫无变化。余量头(如果你拿得到)显示还剩很多。而且它倾向于全有或全无——要么这个客户端能用,要么就是不能用——而不是随负载逐渐劣化。
真正管用的是改请求路径,不是改请求速率。把这个客户端改走 OpenAI 兼容接口、而不是原生接口,流量就有了不同的形状和不同的判定结果,通常立刻就通。降并发是次一级的措施;单独用它一般什么都不会发生,而这恰恰是很多人最后得出「我的配额坏了」这个结论的原因。
curl 打过去是通的,那你就没有额度耗尽,再怎么退避也不会有用。
什么时候这不是你的问题
以下情况说明问题在上游,不在你的用量:
- 你的
anthropic-ratelimit-*-remaining头显示余量还很足,却仍然在收 429, - 指向同一个端点的其他客户端也在同一时间段开始失败,
- 它是在你的工作负载没有任何变化的情况下开始的,
- 而且你什么都没调,它自己就恢复了。
这四条凑齐,指向的是共享的上游池子或一次总体容量事件。在你为一个本来就帮不上忙的更高档位花钱之前,先去看状态页。
提问题时请带上 request id——9Coding 的报错响应里都有一个,形如 (request id: 2026...)。这个 id 加上时间戳、模型名和完整报错文本,就能把那一次调用查出来。只说「用不了」的反馈没法排查。
429 和 529 是两种不同的故障
529 表示服务过载——那是他们那边的容量问题,影响所有人,跟你的额度无关。两者都会被自动重试,也都只在预算耗尽后才浮到你眼前。区别在于之后该做什么:429 在你这边有杠杆(并发、缓存、档位)。529 没有——你只能等,或者切走。用降并发去修一个 529,无害,但也无用。请看 529 过载。
相关报错
- 529 过载 — 他们那边的容量问题,不是你这边的额度问题
- 401 Unauthorized — 两层认证,一样的报错信息
- TLS 证书错误 — 那个一点重试预算都没有的故障
- 全部报错速查 — 排查手册总表