Area
Proxy and routing
What are you trying to accomplish?
Run one opencodex instance centrally and let Codex clients on other machines see the routed model picker, without any client holding provider credentials. The server owns the providers and OAuth accounts; each client should only need base_url, the admission token, and the model catalog.
Everything except the catalog already works with the setup recommended in #95: a reverse proxy terminates TLS, injects x-opencodex-api-key, and forwards Authorization untouched, so ChatGPT passthrough keeps working per machine. Routed models are reachable by exact id (codex -m anthropic/claude-opus-5). What is missing is a supported way for a client to obtain the catalog that makes those models appear in the picker.
What prevents this today?
Codex builds its picker from files in the client's CODEX_HOME, not from a call to the proxy. The proxy already generates exactly those files on the server:
[opencodex] Codex runtime: /usr/local/bin/codex (version=0.146.0, source=configured)
+ 2 models appended to Codex catalog (/home/node/.opencodex/codex-home/opencodex-catalog.json)
There is no HTTP route that returns them. I went through the /api/* surface in 2.7.42 and there is no catalog route.
GET /v1/models is not a substitute. It returns identifiers only:
{ "id": "fakeprov/cool-model", "object": "model", "created": 0, "owned_by": "openai" }
A generated catalog entry carries roughly 29 fields, including base_instructions, comp_hash, context_window, max_context_window, supported_reasoning_levels, truncation_policy, and input_modalities. Reconstructing that client-side would mean reimplementing the catalog builder, and it still could not be done without provider credentials.
Running ocx on the client does not help either, and quietly makes things worse. On a credential-less client, ocx sync rebuilds routed entries from the local provider list and therefore deletes them. Measured in an isolated CODEX_HOME/OPENCODEX_HOME:
routed entries in catalog → ["fakeprov/cool-model"]
routed entries after a provider-less ocx sync → []
The injector itself works well remotely — with hostname pointing at the server, ocx sync writes a correct provider table including env_http_headers — but injection and catalog rebuild are bundled in every command that exposes them (sync, init, ensure, restore back), so the injector cannot be used on its own. ocx ensure additionally tries to start a local proxy, which is wrong on a client.
The net effect is that every operator who follows the #95 recommendation has to build their own file distribution mechanism, which is also what the caveat in that thread currently advises.
What should OpenCodex do?
Expose the catalog the proxy already generates through the existing token-authenticated management API:
GET /api/catalog → the current opencodex-catalog.json content
Subject to the same admission rules as the rest of /api/*, so on a non-loopback bind it requires x-opencodex-api-key or Authorization: Bearer and nothing new is opened up.
It would help if the response let a client detect Codex version skew, since the catalog embeds base_instructions and comp_hash from the Codex build on the server, and a client running a different Codex version can end up with subtly wrong entries. A response header or a small JSON envelope would both work; I have no preference on the shape.
A second route for the stale-stamped models_cache.json variant would be convenient but is not strictly required, because ocx sync-cache already rebuilds it locally from the on-disk catalog without touching routed entries. Measured on the same isolated setup:
after sync-cache (no providers): ["fakeprov/cool-model"]
models_cache stamp: 2000-01-01T00:00:00Z 0.0.0 1 routed
So clients that have ocx installed need only the single catalog route. Clients that only have Codex would benefit from the second one.
This is deliberately read-only exposure of a file the proxy already writes. It does not touch tenant identity, per-user authorization, or attribution — the parts described as project-scale work in #95.
Example usage or interface
On a client machine, after the one-time config injection:
curl -fsS -H "x-opencodex-api-key: $OPENCODEX_API_AUTH_TOKEN" \
https://proxy.example.com/api/catalog > "${CODEX_HOME:-$HOME/.codex}/opencodex-catalog.json"
ocx sync-cache
The client config.toml is unchanged from what non-loopback injection already produces, except that a reverse proxy allows an https base URL:
model_provider = "opencodex"
model_catalog_json = "/home/me/.codex/opencodex-catalog.json"
[model_providers.opencodex]
name = "OpenCodex Proxy"
base_url = "https://proxy.example.com/v1"
wire_api = "responses"
requires_openai_auth = true
env_http_headers = { "x-opencodex-api-key" = "OPENCODEX_API_AUTH_TOKEN" }
Before and after, for an operator adding a provider on the server:
today add provider in the dashboard → resync → copy opencodex-catalog.json out of the
server by whatever mechanism the operator built → distribute to every client
after add provider in the dashboard → resync → each client runs one authenticated GET
Alternatives or workarounds
What I run today: a static file server sidecar that mounts the catalog directory read-only and is exposed under a path prefix on the same reverse-proxy host, plus a small client script that fetches opencodex-catalog.json and models_cache.json. It works, and it is a reasonable thing for an operator to build, but it is a second service to run and it is re-invented independently by everyone who hits this.
scp/rsync from the proxy host also works, but it requires SSH access to the server from every client machine, which is a much larger grant than an already-issued admission token.
Client-side ocx does not work, for the reasons measured above.
One small note in case it is useful: the guidance in #95 mentions that clients "can fetch it from the proxy's /v1/models". In practice that response carries identifiers only, so a catalog file is still required. Mentioning it only because that sentence is the natural first thing an operator tries.
Additional context
Measured on 2.7.42 running in Docker, with the Codex CLI installed in the image as the catalog source. Happy to open a PR against dev if the endpoint shape is agreed.
Checks
Area
Proxy and routing
What are you trying to accomplish?
Run one opencodex instance centrally and let Codex clients on other machines see the routed model picker, without any client holding provider credentials. The server owns the providers and OAuth accounts; each client should only need
base_url, the admission token, and the model catalog.Everything except the catalog already works with the setup recommended in #95: a reverse proxy terminates TLS, injects
x-opencodex-api-key, and forwardsAuthorizationuntouched, so ChatGPT passthrough keeps working per machine. Routed models are reachable by exact id (codex -m anthropic/claude-opus-5). What is missing is a supported way for a client to obtain the catalog that makes those models appear in the picker.What prevents this today?
Codex builds its picker from files in the client's
CODEX_HOME, not from a call to the proxy. The proxy already generates exactly those files on the server:There is no HTTP route that returns them. I went through the
/api/*surface in 2.7.42 and there is no catalog route.GET /v1/modelsis not a substitute. It returns identifiers only:{ "id": "fakeprov/cool-model", "object": "model", "created": 0, "owned_by": "openai" }A generated catalog entry carries roughly 29 fields, including
base_instructions,comp_hash,context_window,max_context_window,supported_reasoning_levels,truncation_policy, andinput_modalities. Reconstructing that client-side would mean reimplementing the catalog builder, and it still could not be done without provider credentials.Running
ocxon the client does not help either, and quietly makes things worse. On a credential-less client,ocx syncrebuilds routed entries from the local provider list and therefore deletes them. Measured in an isolatedCODEX_HOME/OPENCODEX_HOME:The injector itself works well remotely — with
hostnamepointing at the server,ocx syncwrites a correct provider table includingenv_http_headers— but injection and catalog rebuild are bundled in every command that exposes them (sync,init,ensure,restore back), so the injector cannot be used on its own.ocx ensureadditionally tries to start a local proxy, which is wrong on a client.The net effect is that every operator who follows the #95 recommendation has to build their own file distribution mechanism, which is also what the caveat in that thread currently advises.
What should OpenCodex do?
Expose the catalog the proxy already generates through the existing token-authenticated management API:
Subject to the same admission rules as the rest of
/api/*, so on a non-loopback bind it requiresx-opencodex-api-keyorAuthorization: Bearerand nothing new is opened up.It would help if the response let a client detect Codex version skew, since the catalog embeds
base_instructionsandcomp_hashfrom the Codex build on the server, and a client running a different Codex version can end up with subtly wrong entries. A response header or a small JSON envelope would both work; I have no preference on the shape.A second route for the stale-stamped
models_cache.jsonvariant would be convenient but is not strictly required, becauseocx sync-cachealready rebuilds it locally from the on-disk catalog without touching routed entries. Measured on the same isolated setup:So clients that have
ocxinstalled need only the single catalog route. Clients that only have Codex would benefit from the second one.This is deliberately read-only exposure of a file the proxy already writes. It does not touch tenant identity, per-user authorization, or attribution — the parts described as project-scale work in #95.
Example usage or interface
On a client machine, after the one-time config injection:
The client
config.tomlis unchanged from what non-loopback injection already produces, except that a reverse proxy allows anhttpsbase URL:Before and after, for an operator adding a provider on the server:
Alternatives or workarounds
What I run today: a static file server sidecar that mounts the catalog directory read-only and is exposed under a path prefix on the same reverse-proxy host, plus a small client script that fetches
opencodex-catalog.jsonandmodels_cache.json. It works, and it is a reasonable thing for an operator to build, but it is a second service to run and it is re-invented independently by everyone who hits this.scp/rsyncfrom the proxy host also works, but it requires SSH access to the server from every client machine, which is a much larger grant than an already-issued admission token.Client-side
ocxdoes not work, for the reasons measured above.One small note in case it is useful: the guidance in #95 mentions that clients "can fetch it from the proxy's
/v1/models". In practice that response carries identifiers only, so a catalog file is still required. Mentioning it only because that sentence is the natural first thing an operator tries.Additional context
app-serverkeeps its previous model list until restarted. This request does not change that.Measured on 2.7.42 running in Docker, with the Codex CLI installed in the image as the catalog source. Happy to open a PR against
devif the endpoint shape is agreed.Checks