Codex API 429 错误怎么解决?
简答:看到 Codex 的 429、Too Many Requests 或 “exceeded retry limit, last status: 429” 时,先不要盲目重试:读取完整错误体里的 error.type、error.code、message 和 request ID,再分三路处理——临时限流用 Retry-After 加指数退避,额度或账单错误去 Billing、Limits 或 Usage 处理,多个请求同时异常则查看 Status。本站为第三方指南,非 Codex 官方,具体规则以 OpenAI 官方文档和你当前版本为准。
定义:Codex API 429 是服务端拒绝当前请求的 HTTP 状态码,可能表示请求或 token 速率超限、流量增长过快、组织或项目 spend limit 达到,也可能是 Codex 使用的服务路径暂时异常;最终原因要以 error.code 和响应内容为准。
先看三分支结论:不要把所有 429 都当成“请求太快”
如果终端显示 429、Too Many Requests 或 exceeded retry limit, last status: 429,先保存完整响应,再按下面的表判断。
| 错误码或现象 | 通常表示 | 先做什么 |
|---|---|---|
rate_limit_exceeded | 请求数或 token 数超过适用速率限制 | 降低并发和发送速率,尊重 Retry-After 后重试 |
slow_down | 流量增长过快,即使平均 RPM 或 TPM 尚未达到上限 | 暂停或降速,逐步恢复,不要立即恢复原并发 |
credit_balance_exhausted | API 组织没有可用预付费余额 | 检查 API Billing 或余额;重复重试不能恢复 |
organization_usage_limit_exceeded | 组织达到平台分配的 usage limit | 申请更高的 approved usage limit,或联系支持 |
organization_spend_limit_exceeded | 组织设置的硬性 spend limit 已达到 | 检查组织设置,调整上限或等待重置 |
project_spend_limit_exceeded | 当前项目设置的 spend limit 已达到 | 检查项目设置,调整项目上限或等待重置 |
OpenAI 当前错误码文档明确区分了这些情况;其中账单、余额、usage limit 和 spend limit 错误不能靠重复请求解决。可查看官方 Error codes 文档与官方 Rate limits 文档。
先保存真实错误体:error.code 比“429”更有用
排查时至少记录 HTTP 状态码、error.type、error.code、error.message、请求时间、模型和 request ID。日志中不要保存 API Key、完整 Authorization 头或敏感提示词。
{
"error": {
"type": "rate_limit_error",
"code": "slow_down",
"message": "Request rate increased too quickly. Please slow down.",
"param": null
}
}临时限流的响应头可能包含以下信息:
HTTP/1.1 429 Too Many Requests
retry-after: 10
x-ratelimit-remaining-requests: 0
x-ratelimit-remaining-tokens: 12000
x-ratelimit-reset-requests: 10s
x-ratelimit-reset-tokens: 4s
x-request-id: req_example- Retry-After:服务端要求至少等待的时间;存在且有效时,不要提前重试。
- remaining-requests:当前请求维度的剩余额度。
- remaining-tokens:当前 token 维度的剩余额度。
- reset:对应维度恢复或重置所需的时间。
- request ID:联系支持或对照服务日志时的重要线索。
字段是否出现取决于响应和适用的项目限制,不能假设每个 429 都会返回全部响应头。官方文档列出了 Retry-After、请求和 token 限制及重置字段,实际仍应以响应为准。
Codex CLI 的 “exceeded retry limit, last status: 429” 怎么读
这句提示表示 Codex CLI 在某条请求路径上已经达到客户端的重试预算,最后一次状态是 HTTP 429。它不等于“只要继续按回车就会恢复”:可能是短时限流,也可能是 API 余额、组织限制、项目上限,或者当前 provider 配置没有把这类 429 当作可重试错误。
- 先确认登录方式。如果 Codex CLI 使用 ChatGPT 账号登录,检查的是 ChatGPT/Codex 计划的使用额度;如果使用自己的 API Key,检查的是 API 组织、项目、模型和 API Billing。两套额度不要混在一起判断。
- 再确认 provider 和 Base URL。如果配置了兼容网关,错误可能来自网关而不是 OpenAI API;先记录实际请求的 Base URL,再查看网关返回的错误体和响应头。
- 检查当前 Codex 版本的配置参考。公开仓库的当前配置结构把 provider 网络设置放在
[model_providers.<id>]下,相关字段包括request_max_retries、stream_max_retries和stream_idle_timeout_ms。版本更新可能调整字段或默认值,未知字段不要直接照抄到生产配置。
[model_providers.openai]
name = "OpenAI"
base_url = "https://api.openai.com/v1"
env_key = "OPENAI_API_KEY"
request_max_retries = 4
stream_max_retries = 10
stream_idle_timeout_ms = 300000上面的片段只展示配置形状,不代表所有版本的默认值或每一种 429 都会自动重试。官方仓库的配置源码和 issue 记录显示,Codex 不同请求路径的重试行为可能不同,尤其是请求尚未建立流和流式连接中断这两类路径。修改前先对照你安装版本的配置参考;如果字段不被当前版本支持,不要用它解决额度类 429。可查看Codex 配置源码、provider 重试配置源码和Codex 429 重试行为讨论。
ChatGPT 登录和 API Key:429 的额度边界不同
Codex CLI 常见有两种身份路径,排查时必须先分清。
| 登录方式 | 主要看什么 | 429 后先检查 |
|---|---|---|
| 使用 ChatGPT 账号登录 Codex | ChatGPT 计划、Codex 使用额度、工作区设置以及可用的额外 credits | ChatGPT 或 Codex 的 Usage 页面、当前计划、工作区限制和是否已达到使用上限 |
| 使用自己的 API Key | API 组织、项目、模型的 RPM、TPM、余额、usage limit 和 spend limit | API 控制台的 Limits、Usage、Billing,以及请求实际使用的组织和项目 |
官方帮助中心说明,使用 ChatGPT 登录 Codex 时,使用量和计费遵循 ChatGPT 计划;使用自己的 API Key 时,遵循 API 定价和 API 限制。ChatGPT 计划的 Codex 使用上限也可能因计划和工作区设置而不同,不能把 ChatGPT 订阅额度当成 API Key 的预付费余额。请参考Using Codex with your ChatGPT plan和ChatGPT Work and Codex。
从日志到后台:一套可执行的排查顺序
- 确认请求入口。记录 Codex CLI 使用的是 ChatGPT 登录、OpenAI API,还是第三方兼容网关;同时记录 Base URL、模型和项目标识。
- 读取错误体。先看
error.code,再看error.type、message、HTTP 状态和 request ID。不要只用 message 文本判断。 - 查看 Limits。API Key 路径下,打开组织或项目的 Limits,核对模型的 RPM、TPM 和项目级 token 限制。OpenAI 文档说明,限制可能同时按组织和项目生效,也可能因模型不同而不同。
- 查看 Usage。检查是否有多个 Codex 进程、自动化脚本、队列、定时任务或 CI 工作流共享同一组织和项目。
- 查看 Billing 与 spend limit。遇到
credit_balance_exhausted、organization_usage_limit_exceeded、organization_spend_limit_exceeded或project_spend_limit_exceeded,先处理余额、批准额度或组织/项目上限,再测试。 - 最后查看 Status。如果多个无关请求同时出现 429、延迟或其他错误,查看当前状态页,并保留错误时间、时区、模型和 request ID。历史事故只能说明某些平台异常可能表现为 429,不能据此判断当前一定是平台故障。
截至2026年10月24日,排查仍应以当前状态页和你自己的响应日志为准,而不是用历史事故替代现场证据。
临时 429 的正确重试:Retry-After、指数退避和熔断
临时限流的顺序是:有有效 Retry-After 就至少等待该时长;没有或无法解析时,使用指数退避和随机抖动;同时限制最大尝试次数、总耗时和队列过期时间。失败请求本身可能继续计入每分钟限制,因此无限重试会让问题更严重。
如果应用层自行重试,要关闭 SDK 自带重试,或把两层重试合并计算。下面的 Python 示例直接读取异常的 code 属性,并兼容错误体已经解包或仍包在 error 字段中的情况。
import random
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
import openai
from openai import OpenAI
client = OpenAI(max_retries=0)
PERMANENT_CODES = {
"credit_balance_exhausted",
"organization_usage_limit_exceeded",
"organization_spend_limit_exceeded",
"project_spend_limit_exceeded",
}
def get_error_code(exc):
code = getattr(exc, "code", None)
if code:
return code
body = getattr(exc, "body", None)
if isinstance(body, dict):
error = body.get("error", body)
if isinstance(error, dict):
return error.get("code")
return None
def retry_after_seconds(exc):
response = getattr(exc, "response", None)
headers = getattr(response, "headers", {}) or {}
value = headers.get("retry-after") or headers.get("Retry-After")
if not value:
return None
try:
return max(0.0, float(value))
except (TypeError, ValueError):
try:
target = parsedate_to_datetime(value)
if target.tzinfo is None:
target = target.replace(tzinfo=timezone.utc)
return max(
0.0,
(target - datetime.now(timezone.utc)).total_seconds(),
)
except (TypeError, ValueError, OverflowError):
return None
def call_with_retry(model, input_text):
for attempt in range(6):
try:
return client.responses.create(
model=model,
input=input_text,
)
except openai.RateLimitError as exc:
code = get_error_code(exc)
if code in PERMANENT_CODES:
raise RuntimeError(
f"non-retryable 429: {code}"
) from exc
if attempt == 5:
raise RuntimeError(
"temporary 429: retry budget exhausted"
) from exc
wait = retry_after_seconds(exc)
if wait is None:
wait = min(60.0, 2 ** attempt)
wait += random.uniform(0, 1)
time.sleep(wait)
except openai.InternalServerError as exc:
if attempt == 5:
raise
wait = min(60.0, 2 ** attempt)
wait += random.uniform(0, 1)
time.sleep(wait)
response = call_with_retry(
model="YOUR_MODEL",
input_text="Hello",
)这里的 max_retries=0 用于避免应用层和 SDK 叠加重试;生产代码还应增加总截止时间、取消机制、脱敏日志和任务幂等处理。官方文档说明,Python SDK 对 429 使用 RateLimitError,对 503 使用 InternalServerError,但不同 SDK 版本对长 Retry-After 的处理可能不同。
JavaScript 熔断时,不要在临时 429 分支最后无条件抛错,否则队列会直接退出。下面的示例读取 Retry-After,并在熔断窗口结束后清零失败计数。
const PERMANENT_CODES = new Set([
"credit_balance_exhausted",
"organization_usage_limit_exceeded",
"organization_spend_limit_exceeded",
"project_spend_limit_exceeded",
]);
let openedUntil = 0;
let failures = 0;
function retryAfterMs(error) {
const value =
error?.headers?.get?.("retry-after") ??
error?.headers?.["retry-after"] ??
error?.response?.headers?.["retry-after"];
const seconds = Number(value);
return Number.isFinite(seconds) ? Math.max(0, seconds * 1000) : null;
}
async function callWithGuard(request) {
if (Date.now() < openedUntil) {
return { skipped: true, reason: "circuit_open" };
}
try {
const result = await request();
failures = 0;
return result;
} catch (error) {
const code = error?.code ?? error?.error?.code;
const status = error?.status ?? error?.statusCode;
if (status === 429 && PERMANENT_CODES.has(code)) {
throw error;
}
if (status === 429 || status === 503) {
failures += 1;
const serverWait = retryAfterMs(error);
const fallbackWait = Math.min(60000, 1000 * 2 ** (failures - 1));
const wait = serverWait ?? fallbackWait;
if (failures >= 3) {
openedUntil = Date.now() + Math.max(wait, 30000);
}
return {
skipped: true,
reason: "temporary_throttle",
code,
retryAfterMs: wait,
};
}
throw error;
}
}
async function runAfterCircuitWindow(request) {
if (Date.now() >= openedUntil) {
failures = 0;
}
return callWithGuard(request);
}上层队列应根据返回值延迟重入队,而不是让临时 429 直接触发 Promise.all 失败。示例只处理数字秒格式;如果网关返回 HTTP-date,应在网关适配层解析,且不要把无效的 Retry-After 当成零等待。
官方 API 文档给出的原则是:有效 Retry-After 作为最小等待时间;缺失或无效时使用指数退避加随机延迟;对账单、额度和 spend limit 错误不要重试。参见官方重试与限流说明。
用最小 POST 验证真实推理请求,不要只请求 /v1/models
GET /v1/models 只能验证某些认证、路由或模型列表能力,不能代表 Responses 推理请求的 RPM、TPM 或模型负载。要验证实际推理链路,应发送一个最小的 Responses POST,并查看状态码、响应体和限流响应头。
curl -i https://api.openai.com/v1/responses \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "YOUR_MODEL",
"input": "Reply with one word: OK",
"max_output_tokens": 16
}'如果使用 --fail-with-body,请确认 curl 版本支持该参数;否则保留 -i,手动查看 429 的响应体和响应头。不要把 Key 写进脚本仓库,Shell 中用环境变量读取,并确认变量存在而不是打印变量值。
PowerShell 如果需要读取响应头,应使用 Invoke-WebRequest,而不是只返回正文的调用方式:
$headers = @{
Authorization = "Bearer $env:OPENAI_API_KEY"
"Content-Type" = "application/json"
}
$body = @{
model = "YOUR_MODEL"
input = "Reply with one word: OK"
max_output_tokens = 16
} | ConvertTo-Json
try {
$response = Invoke-WebRequest `
-Uri "https://api.openai.com/v1/responses" `
-Method Post `
-Headers $headers `
-Body $body
$response.StatusCode
$response.Headers
$response.Content
}
catch {
if ($_.Exception.Response) {
$_.Exception.Response.StatusCode
$_.Exception.Response.Headers
}
throw
}$env:OPENAI_API_KEY 只对当前 PowerShell 会话生效。若请求走的是兼容网关,把两个示例中的 URL 换成实际 Base URL,并以该服务商的路径、模型和认证文档为准。
降低 Codex 429 的工程化做法
- 集中控制并发。多个 Codex 进程、队列消费者、批处理和 CI 工作流可能共享同一项目限额,优先使用队列、令牌桶或集中限流器。
- 同时控制 RPM 和 TPM。请求次数没超不代表 token 没超;长上下文和过大的输出上限都会提高 token 维度压力。
- 把输出上限设到合理范围。Responses API 使用
max_output_tokens;不要把上限设置得远高于实际需求。 - 逐步升速。遇到
slow_down后,先降低发送速率,再逐步增加,不要让多个进程同时立即重试。 - 为 CI 设置并发策略。GitHub Actions 可先限制同一任务组的并发,但它不能替代应用层退避。
name: Codex jobs
on:
workflow_dispatch:
push:
concurrency:
group: codex-api-queue
cancel-in-progress: false
jobs:
call-codex:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Codex task
run: ./scripts/run-codex-task.sh
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}如果使用第三方兼容网关,网关可能有独立的并发、余额和限流规则,也可能不透传上游的错误码和响应头。遇到 429 时先区分网关层还是上游层,再按该服务商文档核验;查不到来源时,把错误标记为“来源未核实”,不要直接把它当作 OpenAI 官方错误码。
这些状态码不要用同一套重试逻辑
| 状态或错误 | 第一动作 |
|---|---|
400 或 unsupported_parameter | 检查接口、模型和参数名是否匹配;参数错误不应重试 |
| 401 | 检查 API Key 是否为空、过期、撤销,或是否读错了环境变量 |
| 403 | 检查组织、项目、权限或区域限制 |
| 404 | 检查 Base URL、路径、模型或资源 ID |
503 或 server_is_overloaded | 按 Retry-After 或指数退避重试;持续出现时检查 Status 或更换模型路径 |
| 流式输出中断 | 区分流开始前的 HTTP 错误和流开始后的事件错误;已经产生部分输出时,不要无条件完整重放请求 |
官方错误码文档把 503 的 server_is_overloaded 与 429 的 slow_down 区分开来;两者都可能需要等待,但触发原因不同。对任何状态码,都应先读取错误体再决定是否重试。
最后检查清单:五分钟内确认是哪一层的问题
- 错误体里是否有明确的
error.code? - 请求是 ChatGPT 登录路径、OpenAI API Key,还是第三方兼容网关?
- 是否同时检查了实际组织、项目、模型对应的 Limits、Usage 和 Billing?
- 是否读取了
Retry-After、剩余请求数、剩余 token 和 reset 头? - 是否存在多个 Codex 进程、CI、定时任务或队列消费者同时发请求?
- 应用层重试时,是否关闭了 SDK 自带重试,或已经把两层预算合并计算?
- 如果是 CLI 的
exceeded retry limit,是否核对了当前版本的 provider 配置和重试字段? - 如果多个请求同时异常,是否查看了当前 Status 页面,而不是只根据单个 429 推断原因?
如果你需要用兼容接口复现 Codex 请求,建议先核对 Base URL、Key、模型、返回错误体和限流响应头,再决定是否接入应用层重试。
常见问题
Codex API 429 是不是等一会儿就能恢复?
只有临时速率限制或 slow_down 通常适合等待后重试。若 error.code 是 credit_balance_exhausted、organization_usage_limit_exceeded、organization_spend_limit_exceeded 或 project_spend_limit_exceeded,必须先处理余额、批准额度或组织/项目限制。
为什么 RPM 没超过,Codex 仍然返回 429?
RPM 和 TPM 是不同维度,组织与项目也可能分别有限制;另外 slow_down 反映流量增长速度,即使平均 RPM、TPM 尚未耗尽也可能触发。应同时检查 token 响应头、Retry-After 和短时间内的并发突发。
“exceeded retry limit, last status: 429” 是 Codex 坏了吗?
不一定。它表示某条 Codex 请求路径已经达到客户端重试预算,最后一次状态是 429。可能是临时限流、额度错误、provider 配置或兼容网关返回;先查看 error.code、实际 Base URL、登录方式和当前版本配置。
ChatGPT 订阅能不能直接当成 API Key 额度?
不能直接混用。ChatGPT 登录 Codex 使用 ChatGPT 计划和 Codex 使用额度;自己的 API Key 使用 API 组织、项目、余额、usage limit 和 spend limit。两者应分别查看对应的 Usage、Billing 和限制页面。
openai-python 的 RateLimitError 应该怎么读取 error.code?
优先读取异常对象的 code 属性;为了兼容不同 SDK 版本,再检查 exc.body,并兼容错误体本身就是 error 对象或仍包在 error 字段中的情况。应用层自行重试时,可使用 OpenAI(max_retries=0) 避免 SDK 与应用层嵌套重试。
第三方兼容网关返回 429,应该看谁的额度?
先确认 429 是网关自身还是上游返回。检查实际 Base URL、网关日志、错误体和响应头;网关可能有独立并发、余额和限流规则,也可能不透传上游字段,最终以该服务商文档和可核验日志为准。