Skip to main content
Codex CLI speaks the OpenAI Responses API and nothing else, and it builds its request URL by appending /responses to the base_url you configure — it never inserts a /v1 of its own. Relay serves it a dedicated base, https://your-relay/codex/v1, so a turn lands on POST /codex/v1/responses. Relay translates the Responses shape into whatever the routed upstream speaks, so Codex is not limited to OpenAI models — any model your relay key’s policy grants works, including Anthropic, Gemini and a self-hosted Ollama build.

Setup

Add a provider block to ~/.codex/config.toml (CODEX_HOME relocates that directory) and point the top-level model_provider at it:
Then RELAY_API_KEY=sk-wr-your-relay-key codex. The key goes out as Authorization: Bearer, which is how Relay expects a relay key. Three constraints come from Codex itself. The provider id cannot be openai, ollama, lmstudio or amazon-bedrock* — those are reserved and the built-in definition wins. The name must be neither OpenAI nor azure, both of which switch Codex into vendor-specific behaviour. And wire_api must be responses; "chat" is rejected at config load, which breaks every codex invocation until you remove it.

Model names

Codex resolves the model string against a catalog compiled into its binary, and falls back to synthesised metadata with a warning for anything it does not recognise — so any Relay model name works as-is. Naming a model with a known prefix inherits that model’s capability flags, which is occasionally what you want and otherwise harmless. model_context_window can only lower a recognised model’s window, never raise it: Codex clamps the value against its own catalog maximum.

Model picker

Codex’s /model picker lists a catalog compiled into its binary. For a provider authenticated with env_key it never asks the gateway for a list, so the only way in is model_catalog_json — and that file replaces the bundled catalog wholesale, including the agent prompt Codex sends with every turn, which the catalog is where Codex keeps. A picker file therefore always comes with a prompt file; the two are one step, not a file and an option.
Match the tag in that second URL to your own codex --version — the prompt ships with the binary and changes between releases. Both are top-level keys, so they must sit above the first [table] header in config.toml — above [model_providers.relay], [projects."…"], [profiles.…], all of them. TOML assigns every key after a header to that table, so the same two lines appended to the end of the file silently become projects."…".model_catalog_json and are ignored: the picker keeps showing Codex’s bundled models and nothing is logged. Verify placement with codex exec --strict-config -m <model> "ok". Strict mode rejects a key Codex does not recognise and prints its file, line and full dotted path, which is the only signal you get — without it a misplaced key is dropped in silence. Once the keys are genuinely top-level, a missing or empty prompt file is loud: Codex refuses to start and names the file. The catalog file on its own is not — every model listed in it would run with no agent prompt at all, and nothing says so. Both values are paths, never URLs — Codex reads them off disk at startup. A leading ~ expands to your home directory, and a relative path resolves against the directory of the config file it appears in ($CODEX_HOME, ~/.codex by default), so model_catalog_json = "relay-models.json" picks up ~/.codex/relay-models.json as well. model_catalog_json is also accepted inside a [profiles.<name>] block, and it is applied at startup only. The document lists exactly the models your relay key’s policy grants, each with the catalog’s context window, its hosts and their per-Mtok rates as the picker subtitle, and reasoning-effort options for models the catalog marks as reasoning. It is a snapshot: re-run the curl after a policy or catalog change. If you would rather hand-write the file, this is a complete minimal entry — every key shown is required except context_window, and Codex rejects a document with no models:
visibility: "list" is what puts a row in the picker, supported_in_api: true is what keeps it there for a key-authenticated provider, and the list is ordered by ascending priority. context_window is optional and omitted when the catalog declares none; leaving max_context_window out keeps your model_context_window override free, since Codex clamps that value against the catalog maximum when one is present. Codex has no field for a maximum output-token count, so Relay does not project one. Unknown keys are ignored, and a model slug absent from the file still routes — Codex synthesises fallback metadata for it with a warning.

Per-role models

[profiles.<name>] blocks and the -p/--profile <name> file layer ($CODEX_HOME/<name>.config.toml) both carry their own model and model_provider, and agent-role files do too — so one Relay can serve a cheap review model and an expensive build model from a single config.

What you get

  • Usage — every turn appears in GET /logs and the /usage/* endpoints, attributed to your relay key, with token counts and timing.
  • Payloads — with payload logging enabled, the full request and response bodies are readable at GET /logs/{request_id}.
  • Attribution — the client’s session-id, thread-id, x-client-request-id, x-codex-installation-id, x-codex-window-id, x-codex-parent-thread-id, x-openai-subagent and originator headers are recorded on the usage event as extras, alongside client=codex. That is what lets you cost one session, or one sub-agent turn inside it.
See Usage & Logging for reading it back.

Known limits

  • Hosted tools do not cross to other vendors. Codex’s apply_patch (a custom tool) and its namespace groupings are translated for non-OpenAI upstreams — the model sees a function taking one input string and Codex receives a normal custom_tool_call back — but web_search and other server-side tools have no equivalent elsewhere and are dropped on such routes. Every tool on an OpenAI upstream is forwarded as sent.
  • The picker’s prompt file is Codex’s generic one. model_instructions_file applies a single prompt to every model, where Codex’s bundled catalog carries a per-model instruction template for each OpenAI model it ships. It also drifts with Codex releases, so re-fetch it at the matching tag when you upgrade.
  • Codex does not retry a 429 and ignores Retry-After. Its only backoff hint is a try again in Ns phrase scraped out of the error message text, so a rate-limited turn surfaces to you rather than resolving itself.
  • A signed-in ChatGPT account overrides base_url. If Codex is logged in, requests can bypass the gateway entirely and reach no Relay; run codex logout first.
  • Use 127.0.0.1, not localhost, in a local base_url — Codex’s name resolution fails on the latter.