SudoCode

Codex API 429 错误怎么解决?

更新于 2026-10-24 · sudocode.cyou

简答:看到 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,先保存完整响应,再按下面的表判断。

Codex API 与 Codex CLI 的 429 判断表
错误码或现象通常表示先做什么
rate_limit_exceeded请求数或 token 数超过适用速率限制降低并发和发送速率,尊重 Retry-After 后重试
slow_down流量增长过快,即使平均 RPM 或 TPM 尚未达到上限暂停或降速,逐步恢复,不要立即恢复原并发
credit_balance_exhaustedAPI 组织没有可用预付费余额检查 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

字段是否出现取决于响应和适用的项目限制,不能假设每个 429 都会返回全部响应头。官方文档列出了 Retry-After、请求和 token 限制及重置字段,实际仍应以响应为准。

Codex CLI 的 “exceeded retry limit, last status: 429” 怎么读

这句提示表示 Codex CLI 在某条请求路径上已经达到客户端的重试预算,最后一次状态是 HTTP 429。它不等于“只要继续按回车就会恢复”:可能是短时限流,也可能是 API 余额、组织限制、项目上限,或者当前 provider 配置没有把这类 429 当作可重试错误。

  1. 先确认登录方式。如果 Codex CLI 使用 ChatGPT 账号登录,检查的是 ChatGPT/Codex 计划的使用额度;如果使用自己的 API Key,检查的是 API 组织、项目、模型和 API Billing。两套额度不要混在一起判断。
  2. 再确认 provider 和 Base URL。如果配置了兼容网关,错误可能来自网关而不是 OpenAI API;先记录实际请求的 Base URL,再查看网关返回的错误体和响应头。
  3. 检查当前 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 常见有两种身份路径,排查时必须先分清。

两种 Codex 使用方式的排查入口
登录方式主要看什么429 后先检查
使用 ChatGPT 账号登录 CodexChatGPT 计划、Codex 使用额度、工作区设置以及可用的额外 creditsChatGPT 或 Codex 的 Usage 页面、当前计划、工作区限制和是否已达到使用上限
使用自己的 API KeyAPI 组织、项目、模型的 RPM、TPM、余额、usage limit 和 spend limitAPI 控制台的 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。

从日志到后台:一套可执行的排查顺序

  1. 确认请求入口。记录 Codex CLI 使用的是 ChatGPT 登录、OpenAI API,还是第三方兼容网关;同时记录 Base URL、模型和项目标识。
  2. 读取错误体。先看 error.code,再看 error.type、message、HTTP 状态和 request ID。不要只用 message 文本判断。
  3. 查看 Limits。API Key 路径下,打开组织或项目的 Limits,核对模型的 RPM、TPM 和项目级 token 限制。OpenAI 文档说明,限制可能同时按组织和项目生效,也可能因模型不同而不同。
  4. 查看 Usage。检查是否有多个 Codex 进程、自动化脚本、队列、定时任务或 CI 工作流共享同一组织和项目。
  5. 查看 Billing 与 spend limit。遇到 credit_balance_exhausted、organization_usage_limit_exceeded、organization_spend_limit_exceeded 或 project_spend_limit_exceeded,先处理余额、批准额度或组织/项目上限,再测试。
  6. 最后查看 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 的工程化做法

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 官方错误码。

这些状态码不要用同一套重试逻辑

Codex/API 常见错误的第一动作
状态或错误第一动作
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 区分开来;两者都可能需要等待,但触发原因不同。对任何状态码,都应先读取错误体再决定是否重试。

最后检查清单:五分钟内确认是哪一层的问题

  1. 错误体里是否有明确的 error.code?
  2. 请求是 ChatGPT 登录路径、OpenAI API Key,还是第三方兼容网关?
  3. 是否同时检查了实际组织、项目、模型对应的 Limits、Usage 和 Billing?
  4. 是否读取了 Retry-After、剩余请求数、剩余 token 和 reset 头?
  5. 是否存在多个 Codex 进程、CI、定时任务或队列消费者同时发请求?
  6. 应用层重试时,是否关闭了 SDK 自带重试,或已经把两层预算合并计算?
  7. 如果是 CLI 的 exceeded retry limit,是否核对了当前版本的 provider 配置和重试字段?
  8. 如果多个请求同时异常,是否查看了当前 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、网关日志、错误体和响应头;网关可能有独立并发、余额和限流规则,也可能不透传上游字段,最终以该服务商文档和可核验日志为准。

相关阅读

参考资料

  1. OpenAI API Docs:Error codes
  2. OpenAI API Docs:Rate limits
  3. OpenAI Help Center:Using Codex with your ChatGPT plan
  4. OpenAI Help Center:ChatGPT Work and Codex
  5. OpenAI Codex GitHub:配置结构源码
  6. OpenAI Codex GitHub:Provider 重试配置源码
  7. OpenAI Codex GitHub Issue:HTTP 429 与重试限制