Create a chat completion (OpenAI-compatible)
https://api.stav.ai/v1/chat/completionsGenerates a model response for a conversation. Drop-in for the
OpenAI Python or TypeScript SDK — set base_url="https://api.stav.ai/v1"
and your existing code works.
Supports streaming via SSE (stream=true), function calling
(tools[]), structured output (response_format), extended
thinking (reasoning_effort), and prompt caching markers
(cache_control on messages, tools and the system prompt, where
the underlying model supports it).
Pass model="auto" to invoke the Smart Router, or
@workspace-slug/router-slug to use one of your workspace's named routers.
tools[] are your own functions: Stav returns the call the model
wants to make and your application executes it. Stav does not run
tools server-side.
Request body
modelstringrequiredOne of these forms:
creator/model— a catalogue model; the Smart Router picks the Provider Endpoint among those your policy admits;creator/model/provider— the same model pinned to one Inference Provider (add:tier, e.g.:fast, to pin a serving tier). A pin never bypasses your policy: an endpoint the model lacks is a 404, one your policy excludes is a 403, both by name;- the bare model name (
claude-opus-5) — a convenience alias, accepted when it names exactly one catalogue model; auto— your workspace's default routing policy;@workspace-slug/router-slug— a named router your workspace authored;creator/model/@node— the same model pinned to one of your own Stav Tunnel nodes (@workspace-slug/nodealso works). A model your node serves that is not in the catalogue is named@workspace-slug/local-idand pinned as@workspace-slug/local-id/@node. A pin to a node never fails over; auto and router traffic does.
messagesarray of objectsrequiredShow 5 child fields
roleenumrequiredsystemuserassistanttoolcontentstring | array of objectString for text-only messages, or array of content parts (text, image_url, input_audio) for multimodal models.
namestringOptional name for the message author.
tool_call_idstringRequired when role is
tool.tool_callsarray of objectsTool calls the assistant made (assistant-role messages only).
Show 3 child fields
idstringrequiredtypestringrequiredfunctionobjectrequiredShow 2 child fields
namestringrequiredargumentsstringrequiredJSON-encoded argument object.
streambooleanstream_optionsobjectStreaming options, applied only when
stream=true.Show 1 child field
include_usagebooleanWhen true, a final SSE chunk carries a
usageobject with token counts and an emptychoicesarray, before the[DONE]sentinel. Without it a streaming response reports no token counts at all.
nintegerNumber of choices to return. Only
1is supported —n>1returns 400 (unsupported_parameter). Issue parallel requests instead.providerobjectStav extension. Narrows which providers may serve an explicitly pinned model. Not applied on the
autoor named-router path — shape those with workspace policy and router allowlists instead.Show 7 child fields
onlyarray of stringProvider allowlist.
ignorearray of stringProvider blocklist.
max_priceobjectPer-1M-token EUR ceiling.
Show 2 child fields
promptnumbercompletionnumber
quantizationsarray of stringsortNot supported. Sending it returns 400
unsupported_parameterwithparamprovider.sort.require_parametersNot supported. Sending it returns 400
unsupported_parameterwithparamprovider.require_parameters.allow_fallbacksNot supported. Sending it (true or false) returns 400
unsupported_parameterwithparamprovider.allow_fallbacks.
modelsarray of stringStav extension, reserved for a later release. Accepted on
autoor a Router and has no effect today. With an explicit model it returns 400unsupported_parameter(parammodels).temperaturenumberSampling temperature. Higher is more random. Honoured on: OpenAI, Anthropic, Google, Mistral.
top_pnumberNucleus sampling cutoff. Honoured on: OpenAI, Anthropic, Google, Mistral.
max_tokensintegerMaximum tokens to generate. Honoured on: OpenAI, Anthropic, Google, Mistral.
response_formatobjectStructured-output contract (json_object / json_schema). type grammar is refused (400). Honoured on: OpenAI, Anthropic, Google, Mistral. Requires the
structured_outputcapability — withmodel="auto"this becomes a hard filter on the candidate pool.Show 3 forms
ResponseFormatTexttypestringrequired
ResponseFormatJsonObjecttypestringrequired
ResponseFormatJsonSchematypestringrequiredjson_schemaobjectrequiredShow 4 child fields
namestringrequiredschemarequiredA JSON Schema 2020-12 document.
descriptionstringstrictboolean
toolsarray of objectsFunction/tool definitions the model may call. Hosted tools (any
typeother thanfunctionorcustom, e.g.web_search) are dropped and named inX-Stav-Dropped-Params: no provider on this surface runs them. Honoured on: OpenAI, Anthropic, Google, Mistral. Requires thefunction_callingcapability — withmodel="auto"this becomes a hard filter on the candidate pool.Show 2 child fields
typestringrequiredfunctionobjectrequiredShow 3 child fields
namestringrequireddescriptionstringparametersJSON Schema for the function's arguments.
allowed_toolsarray of string | objectRestrict which of
toolsthe model may call, without changing the tool array — so a mid-loop restriction does not invalidate the prompt cache. The value is passed to the provider as sent. Honoured on: OpenAI. Returns 400unsupported_parameteron: Anthropic, Google, Mistral. Requires thefunction_callingcapability — withmodel="auto"this becomes a hard filter on the candidate pool.tool_choiceenum | objectConstrain which tool the model may call. Honoured on: OpenAI, Anthropic, Google, Mistral. Requires the
function_callingcapability — withmodel="auto"this becomes a hard filter on the candidate pool.Show 1 form
ToolChoiceFunctiontypestringrequiredfunctionobjectrequiredShow 1 child field
namestringrequired
reasoning_effortenumReasoning depth for models that support extended thinking (none, minimal, low, medium, high, xhigh, max).
noneasks a model that reasons by default to stop; it does not require theextended_thinkingcapability. Translated to the nearest level the serving endpoint accepts (ties round down); the level sent is returned inX-Stav-Reasoning-Effort. Honoured on: OpenAI, Anthropic, Google, Mistral. Aliases accepted:reasoning,thinking. Any level other thannonerequires theextended_thinkingcapability — withmodel="auto"this becomes a hard filter on the candidate pool.noneminimallowmediumhighxhighmaxstopstring | array of stringUp to 4 sequences that halt generation. Honoured on: OpenAI, Anthropic, Google, Mistral.
top_kintegerTop-k sampling cutoff. Honoured on: Anthropic, Google. Returns 400
unsupported_parameteron: OpenAI, Mistral.seedintegerBest-effort determinism across identical requests. Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.logprobsbooleanReturn log probabilities for output tokens. Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.top_logprobsintegerMost-likely tokens to return per position (0–20). Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.presence_penaltynumberPenalise tokens already present in the text. Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.frequency_penaltynumberPenalise tokens by existing frequency. Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.logit_biasobjectPer-token likelihood bias map (token id → −100..100). Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.verbosityenumGPT-5 output verbosity hint (low / medium / high). Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.lowmediumhighpredictionobjectPredicted output for latency-optimised edits (OpenAI). Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.prompt_cache_keystringCache-routing hint for OpenAI automatic prompt caching. Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.safety_identifierstringStable end-user identifier for abuse monitoring. Honoured on OpenAI; declared intentional-ignore elsewhere (portable clients send it universally). Honoured on: OpenAI. Declared no-op on: Anthropic, Google, Mistral (accepted and ignored, never a 400).
service_tierenumOpenAI processing tier (auto / default / flex / priority). Billing follows the tier the provider echoes, not the one sent (auto may come back as priority); the request log shows it. A tier the endpoint has no conditional rate for is refused with 400 service_tier_unpriced (the message names the tier); auto is accepted only when every tier the provider may pick is priced (issue #1154 gap 4). Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.autodefaultflexpriorityuserstringStable end-user identifier (legacy; see safety_identifier). Honoured on OpenAI; declared intentional-ignore elsewhere (OpenAI-SDK clients attach it to every request, any provider). Honoured on: OpenAI. Declared no-op on: Anthropic, Google, Mistral (accepted and ignored, never a 400).
parallel_tool_callsbooleanAllow the model to emit multiple tool calls in one turn. Honoured on: OpenAI. Returns 400
unsupported_parameteron: Anthropic, Google, Mistral.min_pnumberMin-p nucleus floor. Provider-specific tuning. Not honoured on any provider — returns 400
unsupported_parameternaming the parameter and the model that resolved.repetition_penaltynumberRepetition penalty. Provider-specific tuning. Not honoured on any provider — returns 400
unsupported_parameternaming the parameter and the model that resolved.storebooleanPersist the completion for later retrieval.
falseis accepted: Stav never persists completions.truewould store your output in Stav's provider account, not yours.truereturns 400unsupported_parameteron every provider, naming the parameter and the model that resolved.session_idstringStav extension — explicit conversation session id (overrides anchor chaining). Also names the routing session: the router uses it to remember what it decided last turn, for sticky routing and warm caches. Namespaced by team and API key before hashing, so two tenants sending
defaultnever share state. Also accepted as theX-Session-Idrequest header; the resolved session is echoed back in theX-Session-Idresponse header. Meant for Stav's router and forwarded to no provider — accepted on every model, never a 400.cost_tierenumStav extension, reserved for a later release. Accepted and checked (an unknown value is a 400), and has no effect today. Meant for Stav's router and forwarded to no provider — accepted on every model, never a 400.
lowbalancedhighmaxremaining_turnsintegerStav extension, reserved for a later release. Accepted and checked (outside 0–1000 is a 400), and has no effect today. Meant for Stav's router and forwarded to no provider — accepted on every model, never a 400.
metadataobjectFree-form tags on a stored completion. Tied to
store. Not honoured on any provider — returns 400unsupported_parameternaming the parameter and the model that resolved.modalitiesarray of stringRequested output modalities (e.g. audio). Stav serves text. Not honoured on any provider — returns 400
unsupported_parameternaming the parameter and the model that resolved.include_reasoningbooleanEcho the reasoning trace in the response. Reasoning is reported as a token count on
usage.completion_tokens_detailsinstead. Not honoured on any provider — returns 400unsupported_parameternaming the parameter and the model that resolved.chat_template_kwargsobjectBackend chat-template arguments. Not honoured on any provider — returns 400
unsupported_parameternaming the parameter and the model that resolved.
Notes
Many gateways accept every parameter and quietly drop the ones the upstream model cannot use. You get a 200 and the setting did nothing.
Stav does the opposite:
A parameter is either honoured, or refused by name with a
400. It is never silently dropped.
If a provider cannot honour a parameter, the request is refused. Where something must be removed to make the request work — a hosted tool no provider runs, or a cache marker on the translation path — the response names it in X-Stav-Dropped-Params.
This page covers /v1/chat/completions, where the contract is enforced by one parameter registry. /v1/messages and /v1/responses are covered at the end.
What a refusal looks like
{
"error": {
"message": "Parameter 'logit_bias' is not supported by provider 'anthropic'. Remove it or route to a model/provider that supports it.",
"type": "invalid_request_error",
"param": "logit_bias",
"code": "unsupported_parameter",
"request_id": "chatcmpl-…",
"docs_url": "https://stav.ai/docs/api/errors#request-validation"
}
}
When the limit belongs to the model rather than the provider — stop on an OpenAI GPT-5 or o-series model, for example — the message names the model, and the router and model you asked for if they differ:
Parameter 'stop' is not supported by model 'gpt-5-5'. Remove the parameter, or address a
model served by a provider that supports it (anthropic, google, mistral, openai).
A field the request schema does not know is also a 400 naming the field.
Discover before you send
GET /v1/params— the registry as JSON: every parameter, its description, the dialects that honour it, and the capability it needs. Anonymous, no key required.GET /v1/models/{id}—stav_capabilitiesfor one model (tool_use,vision,structured_output,extended_thinking, …) andstav_supports_thinking.
curl https://api.stav.ai/v1/params
curl https://api.stav.ai/v1/models/anthropic/claude-sonnet-4-5 \
-H "Authorization: Bearer $STAV_API_KEY"
When you route with model="auto" or a Router, the capabilities your request needs (tools, images, a schema, reasoning) become hard filters on the candidate pool.
Dialects, not providers
Support is keyed by wire dialect:
| Dialect | Backs |
|---|---|
openai | OpenAI and every OpenAI-compatible Inference Provider (xAI, Fireworks and others) |
anthropic | Anthropic |
google | Gemini |
mistral | Mistral |
The table
Legend: ✅ honoured · 400 refused by name · ignored accepted and not forwarded, by design
model, messages, stream and stream_options are request structure, not parameters. stream_options.include_usage is honoured on every dialect.
| Parameter | openai | anthropic | mistral | Notes | |
|---|---|---|---|---|---|
temperature | ✅ | ✅ | ✅ | ✅ | Left out, and named in X-Stav-Dropped-Params, where the Provider Endpoint accepts only the default (for example OpenAI GPT-5 and o-series models). Also left out on Anthropic while reasoning is active, and on Anthropic when a non-default top_p is also set, because the provider rejects both together |
top_p | ✅ | ✅ | ✅ | ✅ | Left out, and named in X-Stav-Dropped-Params, where the Provider Endpoint accepts only the default. On Anthropic, sent only when non-default |
max_tokens | ✅ | ✅ | ✅ | ✅ | Sent as max_completion_tokens where the Provider Endpoint wants that name. Anthropic gets 4096 when unset. Above the Provider Endpoint's own output limit, it is lowered to that limit and named in X-Stav-Clamped-Params — see below |
max_completion_tokens | ✅ | ✅ | ✅ | ✅ | The same cap as max_tokens — send either. Both with different values is a 400 |
stop | ✅ | ✅ | ✅ | ✅ | Anthropic receives stop_sequences. 400 where the Provider Endpoint refuses it (for example OpenAI GPT-5 and o-series models). Stav does not enforce a count; providers do (OpenAI: 4) |
tools | ✅ | ✅ | ✅ | ✅ | Needs the tool-use capability. Hosted tools are dropped — see below |
tool_choice | ✅ | ✅ | ✅ | ✅ | |
allowed_tools | ✅ | 400 | 400 | 400 | Restricts which tools may be called without changing the array, so the prompt cache survives |
parallel_tool_calls | ✅ | 400 | 400 | 400 | |
response_format | ✅ | ✅ | ✅ | ✅ | text, json_object, json_schema, grammar. Needs the structured-output capability |
reasoning_effort | ✅ | ✅ | ✅ | ✅ | none, minimal, low, medium, high, xhigh, max. reasoning and thinking are accepted aliases. Moved to the nearest level the endpoint accepts (ties go lower); the level sent is in X-Stav-Reasoning-Effort. See Reasoning |
top_k | 400 | ✅ | ✅ | 400 | |
seed | ✅ | 400 | 400 | 400 | |
logprobs | ✅ | 400 | 400 | 400 | |
top_logprobs | ✅ | 400 | 400 | 400 | 0–20 |
presence_penalty | ✅ | 400 | 400 | 400 | |
frequency_penalty | ✅ | 400 | 400 | 400 | |
logit_bias | ✅ | 400 | 400 | 400 | |
verbosity | ✅ | 400 | 400 | 400 | low / medium / high |
prediction | ✅ | 400 | 400 | 400 | Predicted outputs |
prompt_cache_key | ✅ | 400 | 400 | 400 | Also used as a session identity for routing |
service_tier | ✅ | 400 | 400 | 400 | |
user | ✅ | ignored | ignored | ignored | SDKs send it everywhere, so it is not refused |
safety_identifier | ✅ | ignored | ignored | ignored | Same reason |
store | — | — | — | — | false is honoured (Stav never stores completions). true is a 400: it would store your output in Stav's provider account, where you cannot reach or delete it |
n | — | — | — | — | 1 only. n > 1 is a 400 — send parallel requests |
min_p | 400 | 400 | 400 | 400 | Not supported. Ask if you need it |
repetition_penalty | 400 | 400 | 400 | 400 | Not supported. Ask if you need it |
metadata | 400 | 400 | 400 | 400 | Tags a stored completion; there is no store |
modalities | 400 | 400 | 400 | 400 | Text output only |
include_reasoning | 400 | 400 | 400 | 400 | Reasoning tokens are reported in usage.completion_tokens_details |
chat_template_kwargs | 400 | 400 | 400 | 400 |
Stav extensions
Read by Stav, never forwarded to a provider.
| Parameter | Meaning |
|---|---|
session_id | Your conversation id (≤128 chars). Keeps routing sticky across turns. The resolved session comes back in X-Session-Id |
provider | On a model you name: only, ignore, max_price, quantizations — the model must pass them, or the request is refused. sort, require_parameters and allow_fallbacks are a 400 unsupported_parameter |
See Routing hints. models, cost_tier and remaining_turns are reserved for a later release: they are accepted and have no effect today (models with a named model is a 400 unsupported_parameter).
Refused by name. These parse, but nothing honours them, so each returns 400 unsupported_parameter with param set to the field: provider.sort, provider.require_parameters, provider.allow_fallbacks, and response_format with type: "grammar".
Dropped, and named
Four things are removed rather than refused, because leaving them in would break the request.
Sampling a model does not take. Some Provider Endpoints accept only the default temperature and top_p. Stav measures this per endpoint, from the provider's own error. A non-default value there is left out and named in X-Stav-Dropped-Params, so SDK defaults such as temperature=0.7 keep working.
A reasoning level on a model that does not reason. On /v1/chat/completions, when you name a model that cannot reason, reasoning_effort (or reasoning / thinking) is left out and named in X-Stav-Dropped-Params. The answer is the same either way: the model does not think. Many agents send a level on every request, so this keeps them working on every model. none is never named — it asks for no reasoning, which the model already does. With auto or a Router, a level still limits the choice to models that reason; see Reasoning.
Hosted tools. Tools that ask the server to run something (web_search, file_search, code_interpreter, Anthropic's web_search_20250305, bash_20250124, …) are not run by any provider behind these surfaces. They are dropped and named in X-Stav-Dropped-Params:
/v1/chat/completions: every tool whosetypeis notfunctionorcustom./v1/messageswith a non-Claude model: every tool whosetypeis notcustom./v1/responseswith a model served through translation: hosted tools such asweb_search.
If tool_choice points at a dropped tool, or requires a tool when none is left, it is dropped too.
cache_control. It sits inside messages, tools and the system prompt:
| Provider | Behaviour |
|---|---|
| Anthropic | Honoured, including on /v1/chat/completions |
| Removed — Gemini's OpenAI-compatible API rejects the key | |
| OpenAI, xAI, Mistral | Ignored; they cache automatically |
On /v1/messages with a non-Claude model it is dropped and named in X-Stav-Dropped-Params.
Lowered, and named
max_tokens above the Provider Endpoint's output limit. max_tokens is a ceiling, and some Provider Endpoints have a lower one — Scaleway serves GLM-5.2 with at most 16,384 output tokens. Stav sends the endpoint's own limit instead and names it in X-Stav-Clamped-Params, for example max_tokens=16384. The answer is the one the provider would give at its own limit. When Stav does not know the limit yet, it learns it from the provider's refusal and retries once at the stated limit; later requests are lowered before the call. A context-length error is not an output limit and is never read as one.
Other surfaces
/v1/messages. Claude models are proxied to Anthropic as sent; unknown fields are forwarded, not refused. Other models are translated onto the chat path: thinking becomes reasoning_effort, and the chat-completions rules above apply. session_id and provider are accepted here too. provider follows the chat-completions rules on every model, including Claude, and is never forwarded to Anthropic.
/v1/responses. OpenAI models are proxied as sent. Other models are translated onto the chat path. store: true is refused (403 store_not_permitted) when your workspace requires zero data retention.
/v1/audio/transcriptions. A multipart/form-data request with its own short list: file, model, language, prompt, response_format, temperature. The same contract applies — any other field, and stream, is refused by name with 400 unsupported_parameter. auto and Routers are refused (router_not_supported). See Audio transcriptions.
Streaming and errors
A refusal found before dispatch is a normal 400. An error after a stream has opened arrives as an SSE error frame followed by a chunk with finish_reason: "error" — the status line already said 200. Check every frame. See Errors.
Response headers
| Header | Meaning |
|---|---|
X-Stav-Request-Id | Unique request identifier, identical to the completion |
X-Stav-Model | The model that actually served the request — not necessarily the one you asked for, when routing or failover intervened. |
X-Stav-Provider | The provider that served the request. |
X-Stav-Sovereignty-Level | The effective sovereignty level (L0–L4 ladder) of the endpoint
that served this request. |
X-Stav-Route-Reason | One-line, human-readable rationale for the selection. Present on pinned requests too, where it restates the selection. Prose — do not parse it; the structured decision is in the routing log. |
X-Stav-Router | The named or default router that handled the request, when one did. |
X-Stav-Served-By |
|
X-Stav-Node-Id | The id of your Stav Tunnel node that served (with |
X-Stav-Route-Time-Ms | On a streaming response, time from handler start to provider dispatch. On a non-streaming response, total handler latency including the provider round-trip. The two are not comparable. |
X-Stav-Reasoning-Effort | Present when the request carried a reasoning control
( |
X-Stav-Reasoning-Chosen-By | Who chose the reasoning level the provider received: |
X-Stav-Upstream-Surface | The provider API that served the request: |
X-RateLimit-Limit | Per-minute request cap on this key. |
X-RateLimit-Remaining | Requests remaining in the current window. |
X-RateLimit-Reset | Unix timestamp (seconds) when the current window rolls over. |
Errors
| Status | When |
|---|---|
| 400 | Invalid request — malformed JSON, missing required fields, or invalid parameter values. |
| 401 | Missing or invalid API key. |
| 403 | API key lacks the required scope (e.g., |
| 404 | Model id is not in the catalogue, or the caller's key doesn't have access to it. |
| 429 | Per-key or per-IP rate limit exceeded. |
| 502 | An upstream provider (Anthropic, OpenAI, etc.) returned an error. |
Every error code, what it means and what to do: Errors.