Most gateways accept every parameter and quietly drop the ones the upstream model cannot use. You get a 200, the request runs, and the setting you thought you applied did nothing. That failure mode is invisible until it costs you something.
Stav takes the opposite position, and it is the single behaviour most likely to surprise you when migrating:
A parameter is either honoured, or refused by name with a
400. It is never silently dropped.
Absence of provider support is not "skip it" — it is the refusal.
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"
}
}
Where the constraint belongs to the specific model rather than the provider — stop on an OpenAI GPT-5 or o-series model, for instance — the message names the model and the dialects that would accept the parameter instead:
Parameter 'stop' is not supported by model 'gpt-5.4'. Remove the parameter, or address a
model served by a provider that supports it (anthropic, google, mistral, openai).
Either way you get the parameter, the thing that refused it, and what to do. You should not have to read anyone's source to act on it.
An unknown field is also rejected, naming the field. There is no extra bucket.
Dialects, not providers
Support is keyed by wire dialect, not by vendor:
| Dialect | Backs |
|---|---|
openai | OpenAI, xAI, Cohere, Azure, OpenRouter, self-hosted OpenAI-compatible servers |
anthropic | Anthropic |
google | Gemini |
mistral | Mistral |
The table
Legend: ✅ honoured · 400 refused by name · no-op declared intentional ignore
messages, stream and stream_options are not in the table: they are request structure rather than knobs, and sit outside the refusal gate by design. stream_options.include_usage is honoured on every dialect and is what makes token counts appear on a stream.
| Parameter | openai | anthropic | mistral | Notes | |
|---|---|---|---|---|---|
temperature | ✅ | ✅ | ✅ | ✅ | Omitted on OpenAI restricted families (GPT-5 / o-series), on Anthropic while reasoning is active, and on Anthropic when an explicitly non-default top_p is present — the provider rejects both together. That last one is a declared drop, not a refusal |
top_p | ✅ | ✅ |
Stav extensions
| Parameter | Meaning |
|---|---|
session_id | Explicit conversation session identifier (≤128 chars). Overrides response-anchored chaining. Echoed back as X-Session-Id |
provider | Narrow which providers may serve an explicitly pinned model: only, ignore, max_price, quantizations. Not applied on the auto / named-router path — shape those with team policy and router allowlists instead |
Accepted but not applied today. extra="forbid" means every field the schema knows about has to stay declared, so a few are accepted without currently having an effect: the models fallback array, and provider.sort, provider.require_parameters and provider.allow_fallbacks. Do not build on them.
The two rules that catch migrations
1. max_tokens, always. OpenAI's newer SDKs send max_completion_tokens. Stav does not accept that field inbound — it renames max_tokens per provider for you. Most clients have a compatibility switch for this; set it to max_tokens.
2. n > 1 is not supported. Issue parallel requests instead.
Discover before you send
Strict refusal is only fair if you can find out in advance. Every model publishes its capability set:
curl https://api.stav.ai/v1/models/claude-sonnet-4-5-20250929 \
-H "Authorization: Bearer $STAV_API_KEY"
{
"stav_supports_thinking": true,
"stav_capabilities": {
"extended_thinking": true,
"tool_use": true,
"vision": true,
"structured_output": true,
"pdf_input": true
}
}
And when you route with model="auto", the capability requirements implied by your request become hard filters on the candidate pool — so a routed request that needs tools will never be handed to a model without them.
Inline annotations
cache_control rides inside message content, tool definitions and the system prompt rather than at the top level, so it is not part of the payload gate. Its contract is declared rather than accidental:
| Provider | Behaviour |
|---|---|
| Anthropic | Honoured natively, including on /v1/chat/completions |
| Stripped — Gemini's OpenAI-compatible surface rejects the key | |
| OpenAI, xAI, Mistral | Ignored; these cache implicitly |
Streaming and errors
A parameter refusal detected before dispatch returns a normal HTTP 400. If a refusal surfaces after a streaming response has been opened, it is delivered as an SSE error frame followed by a chunk with finish_reason: "error" — the HTTP status will already have been 200. Streaming clients should check for an error key on each frame rather than relying on the status line alone.
Asking for a parameter
The refused-on-every-dialect entries are deliberate: registering a parameter means committing to its semantics on every provider, and Stav would rather refuse honestly than half-implement. If you need one of them, say so — the registry is one entry per parameter, and adding one is a small change.