IronStratum

OpenAI-compatible API guide: the compatibility matrix

An OpenAI-compatible API is a wire format agreement, not a relationship with OpenAI: the same chat completions request and response shapes, the same bearer-key authentication, and the same streaming conventions, served by somebody else. This guide defines the phrase precisely, shows the two-line swap that moves any OpenAI SDK onto this platform, publishes the endpoint by feature compatibility matrix that no ranking page carries, lists the exact model strings to type, and gives you a five-minute test that proves what an endpoint really accepts, including the failure it returns when you send a parameter it does not know.

Every "compatible" claim below is checked against a named source. Our column comes from this platform's own published API contract, verified 11 September 2026. Competitor columns quote their official documentation, read the same day. Where a provider documents nothing, the cell says so instead of guessing.

What the phrase means

Compatibility means the wire format matches: a POST to a chat completions path with a messages array in the body, a choices array with a message inside it on the way back, Server-Sent Events for streaming, function tools, and a bearer token in the Authorization header. OpenAI never declared this interface a standard. Its SDKs, tutorials, and ecosystem made it one, and now every serious serving surface speaks some dialect of it: local runners, hosted open-weights platforms, and the frontier vendors' own compatibility layers.

The value is simple. You write one integration. You keep your tooling, your eval harness, and your retry logic. When a model, a price, or a provider stops fitting, you change a URL and a model string instead of rewriting the application.

The fine print is what this page is about. "Compatible" is a claim on a spectrum, and providers sit at different points on it. Some accept the request shapes but silently drop parameters they do not know. Some omit whole features, or rename fields, or return error bodies your OpenAI-written code was never built to parse. The two-line swap works right up until it does not, and the difference between providers is exactly what breaks it.

The two-line swap

Point the official SDK at a different base URL, give it a key, and name a model. The openai-python library documents the base_url argument and the OPENAI_BASE_URL environment variable for exactly this:

import os
from openai import OpenAI

client = OpenAI(
    base_url=os.environ["OPENAI_BASE_URL"],
    api_key=os.environ["IRONSTRATUM_API_KEY"],
)

response = client.chat.completions.create(
    model="qwen3.8-27b",
    messages=[{"role": "user", "content": "Say this is a test."}],
)
print(response.choices[0].message.content)

The same call over HTTP, with the base URL and key read from the environment:

curl -sS $IRONSTRATUM_BASE_URL/v1/chat/completions \
  -H "Authorization: Bearer $IRONSTRATUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen3.8-27b",
    "messages": [{"role": "user", "content": "Say this is a test."}]
  }'

Three things change when you move between providers: the base URL, the key, and the model string. Everything else in your request should survive unchanged. That last claim is testable, and the tester section at the end of this guide tests it, including the case most migrations hit in production: a parameter the new endpoint does not accept.

One naming rule from our side. The model string here is the registry alias, exactly as the models list returns it: qwen3.8-27b, not a vendor name, not a variant suffix. The full table of callable aliases is in the per-model section below, and GET /v1/models returns the live list, so you never have to hardcode a guess.

The compatibility matrix

No page ranking for this topic publishes one, which is strange, because the matrix is the topic. Rows are checks a real integration makes. Columns are this platform's contract, Google's compatibility layer for Gemini, NVIDIA's hosted NIM endpoints, and Ollama's local endpoint. Every cell is sourced; the notes under the table carry the receipts.

CheckThis platform (verified 2026-09-11)Gemini compatibility layerNVIDIA hosted NIMOllama local
POST /v1/chat/completionsYes, documented pathYesYes, at their inference hostYes
GET /v1/models discoveryYes, no key required, lists the public aliases with pricing and context fieldsYes, models.list documentedNot on the chat API reference; models are per-model pagesYes, but created means last modified
Embeddings endpointYes, /v1/embeddingsYesNot on the chat API referenceYes, /v1/embeddings
StreamingSSE chunks; final usage chunk with empty choices, then [DONE], always onYes, streaming documentedYes, SSE chunks in the specYes, streaming listed; stream_options include_usage accepted
Tool callsYes: tools, tool_choice, parallel_tool_calls, tool results in messagesYes: tools and tool_choice documentedYes: tools and tool_choice in the request specYes: tools listed; parallel_tool_calls not listed
Structured outputresponse_format accepted as a request objectYes, structured output with schemasAbsent from the chat request specJSON mode listed
Token limit parameterBoth max_tokens and max_completion_tokens, identical behaviorNot stated on the compatibility pagemax_tokens onlymax_tokens only
Unknown parameters400 that names the parameter; store and service_tier rejected explicitlySilently ignored, in their words422 problem-details errorNot documented
Auth shapeBearer key required, 401 on missing or unknownBearer API keyBearer API keyKey required by SDKs, ignored by the server
Error envelopeOpenAI-shaped error object with message, type, param, code, plus a request id headerNot documented on the pageProblem-details JSON; 202 pending responses return a polling headerNot documented
reasoning_effort valuesminimal, low, medium, highMapped to thinking levels; cannot be switched off on current modelsAccepted, values per modelhigh, medium, low, max, none

The sources, row by row. Google documents the Gemini layer at one base URL covering chat, embeddings, image generation, video, models.list, function calling, and structured output, and states its unknown-parameter behavior verbatim: parameters not listed on the page "will be silently ignored by the compatibility layer" (Gemini OpenAI compatibility). NVIDIA's published OpenAPI spec for its hosted chat endpoints says "Compatible with OpenAI", accepts 12 request fields including tools, tool_choice, and reasoning_effort, and omits response_format and max_completion_tokens entirely; its error surface is a problem-details shape, and its 202 response hands back a polling request id header, which a client written to the OpenAI shape does not expect (NVIDIA NIM API reference). Ollama documents the widest local surface: chat with vision parts and logit_bias, legacy completions, embeddings, models list with modified-time semantics, and a Responses endpoint limited to the stateless flavor (Ollama compatibility docs).

Our column is not marketing copy. It is the published contract: 17 documented paths, 26 accepted chat parameters with the request object closed to unknown keys, streaming that always ends with a usage chunk before the done marker, and a 400 that names any parameter the API does not accept. Beyond the OpenAI core, the contract adds delta sessions: a session_id where each turn sends only the new messages, the server keeps the conversation, and an unseen id creates one. It also adds wallet reads at /v1/credits and a conversation list, the additive surface the OpenAI interface never had.

The last row is worth a second look: reasoning_effort takes minimal, low, medium, and high here, while the same parameter name takes five different values on a local Ollama server and maps onto a different mechanism at Gemini. Same name, different dialects. If you copy a "none" out of a local example and send it here, you get a 400 naming the value's parameter, not a silent no-op.

Where compatibility breaks

The partial-compatibility problem is not hypothetical. Router and adapter projects exist because providers diverge, and their trackers record the exact shape of the pain. In the LiteLLM project's issue tracker, issue 17246 (filed 28 November 2025, closed 9 December 2025) describes a proxy that failed to emit tool_calls deltas in a stream whenever the model answered with text and a function call together, while the same request without streaming worked and pure tool-call answers streamed fine. The label on that issue is llm translation. That is the risk a compatibility layer adds even when both endpoints involved are individually fine: one more translation between your code and the wire, and the bug sits in the seam.

The quieter divergences do not file issues; they just waste your evening. A parameter typo that produces a happy 200 on one endpoint and a naming 400 on another. A models list whose created field means "last modified", so your cache logic wrongly refreshes. An async 202 where you expected a completion. An error body shaped for a different ecosystem, which your generic handler reduces to a useless string.

The community hit the auth corner of this years ago. A much-discussed local speech server thread on a large local-model forum (27 May 2024) introduced a transcription server "an OpenAI API compatible transcription server" in the author's words, and the most practical comment exchange was about keys: the server works fine over plain HTTP with no key, but the OpenAI command line tools and SDKs refuse to run without one. The author's workaround, in the thread: set the key to anything, "it doesn't matter what you actually set it to since you're using a local API, it's a limitation that's imposed by OpenAI tooling". Compatibility cuts both ways: the client imposes its assumptions too.

None of this is an argument against the format. It is the argument for the matrix, and for the test recipe below: verify the four or five behaviors your code actually depends on, on the exact endpoint you are about to commit to.

Provider directory: local and hosted

Three groups cover the field.

Local runners speak the format on your own machine. Ollama has exposed the chat shape since 8 February 2024, at a localhost address, with the key required by tooling but ignored by the server. LM Studio documents five endpoints including the newer Responses API. The llama.cpp project's server exposes the same surface for raw model files. The local-versus-hosted decision, hardware, quants, and break-even arithmetic, is its own guide: Ornith 1.5 local vs hosted.

Hosted open-weights platforms rent the models by the token. This platform is one, across chat, embeddings, rerank, speech, and document parsing lanes. NVIDIA's NIM serves a large open catalog behind one inference host. DeepInfra positions itself as an inference cloud with an OpenAI-compatible API over hundreds of open models, and Cloudflare Workers AI covers chat and embeddings from your Cloudflare account.

Frontier vendors run compatibility layers of their own, so you can reach their models from the same SDK. Google's layer for Gemini is the most complete example, and its documentation is explicit that the surface is still in beta.

One gotcha spans all three groups: model strings are a dialect per provider. Registry aliases here (qwen3.8-27b). Organization-prefixed paths at NVIDIA and DeepInfra (openai/gpt-oss-20b class). Prefixed account-scoped names at Cloudflare (@cf/... class). Plain tags on Ollama (llama3.2). Vendor names on Google's layer. Never port a model string between providers; list models and copy the string you are given. Ollama even ships a rename command for tools that hardcode OpenAI's default model names.

Per-model swap snippets

One snippet, ten exact strings. The same two-line swap works for every chat model this platform lists; only the model field changes. Context windows come from the same models list:

import os
from openai import OpenAI

client = OpenAI(
    base_url=os.environ["OPENAI_BASE_URL"],
    api_key=os.environ["IRONSTRATUM_API_KEY"],
)

response = client.chat.completions.create(
    model="ornith-1.5-35b",          # any alias from the table below
    messages=[{"role": "user", "content": "Refactor this function to fail loudly on empty input."}],
    stream=True,
)
for chunk in response:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
Model string (exact)Public nameContext window (tokens)
qwen3.8-27bQwen3.8 27B262,144
qwen3.6-35bQwen3.6 35B A3B131,072
minimax-m2.7MiniMax M2.7196,608
muse-glimmer-30bMuse Glimmer 30B131,072
ornith-1.5-35bOrnith 1.5 35B A3B100,000
ornith-1.5-9bOrnith 1.5 9B100,000
glm-5.3-flashGLM 5.3 Flash1,048,576
deepseek-v4-flash-0731DeepSeek V4 Flash (0731 snapshot)1,310,720
gemma-4-31b-itGemma 4 31B IT262,144
deepseek-v4-proDeepSeek V4 Pro1,000,000

The chat parameter surface under those strings is wide: temperature, top_p, top_k, min_p, both penalty types, stop, seed, n, logit_bias, logprobs with top_logprobs, parallel_tool_calls, response_format, and the reasoning_effort knob for thinking models. Both names for the token limit are accepted and behave identically, which matters more than it sounds: OpenAI's own reference has deprecated max_tokens in favor of max_completion_tokens, and the old name fails outright on some of their model families, so codebases carry a mix of both today. An endpoint that honors both identically keeps that whole class of migration pain off your desk.

Test an endpoint in five minutes

Tester tools exist because this check matters; one popular web checker advertises exactly the job, "checks if it looks like OpenAI". You do not need a tool. Four curl probes against the endpoint you are evaluating, and you know more than its marketing page will ever tell you.

Probe zero, reachability. Then the models list, because it is the lowest-effort truth test on any endpoint:

curl -sS $IRONSTRATUM_BASE_URL/v1/models \
  -H "Authorization: Bearer $IRONSTRATUM_API_KEY"

One extra fact falls out here: the models list is public by design, and this call works without the key too. Every alias in the table above comes back from it, each carrying its pricing and context fields. If a provider's models list fails or hides, you will be hardcoding model strings, which is where dialect errors begin.

Probe one, the minimal chat call. One message, one model, nothing else:

curl -sS $IRONSTRATUM_BASE_URL/v1/chat/completions \
  -H "Authorization: Bearer $IRONSTRATUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m2.7",
    "messages": [{"role": "user", "content": "Reply with the single word: ready"}]
  }'

You are checking the response envelope: an id, an object field, a choices array with a message, and a usage block. Those are the fields every OpenAI-written parser touches first.

Probe two, the deliberate bad parameter. This is the one testers skip and production code trips on. Send a parameter no sane endpoint accepts:

curl -sS $IRONSTRATUM_BASE_URL/v1/chat/completions \
  -H "Authorization: Bearer $IRONSTRATUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-m2.7",
    "messages": [{"role": "user", "content": "Reply with the single word: ready"}],
    "verbosity": "high"
  }'

The healthy response on this platform is a 400 whose body names the offender:

{"error": {"message": "Unknown parameter: 'verbosity'.", "type": "invalid_request_error", "param": "verbosity", "code": "unknown_param"}}

That is the behavior you want from anything you build on. The same law covers documented non-support: store and service_tier are rejected with a 400 naming them, never quietly dropped, so a codebase written against a provider that accepts those fields finds out in the first integration test instead of in a silent behavioral gap. Endpoints that silently ignore unknown parameters pass every happy-path test and then swallow a misspelled parameter you actually needed. The OpenAI error codes guide documents the same convention on their side, the error object carrying a type and a param field, which is why this failure mode is parseable rather than fatal.

Probe three, streaming, because it breaks more often than anything else:

curl -sSN $IRONSTRATUM_BASE_URL/v1/chat/completions \
  -H "Authorization: Bearer $IRONSTRATUM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "glm-5.3-flash",
    "messages": [{"role": "user", "content": "Count from one to five."}],
    "stream": true
  }'

Watch the end of the stream, not the middle. This platform always emits a final chunk with an empty choices array and the full usage for the request, then the [DONE] marker. That matches the terminal-chunk convention in the OpenAI streaming reference, with one difference worth knowing: there, you must request the usage chunk with stream_options; here it is always on. Your cost accounting can therefore read every stream without a second request. The streaming guide covers the details.

The same four probes work anywhere, with the base URL and key swapped, and the differences are the findings. At Google's compatibility layer, the deliberate-bad-parameter probe will not fail: their documentation states unlisted parameters are silently ignored, so the naming 400 you should demand is exactly the one you will not get. At NVIDIA's hosted endpoints, read the fine print before the probes: their spec documents an async pending response with a polling header, so a slow answer is not necessarily a broken endpoint. Against a local Ollama server the models probe looks like this, keyless and on your own machine:

curl -sS http://localhost:11434/v1/models

Two refinements for production code. Give every chat POST an Idempotency-Key header: replays of the same key with an identical body return the stored response, and a reused key with a different body fails with a 409 instead of double-charging you, which makes retry logic safe to write. The idempotency guide and the errors guide carry the full contracts.

Routers and adapter layers

A router is a proxy that speaks the OpenAI format to you and translates to many providers behind itself. LiteLLM is the best-known open-source example, and its provider directory runs past a hundred entries. Routers earn their keep when you genuinely need many providers at once: failover between hosts, per-route budgeting, one key across a vendor zoo.

They also add the translation-seam failure mode documented above, plus a configuration surface of their own. The honest rule: if one endpoint meets your needs, connect directly and keep the matrix cells you depend on pinned by the four probes. Add a router when multi-provider requirements are real, not in anticipation of them. For a first project on this platform, the quickstart is the direct path.

Wallet, metering, and what we deliberately reject

Every call on this platform is metered against a prepaid wallet: a key whose balance reaches zero is denied at request time, a production behavior verified 11 September 2026. The worst case a runaway loop can produce is a wallet that needs a top-up. Per-key spend caps are an industry practice worth asking any provider for; they are not a shipped feature here, and the wallet floor is today's hard stop. The full treatment of metered-spend failure modes, leak forensics, and prepaid-versus-postpaid worst cases is its own guide: how to avoid API bill shock. Rates for every lane sit on the pricing page.

Two rejections are features here, not gaps. Unknown parameters are refused loudly, because the silent alternative turns integration bugs into behavior differences you discover weeks later. And the speech and parsing lanes keep the same authentication and error shapes as chat, so the compatibility your chat code enjoys is not a chat-only accident. The speech-lane economics and the parsing-lane guides carry their own lanes: TTS local vs API break-even and merged-cell PDF table extraction.

Frequently asked questions

What is an OpenAI compatible API?

An API that speaks the same wire format as OpenAI's chat completions interface: the same endpoint paths, request bodies with a messages array, response bodies with choices and usage, Server-Sent Events streaming, function tools, and bearer-token authentication. It is a format agreement, not a business relationship, and it is not a guarantee: providers differ in which parameters, features, and error shapes they actually implement, which is why this guide carries a matrix.

Which providers are compatible with the OpenAI API?

Three groups. Local runners: Ollama and LM Studio and the llama.cpp server on your own machine. Hosted open-weights platforms: this platform, NVIDIA's NIM endpoints, DeepInfra, Cloudflare Workers AI. Frontier compatibility layers: Google's layer for Gemini is the most complete. Every entry in the directory section above links the provider's own compatibility documentation, and the matrix section shows exactly where each of the four we tested matches and diverges.

Is there a free API that is OpenAI-compatible?

The local ones are free to call once installed: no metering, no key, your hardware and electricity. Hosted providers rotate free tiers and trial credits, and their own pricing pages are the only current source for those. This platform claims no free tier: every call is metered against a prepaid wallet, and rates live on the pricing page.

What do I change to point my code at another provider?

Three values: the base URL, the API key, and the model string. In the official SDKs, that is the base_url argument or the OPENAI_BASE_URL environment variable, the api_key argument, and the model field. Everything else, your messages, tools, and parsing code, should survive unchanged. Then run the four probes in the tester section on the new endpoint before you trust it, because "should survive" is a claim, and the probes turn it into an observation.

What parts of the API are NOT actually compatible?

The recurring soft spots, in the order integrations hit them: streaming conventions beyond the first chunk (the terminal usage chunk and done marker), tool_calls delta shapes in streams, response_format and structured output support, the token-limit parameter naming (max_tokens versus max_completion_tokens, with OpenAI deprecating the former and some endpoints accepting only one), reasoning_effort value sets, unknown-parameter behavior (silent ignore versus a 400 that names the parameter), and error body shapes. The matrix covers all seven rows for four providers, each cell sourced to official documentation.

How do I test that an endpoint really is compatible?

Four curl probes, five minutes: list models, send a minimal chat call and check the response envelope, send a deliberately unknown parameter and demand a 400 that names it, and stream once while watching the end of the stream for the final usage chunk and done marker. The tester section above gives the exact commands and the healthy responses for each probe on this platform, and the same probes work against any endpoint you are evaluating.

Does the OpenAI SDK work with local models?

Yes. Local runners expose the OpenAI shape on localhost, and the SDK connects with the base URL pointed at it. One quirk: the SDKs refuse to start without an API key even when the server ignores the key entirely, so local setups set the key to any placeholder string; Ollama's own examples use a fixed placeholder and mark it required but ignored. List models on the local endpoint first and use its exact tags as your model strings.


The compatibility question has an empirical answer, and now you have the instruments: the matrix for what is documented, the swap for what changes, and the probes for what is true. Current rates for every lane sit on the pricing page, and a wallet plus a key takes minutes on the signup page.

Last verified: 2026-09-11