Codex CLI and the Responses API: why most relays 404
OpenAI's Codex CLI doesn't call the classic Chat Completions API — it speaks the newer Responses API wire format. Most OpenAI-compatible relays only implement Chat Completions, so Codex pointed at them fails with 404s or malformed-request errors even though the same key works in curl. Router One implements the Responses wire format natively (wire_api = "responses"), so Codex works with a two-minute config change.
The one-line root cause
Codex sends requests to /responses-style routes with a request schema that Chat Completions servers don't recognize. A relay that only speaks Chat Completions answers with 404 (unknown path) or 400 (unknown fields). No amount of key or model fiddling fixes a wire-format mismatch — the gateway itself has to support it.
The working configuration
Point Codex at Router One in ~/.codex/config.toml and export your key in the same shell that runs codex:
# ~/.codex/config.toml model_provider = "router" [model_providers.router] name = "router" base_url = "https://api.router.one/v1" env_key = "ROUTER_ONE_API_KEY" wire_api = "responses" # in your shell: # export ROUTER_ONE_API_KEY=sk-your-router-one-key
Error checklist
If Codex still fails after the config change:
| Symptom | Cause | Fix |
|---|---|---|
| 404 on every request | base_url points at a relay without Responses API support, or has a typo | Set base_url to https://api.router.one/v1 exactly and keep wire_api = "responses". |
| 401 unauthorized | ROUTER_ONE_API_KEY isn't visible in the shell running codex | Export the key in the same session (or your shell profile), then restart the terminal and re-run codex. |
| Model not found | Model ID doesn't match the catalog | Copy the exact ID from the /models page — IDs are case-sensitive. |
| 402 insufficient balance | Wallet or key spend cap exhausted | Top up or raise the key's maxSpend in Dashboard → API Keys. |
| Config seems ignored | Codex reads a different config file than the one you edited | Confirm the file lives at ~/.codex/config.toml (Windows: %USERPROFILE%\.codex\config.toml). |
| 400 mentioning background or image_generation | Codex asked for a background response or the image_generation tool — the two Responses features the gateway does not run | Disable background mode; generate images through POST /v1/images/generations instead. Hosted tools and previous_response_id keep working on natively served Responses models. |
| 400 saying the model must be called via /v1/messages | A Claude-family model ID was sent to the Responses endpoint; Claude is served on the Anthropic-compatible endpoint and on /v1/chat/completions, never on /v1/responses | Switch Codex to a model whose detail page lists POST /v1/responses (the GPT family or DeepSeek V4). To work with Claude, use Claude Code instead. |
| Model not found for codex-auto-review | Codex CLI sends the model ID codex-auto-review for its review line (/review and automatic review) unless review_model in config.toml overrides it; relays that only know their own model list, or only speak Chat Completions, reject the ID | Nothing to change on Router One — codex-auto-review is in the catalog and is served on /v1/responses and /v1/chat/completions; pricing is on /models/codex-auto-review. To review with a different model, set review_model in ~/.codex/config.toml. |
What the Responses endpoint accepts — and the two things it rejects
Router One implements the Responses wire format itself, for the models whose detail page lists POST /v1/responses (the GPT family and DeepSeek V4). The table lists current behavior by request feature; anything accepted is forwarded as sent and billed at the model's standard rate.
| Request feature | Status | Notes |
|---|---|---|
| Function tools (Codex's default tool shape) | Accepted | Structured tool calls come back as first-class Responses output items. |
| Custom tools (Codex's freeform apply_patch and similar) | Accepted on natively served Responses models | Use a model whose page lists POST /v1/responses; other models get 400 invalid_request. |
| previous_response_id / conversation / prompt (server-side context references) | Accepted on natively served Responses models | Same model rule as custom tools; the reference is resolved on the native path. |
| Hosted tools: file_search, code_interpreter, computer_use, mcp, web_search | Accepted on natively served Responses models | Forwarded as sent and billed at the model's standard rate — Router One runs none of them itself. |
| input_file parts with file_id or file_url | Accepted on natively served Responses models | Same model rule; the file reference travels with the request. |
| service_tier (any value) | Accepted | Forwarded as sent; billed at the model's standard rate regardless of tier. |
| stream / instructions / temperature / max_output_tokens | Accepted | Forwarded as sent; see the Responses API reference. |
| image_generation tool and image_generation_call input items | Rejected — 400 invalid_request | Image generation is not offered through the Responses endpoint; use POST /v1/images/generations (see /image-generation-api). |
| background: true | Rejected — 400 invalid_request | No background execution and no GET /v1/responses/{id} to fetch a result later; each request completes within its HTTP connection. |
Verify from the trace
Once requests reach the gateway, every Codex call shows up in Dashboard → Logs with model, tokens, cost, and status. If the log stays empty while Codex errors, the failure is still local — config path or env var. If entries appear with 4xx, the trace names the exact limit.
FAQ
Why does my key work in curl but not in Codex?
curl exercises the Chat Completions surface; Codex uses the Responses API wire format. A relay can pass the curl test and still 404 for Codex. The gateway must implement the Responses wire format — Router One does.
What does wire_api = "responses" actually do?
It tells Codex which protocol shape to speak with the provider block. With "responses", Codex sends Responses-API-formatted requests; the endpoint on the other side has to understand them natively.
Does the base URL need /v1?
Yes — for Codex the OpenAI-compatible base URL is https://api.router.one/v1 including /v1. That's the opposite of Claude Code, whose Anthropic-compatible base URL has no /v1 suffix.
Which models can Codex use through the gateway?
The models served natively over the Responses wire format: the GPT family and DeepSeek V4 (deepseek-v4-pro, deepseek-v4-flash). Use the exact model ID from /models — each model page lists whether it exposes POST /v1/responses. Claude models are not on this endpoint: sending one gets a 400 back before any model is called.
I pointed Codex at a Claude model and got a 400. What now?
The gateway refuses that request before any model runs, and the message spells out where the model lives: model 'anthropic/claude-opus-5' must be called via /v1/messages or /v1/chat/completions. Claude is served on the Anthropic-compatible endpoint (the path Claude Code uses) and on /v1/chat/completions, not on /v1/responses. Point Codex at a GPT-family model or DeepSeek V4, and use Claude Code when you want to work with Claude — see /claude-code-china.
What is the codex-auto-review model Codex keeps requesting?
It is the model ID Codex CLI sends for its review line (/review and automatic review) unless review_model in ~/.codex/config.toml points elsewhere. Relays that validate model names against their own list, or only implement Chat Completions, answer model not found. Router One lists codex-auto-review in the catalog, so the request is routed and billed like any other model — it works on /v1/responses and /v1/chat/completions, and the current price is on /models/codex-auto-review.
Do previous_response_id, hosted tools and service_tier work through the gateway?
Yes, on models served natively over the Responses wire format — the model page's endpoint list shows which. Requests are forwarded as sent and billed at the model's standard rate; only background: true and the image_generation tool are rejected with 400.
Why does Codex get 400 when it tries image generation?
Image generation is not offered through the Responses endpoint, so an image_generation tool or image_generation_call item is refused with 400 invalid_request before any model is called. Generate images with POST /v1/images/generations instead — see /image-generation-api.
Can I run a response in the background?
No. background must be omitted or false; background: true returns 400 invalid_request. The gateway does not run detached responses and has no GET /v1/responses/{id}, so a result cannot be fetched after the connection closes.
Does this work from Mainland China?
Yes. The gateway is reachable from Mainland China without a VPN; the config.toml above is identical everywhere.