An OpenAI-compatible API implements OpenAI's request and response shapes, minimally chat completions and a models list. Compatibility usually holds for plain text generation and usually frays on tool calling shapes, streaming deltas, structured output, and newer parameters. Clients differ in one detail that causes most failures: OpenAI-shaped clients want the base URL to carry /v1 while Anthropic-shaped clients want the bare origin, because each appends its own path. Continuum serves both surfaces, an OpenAI one at https://continuumcode.ai/v1 and an Anthropic one at the bare origin, which makes it a clean example of the rule.
- The contract is
POST /v1/chat/completionsplusGET /v1/models. Everything else is optional. - OpenAI clients take a base URL with
/v1. Anthropic clients take the bare origin. Reversing it is the top failure. - Chat Completions and the Responses API are two different wire protocols. Codex now speaks only the second.
- Tool-calling shapes are where compatibility breaks first, and the error message rarely says so.
GET /v1/modelsis what most clients validate against. Serve it or the settings screen refuses your key.- Compatible does not mean identical. Test tool use and streaming, not just "hello".
What the phrase actually promises
| Endpoint | Support in practice |
|---|---|
POST /v1/chat/completions | Universal. This is what the phrase means. |
GET /v1/models | Near universal, because clients validate credentials against it. |
POST /v1/completions | Legacy. Often absent, rarely missed. |
POST /v1/embeddings | Common, but a separate decision from chat. |
POST /v1/responses | Newer and increasingly required. Codex speaks nothing else. |
| Audio, images, batches, files | Assume absent unless the vendor says otherwise. |
The Responses API split is the one that catches people in 2026. Two OpenAI-shaped protocols now exist: Chat Completions, the one everything supports, and Responses, the newer one. The Codex CLI removed the wire_api = "chat" value in version 0.122, so a modern Codex is a Responses client and nothing else. A gateway advertising "OpenAI-compatible" that serves only chat completions will not run Codex, and the error will be an unhelpful protocol or parse failure rather than an honest 404.
The base URL rule that causes most failures
Clients build their own request path from the base you give them. The two families append different things, so the correct base is different, and someone who has just configured one gets the other wrong.
| Client shape | Base URL to configure | What the client requests |
|---|---|---|
| Anthropic (Claude Code, Anthropic SDKs) | https://continuumcode.ai (bare origin) | POST /v1/messages |
| OpenAI (Codex, Cursor, opencode, OpenAI SDKs) | https://continuumcode.ai/v1 | POST /v1/chat/completions, POST /v1/responses |
Claude Code
# Bare origin. Claude Code appends /v1/messages itself.
export ANTHROPIC_BASE_URL="https://continuumcode.ai"
export ANTHROPIC_AUTH_TOKEN="cont_sk_..."
export ANTHROPIC_MODEL="claude-opus-5"
# Background turns use a separate small model whose built-in
# default id most gateways do not serve. Pin it.
export ANTHROPIC_SMALL_FAST_MODEL="claude-sonnet-5"
claude
Run /status inside the session to see the base URL the CLI is actually using. If it still shows Anthropic's own endpoint, the shell was open before you exported the variables.
Codex CLI
model = "gpt-5.6-sol"
model_provider = "continuum"
[model_providers.continuum]
name = "Continuum"
base_url = "https://continuumcode.ai/v1"
env_key = "CONTINUUM_API_KEY"
wire_api = "responses"
env_key names a variable, it does not hold the secret, so the value has to be exported in the shell that runs codex. And wire_api has to be "responses": it accounts for more failed Codex-plus-gateway setups than the key, the URL, and the model name combined, because the other three produce errors that name the thing that is wrong.
Cursor
Cursor's Models settings accept a custom OpenAI API key and an overridden base URL. It validates the pair against GET /v1/models when you save, which is why serving that endpoint is not optional. Cursor only pre-lists OpenAI's own model names, so every other model has to be added by hand under Models, Add model. Cursor Tab and Cursor's own Composer models are unaffected and stay on your Cursor plan.
The four things that break
1. Tool calling shapes
This is the big one. Tool calls are the most intricate part of the schema and the least consistently implemented. Symptoms include tool arguments arriving as a string where the client expects an object, parallel tool calls collapsed into one, missing tool_call_id on results, and a client rejecting a replayed message because it carries a field that only belongs on a streamed delta.
2. Streaming
Differences show up in whether the final chunk carries a usage block, whether [DONE] is sent, how errors mid-stream are represented, and whether the first chunk contains the role. Most clients tolerate variation; agent frameworks that reconstruct exact message objects do not. Test with a streamed tool call, not a streamed sentence.
3. Structured output and response format
response_format with a JSON schema is supported unevenly and sometimes silently ignored, which is the worst failure mode: you get valid JSON most of the time and free-form prose on the request that matters. If your pipeline depends on schema enforcement, verify it against the specific gateway rather than the model.
4. Newer and vendor-specific parameters
Reasoning effort, thinking budgets, cache control, service tiers, and per-vendor extensions are the moving edge. Some gateways pass unknown fields through, some drop them silently, and some 400. Silent dropping is the dangerous one, because your reasoning-effort setting simply stops applying and nothing tells you.
# 1. Does the models list answer? Most clients validate against this.
curl -s https://your-gateway.example.com/v1/models \
-H "Authorization: Bearer $KEY" | head -c 400
# 2. Does a plain completion work?
curl -s https://your-gateway.example.com/v1/chat/completions \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"model":"MODEL_ID","messages":[{"role":"user","content":"say ok"}]}'
# 3. Does a TOOL CALL work? This is the one that actually fails.
curl -s https://your-gateway.example.com/v1/chat/completions \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{"model":"MODEL_ID","messages":[{"role":"user","content":"weather in Paris?"}],
"tools":[{"type":"function","function":{"name":"get_weather",
"parameters":{"type":"object","properties":{"city":{"type":"string"}}}}}]}'
Anthropic-compatible, and why some gateways serve both
The Anthropic surface is a smaller contract: POST /v1/messages, a different content-block model, system as a top-level field rather than a message, and anthropic-version as a header. It matters because Claude Code, the Anthropic SDKs, and a growing number of agent tools speak it natively and cannot be pointed at an OpenAI-shaped endpoint without a translator.
If your gateway serves only one surface, LiteLLM is the usual translator in either direction, and the trade is one more hop in the path of every request.
Questions people ask
What does OpenAI-compatible API mean?
That the endpoint accepts OpenAI-shaped requests and returns OpenAI-shaped responses, at minimum on POST /v1/chat/completions and GET /v1/models. It is a claim about wire format only. Tool calling details, streaming chunk shapes, structured output, and newer parameters can all differ, and usually the first thing to break is tool calling.
Should the base URL include /v1?
For OpenAI-shaped clients, yes: they append /chat/completions or /responses to whatever you give them, so the base must end in /v1. For Anthropic-shaped clients such as Claude Code, no: they append /v1/messages themselves, so the base must be the bare origin. Getting it backwards produces a 404 either way.
Why does my custom endpoint fail in Codex but work elsewhere?
Almost always wire_api. Codex removed the "chat" value in version 0.122 and now speaks only the Responses API, so the provider block needs wire_api = "responses" and the gateway needs to serve POST /v1/responses. A gateway that implements only chat completions cannot run a modern Codex.
Why does my gateway work for chat but fail on tool calls?
Tool calling is the least consistently implemented part of the schema. Common causes are arguments serialised as a string instead of an object, a missing tool_call_id on results, and stream-only fields such as index being replayed on a stored message, which strict clients reject with a 400. Always smoke-test a tool call, not just a text completion.
What is an Anthropic-compatible API?
An endpoint implementing Anthropic's Messages API: POST /v1/messages, content blocks, system as a top-level field, and the anthropic-version header. Claude Code and the Anthropic SDKs speak it natively, so a gateway that serves only an OpenAI surface needs a translation layer such as LiteLLM to support them.
Sources
Every figure above was read from these pages on August 2026. Vendors reprice without notice; if you find a stale number, tell us.