Skip to content

Serve the generated Codex model catalog over the management API for remote clients #709

Description

@nbsp1221

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

  • I searched existing issues and documentation.
  • This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
  • I removed secrets and personal data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions