SSSAiCode

Codex 怎么配置自定义 API endpoint

更新于 2026-10-21 · sssaicodeapi.cyou

简答:自定义 API endpoint 写在用户级 config.toml,不要写进仓库。网关自己发 Key 时用独立 provider:base_url 停在 /v1,wire_api 用 responses 或省略,Key 只放进 env_key。仍弹登录、401 或 404 时,先看 provider 是否仍走官方登录,变量是否进入启动进程,以及 URL 是否为 /v1/responses。

定义:Codex 自定义 API endpoint 是把模型请求改到指定的 Responses 兼容地址,相关字段写在用户级 config.toml 的 provider 上,而不是项目配置。

先贴最小配置,再选定一条路线

网关自己发 Key 时,用下面这份独立 provider。代理仍要 ChatGPT 登录或官方 API Key 时,不要套这份,改用后文的 openai_base_url。两条同时写,请求和凭证会对不上。

先运行 codex --version。截至 2026-10-21,Codex 模型说明写明现行版本不支持 wire_api = "chat",也不支持只有 Chat Completions 的端点。模型名保存前从网关当前列表,或该版本认识的目录里复制,不要保留尖括号。

model = "<MODEL_ID>"
model_provider = "my-gateway"

[model_providers.my-gateway]
name = "My gateway"
base_url = "https://gateway.example.com/v1"
env_key = "CODEX_GATEWAY_API_KEY"
wire_api = "responses"

model 和 model_provider 必须写在第一个表头之前。官方网关接入说明写明,写到表头后面的键属于该表,不会被当成顶层设置。my-gateway 在 model_provider、表名,以及若使用时的 auth 表名里必须相同。标识不能用保留名 openai、ollama、lmstudio,高级配置写明这三个内置标识不能被自定义表覆盖。

三条路线:谁收请求,谁发凭证
路线谁收请求谁发凭证什么时候用
openai_base_url内置 openai,只换地址ChatGPT 登录或官方 API Key代理仍要官方认证
独立 provider 加 env_key你的网关网关 Key,来自环境变量自定义 endpoint 用这条
独立 provider 加 auth 命令你的网关本机命令打印的短期 token管理员已安装凭证助手

只改内置 OpenAI 地址时,用户级文件里写这一行即可,不要再定义同名 provider:

openai_base_url = "https://proxy.example.com/v1"

高级配置把 openai_base_url 用于内置 openai。它和上面的独立 provider 是两条路线,不要为了同一个 endpoint 各写一遍。

配置写在用户目录,改完重启客户端

结论:provider 和 endpoint 写在用户目录,或用户目录里的 profile 文件。写进仓库的 .codex/config.toml 不会改变 endpoint。

macOS 与 Linux 的用户级文件是 ~/.codex/config.toml。Windows 是 %USERPROFILE%\.codex\config.toml。官方网关接入说明写明,WSL 里用 Linux 家目录,除非 CODEX_HOME 指向别处。目录不存在就先建目录。

截至 2026-10-21,配置基础说明把取值优先级写成下面这个顺序,越靠前越优先:

  1. 当次 CLI 标志和 --config。
  2. 已信任项目里的 .codex/config.toml,离当前目录越近越优先。
  3. --profile 选中的 profile 文件,路径是 ~/.codex/profile-name.config.toml。
  4. 用户级 ~/.codex/config.toml。
  5. 已登录工作区下发的云端托管默认值。
  6. 系统配置(Unix 上若存在,为 /etc/codex/config.toml)。
  7. 内置默认值。

同一页把项目层放在 --profile 之前。这只对项目层允许覆盖的键成立。endpoint 是例外:配置参考写明,项目级文件会忽略 openai_base_url、chatgpt_base_url、model_provider、model_providers 等键。所以自定义地址仍然只来自当次 --config、已选中的 profile 文件或用户级文件。项目文件里写了 base_url,看起来像更优先,实际不会生效。

  1. 运行 codex --version。0.134.0 及以后不要再把配置嵌进 config.toml 的 [profiles.名字]。高级配置要求改成 ~/.codex/名字.config.toml,用 codex --profile 名字 选用,并删掉旧的 profile 表和顶层 profile = "名字"。更早版本的旧表本文未展开。
  2. 把上一节字段合并进已有用户级文件。同一个键不要写第二遍。
  3. CLI 与桌面版读这份用户级文件。官方网关接入说明要求保存后重启应用。终端里 export 的变量,从桌面启动的进程不一定继承。
  4. 启动后在 CLI 执行 /status,确认模型名和 provider 标识是 my-gateway。官方接入说明也把 codex doctor 和 /status 当作连接前检查。

当次试跑可以不改文件:codex --config model_provider='"my-gateway"'。高级配置写明 --config 的值按 TOML 解析,字符串要带引号。这只覆盖这一次,不能代替用户级文件。

base_url 停在 /v1,wire_api 只用 responses

结论:base_url 写成网关文档里的 API 根。官方示例是 https://gateway.example.com/v1,末尾不加斜杠,也不要自己再加 /responses。wire_api 写成 responses,或整行省略。配置参考写明,唯一支持的值就是 responses,省略时默认也是它。

网关兼容要求写明:base URL 为 https://gateway.example.com/v1 时,网关必须接受 POST /v1/responses,并保留客户端使用的请求和响应字段。能通的 Chat Completions 或 Anthropic Messages 接口,不能当成已经兼容 Codex。

先对照网关文档里的完整调用地址,再看访问日志里的最终 URL,不要猜路径:

模型标识同样不要从旧文章抄。网关兼容要求写明:先看 codex --version,再用该版本认识的模型名,或让网关提供配套 catalog。模型自报姓名不能当作路由证据。截至 2026-10-21,Codex 模型说明写明 gpt-5.3-codex-spark 已于 2026-09-14 从 ChatGPT 桌面版、Codex CLI 和 IDE 扩展退役;gpt-5.5 自 2026-10-14 12:00 PDT 起从 ChatGPT 登录的 Codex 退役,API Key 会话不受影响。这两条只说明旧教程里的 ID 会过期。自定义网关仍以该网关当前列表为准。

认证只留一种,Key 不进仓库

结论:自定义网关的 Key 用 env_key。不要和 requires_openai_auth = true、auth 命令或内置登录态叠在一起。env_key 的值是变量名,不是密钥本身。

认证说明把自定义 provider 分成三种:requires_openai_auth = true 时走 OpenAI 认证,此时 Codex 忽略 env_key;只设 env_key 时读取该变量;两者都不设则视为不需要认证,适合本地服务。高级配置另有命令方式:[model_providers.my-gateway.auth] 的命令把 token 打到标准输出。该表不能和 env_key、experimental_bearer_token、requires_openai_auth 同时使用。

配置参考把 experimental_bearer_token 标为不推荐,应改用 env_key。密钥不要写进 config.toml、项目文件或仓库。认证说明写明,文件存储时 ~/.codex/auth.json 里是登录缓存;把它当成密码,不要贴到工单或聊天里。它不是 env_key 那个变量。

环境变量说明列出的稳定变量里没有 OPENAI_BASE_URL,也没有一个固定的网关 Key 变量名。变量名由你在 env_key 里指定。官方网关接入说明写明:只在终端里设置的变量,桌面启动的应用不一定看得到。桌面版要把 CODEX_GATEWAY_API_KEY 放到能随应用进程继承的用户环境,保存后重启应用。CLI 则在启动 codex 的那个 shell 里能读到即可。

网关若要的不是 Authorization: Bearer,而是例如 X-API-Key,就删掉 env_key,改成 env_http_headers = { "X-API-Key" = "CODEX_GATEWAY_API_KEY" }。等号右边仍是变量名。头名以网关文档为准,对不上时本文不替任何产品猜测。

三步验收,脚本保存成文件再跑

结论:GET /v1/models 只检查地址和 Key。网关兼容要求写明,健康检查和模型列表不构成一次 Codex 对话,也不能证明工具可用。按这个顺序验收,一次只看一个结果:

  1. GET /v1/models 返回 200。这只说明地址和 Key 能读到模型列表。
  2. POST /v1/responses 的流里出现结束事件 response.completed,而且事件是陆续到达的,不是最后一次性吐出整段。
  3. 再测工具回路,并让网关管理员在日志里核对用户、模型别名和上游路由。模型自己说自己的名字不算数。

下面的脚本是可选核对,不是配置的一部分。保存成文件后执行,不要粘贴进交互式 shell:交互式里的 exit 会关掉当前终端。脚本结束前会先打印响应正文,临时文件随后由 trap 删除,所以不要等脚本退出再去 jq 那个文件。需要本机已有 curl 和 jq。不用 -v,避免把 Key 打出来。

在运行脚本的终端里先设置地址和模型,Key 由脚本用 read -s 读取,不要写进文件:

export CODEX_GATEWAY_BASE_URL="https://gateway.example.com/v1"
export CODEX_MODEL_ID="从模型列表复制的 ID"

状态码脚本保存为 check-gateway.sh,执行 bash check-gateway.sh。它把正文写入文件再打印,用来看状态码和有没有 response.completed,不能用来看是不是边生成边到达。变量为空时会立刻停在 : 那一行,不会继续发请求。

#!/usr/bin/env bash
# 保存为 check-gateway.sh。不要粘贴进交互式 shell。
set -euo pipefail
: "${CODEX_GATEWAY_BASE_URL:?先设置 CODEX_GATEWAY_BASE_URL}"
: "${CODEX_MODEL_ID:?先设置 CODEX_MODEL_ID}"
if [ -z "${CODEX_GATEWAY_API_KEY:-}" ]; then
  read -r -s -p "Gateway API key: " CODEX_GATEWAY_API_KEY
  printf '\n'
  export CODEX_GATEWAY_API_KEY
fi
base_url="${CODEX_GATEWAY_BASE_URL%/}"
model_id="${CODEX_MODEL_ID}"
models_file=$(mktemp)
responses_file=$(mktemp)
payload_file=$(mktemp)
trap 'rm -f "$models_file" "$responses_file" "$payload_file"' EXIT
models_code=$(curl -sS -o "$models_file" -w "%{http_code}" -H "Authorization: Bearer ${CODEX_GATEWAY_API_KEY}" -H "Accept: application/json" "${base_url}/models")
printf 'models_status %s\n' "$models_code"
printf '%s\n' '--- models body ---'
cat "$models_file"
printf '\n'
if [ "$models_code" != "200" ]; then
  printf '%s\n' 'models request failed; responses request was not sent'
  exit 1
fi
jq -n --arg model "$model_id" '{model:$model,input:"Reply with exactly: gateway-ok",stream:true}' > "$payload_file"
responses_code=$(curl -sS --no-buffer -o "$responses_file" -w "%{http_code}" -H "Authorization: Bearer ${CODEX_GATEWAY_API_KEY}" -H "Content-Type: application/json" -H "Accept: text/event-stream" --data-binary @"$payload_file" "${base_url}/responses")
printf 'responses_status %s\n' "$responses_code"
printf '%s\n' '--- responses body ---'
cat "$responses_file"
printf '\n'
if grep -q 'response.completed' "$responses_file"; then
  printf '%s\n' 'found response.completed'
else
  printf '%s\n' 'response.completed not found'
fi

看增量流式时,另存 stream-gateway.sh。这条 curl 使用 -N,没有 -o,data: 行直接出现在终端。一次性出现整段、没有 response.completed,不算流式通过。

#!/usr/bin/env bash
# 保存为 stream-gateway.sh。输出直接打到终端。
set -euo pipefail
: "${CODEX_GATEWAY_BASE_URL:?先设置 CODEX_GATEWAY_BASE_URL}"
: "${CODEX_MODEL_ID:?先设置 CODEX_MODEL_ID}"
if [ -z "${CODEX_GATEWAY_API_KEY:-}" ]; then
  read -r -s -p "Gateway API key: " CODEX_GATEWAY_API_KEY
  printf '\n'
  export CODEX_GATEWAY_API_KEY
fi
base_url="${CODEX_GATEWAY_BASE_URL%/}"
payload_file=$(mktemp)
trap 'rm -f "$payload_file"' EXIT
jq -n --arg model "$CODEX_MODEL_ID" '{model:$model,input:"Reply with exactly: gateway-ok",stream:true}' > "$payload_file"
curl -sS -N -H "Authorization: Bearer ${CODEX_GATEWAY_API_KEY}" -H "Content-Type: application/json" -H "Accept: text/event-stream" --data-binary @"$payload_file" "${base_url}/responses"

Windows 先在同一个窗口设置这两个变量,再运行后面的脚本。任一为空时,脚本会在 TrimEnd 之前停止:

$env:CODEX_GATEWAY_BASE_URL = 'https://gateway.example.com/v1'
$env:CODEX_MODEL_ID = '从模型列表复制的 ID'

把下一段保存为 check-gateway.ps1。Key 用 Read-Host -AsSecureString 读取,请求体先转成 UTF-8 字节,错误体按 UTF-8 读取,用过的非托管字符串会释放。两个请求放在 foreach 里,模型列表失败就 break。这段按 Windows PowerShell 5.1 的 WebException.Response 来写;PowerShell 7 的异常不一定有同名 Response,那时改看 $_.ErrorDetails.Message。本文没有在 5.1 或 7.x 上代跑。Invoke-WebRequest 会等正文结束后再打印,不能判断增量流式。

$bstr = [IntPtr]::Zero
$key = $null
if ([string]::IsNullOrWhiteSpace($env:CODEX_GATEWAY_BASE_URL)) {
  throw '先设置 $env:CODEX_GATEWAY_BASE_URL'
}
if ([string]::IsNullOrWhiteSpace($env:CODEX_MODEL_ID)) {
  throw '先设置 $env:CODEX_MODEL_ID'
}
$baseUrl = $env:CODEX_GATEWAY_BASE_URL.TrimEnd('/')
$modelId = $env:CODEX_MODEL_ID
$secure = Read-Host -AsSecureString -Prompt 'Gateway API key'
try {
  $bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
  $key = [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr)
  $headers = @{ Authorization = "Bearer $key" }
  $json = (@{ model = $modelId; input = 'Reply with exactly: gateway-ok'; stream = $true } | ConvertTo-Json -Compress)
  $bodyBytes = [Text.Encoding]::UTF8.GetBytes($json)
  foreach ($step in @('models','responses')) {
    try {
      if ($step -eq 'models') {
        $resp = Invoke-WebRequest -Uri ($baseUrl + '/models') -Headers $headers -Method Get -UseBasicParsing
      } else {
        $resp = Invoke-WebRequest -Uri ($baseUrl + '/responses') -Headers $headers -Method Post -Body $bodyBytes -ContentType 'application/json; charset=utf-8' -UseBasicParsing
      }
      Write-Output $resp.StatusCode
      Write-Output $resp.Content
    } catch {
      $errResp = $_.Exception.Response
      if ($null -eq $errResp) { throw }
      Write-Output ([int]$errResp.StatusCode)
      $reader = New-Object System.IO.StreamReader($errResp.GetResponseStream(), [Text.Encoding]::UTF8)
      try { Write-Output $reader.ReadToEnd() } finally { $reader.Dispose() }
      break
    }
  }
} finally {
  if ($bstr -ne [IntPtr]::Zero) {
    [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
  }
  $key = $null
}

Windows 上看增量流式,用 curl.exe -N,不要加 -o,输出直接留在终端。不要用 PowerShell 的 curl 别名,那个别名指向 Invoke-WebRequest。

两条请求都成功后,在 CLI 新开会话输入官方接入说明里的那句 Reply with exactly: gateway-ok,并用 /status 核对 provider。文本成功仍不够。网关兼容要求的工具回路是:Codex 收到 function call,在本地执行,再提交对应的 function_call_output,然后得到最终回答。自查时用一句无敏感数据的请求,例如让 Codex 在本地执行 printf gateway-ok,再看网关是否同时记下调用和结果。只出现一句回复,就还没验证工具。

流中断时,网关兼容要求先检查代理是否缓冲了 SSE,以及有没有把失败事件和 response.completed 原样转发。截至 2026-10-21,配置参考写明 request_max_retries 默认 4,stream_max_retries 默认 5,stream_idle_timeout_ms 默认 300000。先改缓冲,不要先把重试调大。

仍弹登录、401、404 时先改哪一处

按这个顺序查,一次只改一处,改完重启桌面版或重开 CLI。

症状和先改的那一处
看到的现象先核对改法
仍弹出 ChatGPT 登录model_provider 是否仍是默认 openai,或 requires_openai_auth 是否为 true独立 Key 只保留 env_key,并确认 /status 里的 provider 标识一致
401 或认证失败启动进程里有没有 env_key 那个变量;有没有和 auth 命令或官方登录态叠用变量设到实际启动 Codex 的环境;认证方式只留一种
404网关日志里的最终 URL 是不是 /v1/responsesbase_url 停在 /v1,不要自带 /responses 或 /chat/completions
模型不存在model 是否从当前网关列表复制换成该列表中的 ID;有 catalog 时与管理员核对别名路由
配置像没保存字段是否写在项目 .codex/config.toml,或写在表头之后移到用户级文件,顶层键放在第一个表头之前
流式中途停住中间代理是否缓冲了 SSE先让 response.completed 增量到达,再谈重试次数

401 时不要打开 auth.json 来对比密钥。那份文件是官方登录缓存。自定义网关这条路线看的是环境变量 CODEX_GATEWAY_API_KEY 在进程里有没有值。桌面版读不到终端变量时,现象同样可以是认证失败:把变量放到用户环境后重启应用。

404 先区分两层:/v1/models 成功而 /v1/responses 返回 404,说明 Key 能用,但 Responses 路径或协议还没按网关兼容要求实现;两个都 404,先检查 base_url 是否多写或少写了 /v1,并以网关访问日志里的最终 URL 为准。网关把所有错误都收成 500 时,客户端无法区分认证、未知模型和上游失败。这要网关侧保留区别,不是再改一遍本地字段能解决的。

本文未核实的范围

本页为第三方接入说明,不是 OpenAI 官方文档。

常见问题

只设置 OPENAI_BASE_URL,为什么自定义 endpoint 不生效?

截至 2026-10-21 能核对到的稳定环境变量列表里没有 OPENAI_BASE_URL。要改内置 openai 的地址,写用户级 config.toml 的 openai_base_url。网关使用自己的 Key 时,改为独立 provider 的 base_url 和 env_key。

项目里的 .codex/config.toml 已经写了 base_url,为什么还是官方地址?

项目级文件会忽略 openai_base_url、model_provider 和 model_providers。把这些字段移到 ~/.codex/config.toml;Windows 则是 %USERPROFILE%\.codex\config.toml。保存后重启客户端,再用 /status 核对 provider。

网关只有 /v1/chat/completions,把 wire_api 改成 chat 可以吗?

不可以。wire_api 唯一支持的值是 responses,省略时默认也是它。现行 Codex 不支持 wire_api = chat,也不支持只有 Chat Completions 的端点。需要网关接受 POST /v1/responses。

终端里能用,桌面版仍报认证失败,是不是 Key 错了?

不一定。只在终端设置的变量,桌面启动的应用不一定继承。确认 env_key 写的是变量名,把该变量设到用户环境,重启桌面版,并用 /status 确认 provider 不是默认 openai。

GET /v1/models 返回 200,能否说明 Codex 已经接好?

不能。模型列表不构成对话,也不证明流式和工具。还需要一条带模型 ID 的 POST /v1/responses,在终端里陆续看到 response.completed,并在网关日志里核对这次请求落在目标账户和目标模型上。

参考资料

  1. Use API/provider credentials through a gateway
  2. Gateway compatibility requirements
  3. Configuration Reference
  4. Advanced Configuration
  5. Config basics
  6. Authentication
  7. Models
  8. Environment variables