OpenAI-compatible providers: non-standard /v1/models key + no Responses API → workaround with codex-relay + stream:false
OpenAI-compatible providers with non-standard /v1/models + no Responses API support
Two-part problem
Any provider that (1) returns {"data":[...]} from /v1/models instead of {"models":[...]} and (2) only exposes Chat Completions, not the Responses API, cannot be used with Codex CLI out of the box. OneProvider (api.oneprovider.dev) is a concrete example, but the pattern applies broadly.
---
Problem 1 — Wrong /v1/models response key
Expected (OpenAI standard):
{ "object": "list", "models": [...] }
Actual (OneProvider and others):
{ "data": [...], "object": "list" }
Codex CLI fails to initialise when model_catalog_json is set, or silently cannot discover models:
Error loading configuration: failed to parse model_catalog_json path … as JSON: invalid type: map, expected a sequence
Provider support confirmed the mismatch is a known bug on their side, not yet fixed, and noted it affects any OpenAI-compatible provider, not just theirs.
---
Problem 2 — Provider only exposes Chat Completions, not Responses API
Codex CLI with wire_api = "responses" calls /v1/responses.
OneProvider (and many third-party proxies / cheaper API resellers) only implement /v1/chat/completions.
Direct curl to /v1/responses → immediate timeout (HTTP 000).
Streaming Chat Completions → first chunk arrives, then hangs (Caddy 120 s hard timeout on provider side).
---
Working solution: codex-relay + stream: false
codex-relay is a Rust bridge that translates Codex's Responses API calls into Chat Completions calls. The critical extra step for providers with streaming issues is forcing non-streaming via --upstream-extra-params.
1. Install codex-relay
cargo install codex-relay
# or: pip install codex-relay
2. Create a wrapper script
~/.local/bin/oneprovider-relay.sh:
#!/bin/bash
API_KEY=$(security find-generic-password -s 'codex-oneprovider-api-key' -w 2>/dev/null)
exec codex-relay \
--upstream https://api.oneprovider.dev/v1 \
--api-key "$API_KEY" \
--port 8446 \
--upstream-extra-params '{"stream": false}'
chmod +x ~/.local/bin/oneprovider-relay.sh
Why stream: false? Without it, codex-relay sends a streaming Chat Completions request to the upstream. Providers with a hard proxy timeout (e.g. Caddy 120 s) deliver the first SSE chunk and then hang, causing a timeout. Forcing non-streaming returns a single JSON response that completes well within the timeout window (~8-16 s in testing).
3. macOS auto-start (launchd)
~/Library/LaunchAgents/com.user.oneprovider-proxy.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.oneprovider-proxy</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/oneprovider-relay.sh</string>
</array>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key>
<string>/tmp/oneprovider-relay.log</string>
<key>StandardErrorPath</key>
<string>/tmp/oneprovider-relay-error.log</string>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/com.user.oneprovider-proxy.plist
4. Codex profile
~/.codex/oneprovider.config.toml (or codex1.config.toml):
model = "gpt-5.6-sol"
model_provider = "oneprovider"
model_reasoning_effort = "max"
[model_providers.oneprovider]
name = "OneProvider"
base_url = "http://127.0.0.1:8446/v1" # relay, not the provider directly
wire_api = "responses"
[model_providers.oneprovider.auth]
command = "/usr/bin/security"
args = ["find-generic-password", "-s", "codex-oneprovider-api-key", "-w"]
timeout_ms = 5000
refresh_interval_ms = 0
codex --profile oneprovider
---
Verified test results
GET /v1/models → HTTP 200 ~0.4 s ✅ {"object":"list","models":[19 entries]}
POST /v1/responses → HTTP 200 ~8-16 s ✅ {"output":[{"content":[{"text":"OK"}]}]}
(Latency is the provider's own, not the relay's overhead.)
---
Suggested Codex-side fix
A small built-in compatibility shim would let providers like this work without any local relay:
[model_providers.oneprovider]
models_list_key = "data" # fall back if "models" key is absent
Or Codex could silently accept both {"models":[...]} and {"data":[...]} when the root object is "list".
---
Related
- #14743 — Responses API format issues with custom providers
- #987 — Can't use other provider model
codex-relay— the relay used in this workaround
2 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
The /v1/models response shape and the Responses-versus-Chat split are compatibility checks I\x27d keep explicit when evaluating a hosted endpoint. A provider can be reachable yet still fail at discovery or request translation. Disclosure: I\x27m building Your Model, an OpenAI-compatible multi-model API platform for developers. I can add some test credits for a side-by-side compatibility check.