Proxy API Formats
opencodex presents one local proxy in several client dialects. A Codex client can speak the Responses API, an OpenAI-compatible app can speak Chat Completions, and Claude Code can speak Anthropic Messages without requiring every upstream provider to implement every format.
The normal translation path is:
client dialect → internal Responses model → provider adapter → provider wire formatprovider events → internal adapter events → client dialectThe Responses representation is the center of the bridge. Native-compatible routes may skip parts of the translation and pass a request through, but authentication, routing, admission control, and response safety still happen at the proxy boundary. Configure the listener and admission keys in Configuration; use Combos when one public model id should select among several targets.
Endpoint overview
Section titled “Endpoint overview”| Client surface | Endpoint | Successful non-stream result | Successful stream or socket result |
|---|---|---|---|
| OpenAI Responses | POST /v1/responses |
Responses JSON | Responses SSE, or Responses JSON text frames over WebSocket |
| OpenAI Chat Completions | POST /v1/chat/completions |
chat.completion JSON |
chat.completion.chunk SSE ending in [DONE] |
| Anthropic Messages | POST /v1/messages |
Anthropic message JSON |
Anthropic Messages SSE |
| Anthropic token count | POST /v1/messages/count_tokens |
{ "input_tokens": number } |
Not applicable |
| Model discovery | GET /v1/models |
One of three catalog contracts | Not applicable |
| Voice and Realtime | POST /v1/live, POST /v1/realtime/calls |
Relayed call-creation response | A separate sideband WebSocket relays frames in both directions |
| Responses compaction | POST /v1/responses/compact |
Replacement-history JSON | Not applicable |
POST /v1/responses
Section titled “POST /v1/responses”This is the native opencodex data-plane shape. The request body must be a JSON object with a
non-empty model. input may be a string or an array of Responses items.
Accepted request fields
Section titled “Accepted request fields”| Area | Accepted shape |
|---|---|
| Model and input | Required non-empty model; optional string input or an item array |
| Message items | user, developer, system, and assistant messages; string content or typed content blocks appropriate to the role |
| Content blocks | Text, input images, input files, output text, refusals, and reasoning summary/text blocks where their parent item permits them |
| Tool history | function_call, function_call_output, custom_tool_call, and custom_tool_call_output items |
| Tools | Function tools plus loose built-in or hosted tool entries; tool_choice accepts auto, none, required, named function/custom choices, hosted choices, or allowed_tools |
| Reasoning | reasoning.effort and reasoning.summary (auto, concise, detailed, or none) |
| Continuation and caching | previous_response_id, store, and prompt_cache_key |
| Generation controls | max_output_tokens, temperature, top_p, stop, presence_penalty, and frequency_penalty |
| Service and execution | stream, service_tier, parallel_tool_calls, instructions, metadata, and user |
| Extended Responses fields | background, include, prompt, text, and truncation are accepted for compatible routes |
Unknown item types are accepted as loose typed items for forward compatibility. Translated adapters handle only the item types they recognize, and may reject a feature their provider cannot represent.
JSON and SSE output
Section titled “JSON and SSE output”With stream: true, the response is text/event-stream. The bridge emits Responses events such as
response.created, output-item and text/tool deltas, and exactly one terminal
response.completed, response.failed, or response.incomplete event. A normal stream ends with
data: [DONE].
With stream: false or no stream, the same adapter events are collected into one Responses JSON
object. Both forms preserve the selected model, output items, terminal status, and usage.
Every terminal Responses usage object includes both detail objects, even when the provider did not report those details:
{ "input_tokens": 0, "output_tokens": 0, "total_tokens": 0, "input_tokens_details": { "cached_tokens": 0 }, "output_tokens_details": { "reasoning_tokens": 0 }}When available, input_tokens_details can also include cache_write_tokens. The always-present
detail objects are a compatibility guarantee for strict Responses clients; zero can mean “not
reported,” not necessarily “the provider performed no such work.”
WebSocket upgrade on the same path
Section titled “WebSocket upgrade on the same path”When websockets is enabled, a client may upgrade /v1/responses instead of opening an HTTP POST.
Authentication and origin admission happen during the WebSocket handshake. They are not repeated
inside each frame.
The client sends JSON text frames:
{ "type": "response.create", "model": "provider/model", "input": "Hello", "tools": [], "generate": true}Everything except type becomes the Responses request body, and the proxy forces streaming for the
turn. A new response.create supersedes and cancels the previous turn on that socket.
response.processed is accepted as a no-op acknowledgement. Unparseable or unrelated frame types
are ignored.
Server frames are JSON text frames. Successful streamed output uses the same JSON payloads that
would appear in SSE data: lines, without the SSE envelope or [DONE]. A non-streaming internal
result is reframed as response.created, zero or more response.output_item.done frames, then a
terminal frame. Errors use this envelope:
{ "type": "error", "status": 502, "error": { "type": "upstream_error", "message": "..." }, "headers": {}}A warmup frame with generate: false does not call an upstream. It returns a synthetic
response.created followed by response.completed, both with an empty response id and no output.
POST /v1/chat/completions
Section titled “POST /v1/chat/completions”This endpoint accepts OpenAI-compatible Chat Completions requests with a required model and a
non-empty messages array. It translates system, user, assistant, and tool messages into internal
Responses items; translates function tools, tool choice, images, reasoning effort, and supported
response formats; runs the normal Responses routing pipeline; then translates the result back.
Non-streaming output has object: "chat.completion". Streaming output uses SSE objects with
object: "chat.completion.chunk", choice deltas, a terminal choice with finish_reason, and
data: [DONE]. Tool-call and usage information are translated back where the source events carry
them.
Because the internal execution path is Responses-based, a provider adapter can impose a narrower feature set. For example, a request feature that cannot be represented by the selected adapter is returned as an error instead of silently changing its meaning.
POST /v1/messages and count_tokens
Section titled “POST /v1/messages and count_tokens”These endpoints speak the Anthropic Messages dialect used by Claude Code and compatible clients. Most requests are translated to Responses, routed normally, then translated back to Anthropic JSON or Anthropic SSE.
Native Anthropic passthrough is eligible only when all of these are true:
- native passthrough has not been disabled in Claude Code configuration;
- the requested model begins with
claudeoranthropic; - the request carries a native Anthropic bearer or
x-api-keycredential; and - no configured alias or model map claims that model id for a routed target.
An eligible request is forwarded in the Anthropic dialect so native beta headers, thinking signatures, and subscription identity remain end to end. Otherwise it takes the Responses round-trip.
POST /v1/messages/count_tokens follows the same model resolution and passthrough decision. A
native-eligible request is forwarded to Anthropic’s count endpoint. Other requests use the local
documented estimate over system content, messages, and tools and return:
{ "input_tokens": 123 }GET /v1/models
Section titled “GET /v1/models”The same route serves three clients that expect incompatible catalog envelopes. Anthropic flavor
wins unless client_version is also present.
| Contract | Trigger | Top-level shape | Model-id behavior |
|---|---|---|---|
| Anthropic model list | anthropic-version header or ?flavor=anthropic, without client_version |
{ "data": [...] } with Anthropic model-info entries |
Claude Code receives readable ids; Desktop can receive its profile-specific alias family |
| Codex catalog | client_version query parameter |
{ "models": [...] } |
Native and routed entries carry the richer Codex catalog fields, visibility, effort, WebSocket, and multi-agent metadata |
| Plain OpenAI list | Neither trigger | { "object": "list", "data": [...] } |
Visible native ids are bare; routed ids are aliases or provider/model |
POST /v1/live and Realtime sideband
Section titled “POST /v1/live and Realtime sideband”POST /v1/live accepts the ChatGPT/Codex App Frameless call-creation surface.
POST /v1/realtime/calls accepts the OpenAI Realtime call-creation surface. opencodex selects an
eligible OpenAI-family route, normalizes the call-creation request for the upstream authentication
mode, and relays the bounded response.
After call creation, clients may join a sideband WebSocket using any supported inbound form:
/v1/live/{callId}/v1/realtime/calls/{callId}/v1/realtime?call_id={callId}
The proxy normalizes the upstream join URL and then transparently relays text and binary frames in both directions. Client protocol headers are preserved while upstream authentication remains proxy-owned.
POST /v1/responses/compact
Section titled “POST /v1/responses/compact”Compaction returns replacement history for clients that need to shorten a long Responses conversation.
| Route type | Behavior |
|---|---|
| Canonical ChatGPT or official OpenAI route | Forwards the request to the native /responses/compact endpoint with the resolved account and model authentication |
| Other routed model | Runs an internal, non-streaming, no-tools compaction turn with a compaction_trigger; requires exactly one synthetic compaction item whose encrypted_content is an ocx1: envelope; decodes that summary into v1 replacement history |
Native compact responses are buffered with a 32 MiB maximum, including responses whose declared
Content-Length already exceeds the limit. The compact-specific failures include:
| Status | Type or code | Meaning |
|---|---|---|
| 400 | invalid_request_error |
Invalid JSON/body shape or missing model |
| 404 | invalid_request_error |
The requested model cannot be routed |
| 499 | client_cancelled |
The client cancelled while forwarding or buffering |
| 502 | compact_response_too_large |
Native compact output exceeded 32 MiB |
| 502 | upstream_error |
Connection, read, or synthetic compaction turn failure |
| 502 | invalid_response_error |
The synthetic turn did not produce exactly one valid, non-empty ocx1: compaction item |
Authentication matrix
Section titled “Authentication matrix”On a loopback-only bind, data-plane admission does not require a configured key. On a remote bind,
use the matrix below. “Dedicated” means X-OpenCodex-API-Key; the other columns mean
Authorization: Bearer ... and x-api-key.
| Surface | Dedicated | Bearer | x-api-key |
|---|---|---|---|
/v1/responses HTTP and WebSocket |
Required | Rejected for proxy admission | Rejected |
/v1/responses/compact |
Required | Rejected for proxy admission | Rejected |
/v1/chat/completions |
Required | Rejected for proxy admission | Rejected |
/v1/messages and /v1/messages/count_tokens |
Accepted | Accepted | Accepted |
/v1/models |
Accepted | Accepted | Accepted |
/v1/live, /v1/realtime/calls, and sideband joins |
Accepted | Accepted | Accepted |
Responses-family and Chat requests reserve Authorization for provider or Codex Direct
passthrough, so a remote proxy key must use the dedicated header. Messages and Realtime surfaces
need broader client compatibility and therefore accept all three forms.
Common error vocabulary
Section titled “Common error vocabulary”Errors use the client dialect’s envelope where needed, but these status/code meanings are stable:
| Status | Type or code | Meaning |
|---|---|---|
| 401 | authentication_error |
A required proxy admission credential is missing or invalid |
| 403 | origin_rejected |
A Responses/OpenAI data-plane request or WebSocket upgrade came from a disallowed origin |
| 503 | combo_unavailable |
Every target in the selected combo is unavailable, in cooldown, disabled, or otherwise ineligible |
| 400 | unreadable_encrypted_agent_task |
An encrypted v2 worker task has no eligible native ChatGPT target that can consume it |
| 426 | upgrade_required |
The Responses WebSocket transport is disabled or the upgrade failed; use HTTP |
Anthropic-origin failures are rendered in Anthropic’s error envelope, so the origin rejection is a
403 permission_error on that dialect rather than the OpenAI-style origin_rejected body.
Encrypted-content hygiene
Section titled “Encrypted-content hygiene”The proxy treats genuine backend ciphertext as opaque. Structurally valid ciphertext is preserved byte for byte: opencodex does not decrypt it, translate its contents, or re-encrypt it for another provider.
Some agent hooks have historically placed plaintext control text in an encrypted_content slot.
For compatibility, the proxy separates that plaintext into text parts while retaining any
structurally valid Fernet runs unchanged. If an agent_message loses all encrypted parts during
that repair, it becomes a normal user message. If a current v2 task remains genuinely encrypted
but the selected routed target cannot read native ChatGPT ciphertext, opencodex fails with
unreadable_encrypted_agent_task instead of sending unreadable bytes to that provider. See
Sub-agent Surface for the client behavior around worker tasks.

