Request parameters
LowRouter routes one OpenAI-shaped request to very different providers. They don’t all support the same parameters. This page covers what happens to each parameter on each kind of route. It also covers how to tell from the response whether a parameter was applied.
Nothing is dropped silently
Every parameter you send ends in one of three ways:
-
Applied. It is translated onto the provider’s own control and has its effect.
-
Rejected with a 400. This happens when the parameter would change the answer and the route cannot express it. Examples: asking for JSON and getting prose, asking for log probabilities and getting none. The error names the parameter and the provider:
JSON{ "error": { "type": "invalid_request_error", "message": "LowRouter rejected the request before it reached provider anthropic: logprobs is not supported by provider anthropic: this route does not return token log probabilities", "param": "logprobs" } }On an
auto/request this does not end the request. Other routes may support the parameter, so LowRouter tries the next route. The 400 only comes back when no route can apply it. -
Disclosed. This happens when the parameter is advisory, such as a reasoning level, a seed or a sampling tweak. Leaving it out still gives a correct answer, so the request goes ahead and the response lists the parameter in
lowrouter_unapplied.
lowrouter_unapplied
lowrouter_unapplied appears on a completion only when something was
not applied. If every parameter was applied, the field is absent.
Here’s an example:
"lowrouter_unapplied": [
{
"parameter": "seed",
"reason": "unsupported_on_this_path",
"detail": "provider anthropic has no equivalent control"
},
{
"parameter": "made_up_param_xyz",
"reason": "unrecognized_parameter",
"detail": "LowRouter does not recognise this parameter, so no provider received it"
}
]On a stream, it’s on the final chunk next to lowrouter_metadata.
Match on parameter and reason in your code. detail is
human-readable prose and may change.
reason | Meaning |
|---|---|
unsupported_on_this_path | The serving provider has no equivalent control. |
rejected_by_provider | The provider refused the parameter for this model. The request was retried without it. |
conflicts_with_sampling | Extended thinking was not enabled because it conflicts with your sampling settings. Anthropic requires temperature 1, no top_k, and top_p of at least 0.95. A sampling parameter the model refused, and that was therefore removed, does not count. |
max_tokens_too_small | Your max_tokens leaves no room for a thinking budget. |
unrecognized_parameter | LowRouter does not know this top-level key, so no provider received it. |
Unknown parameters
A top-level key LowRouter does not model is accepted and disclosed.
LowRouter does not reject it: many clients send another gateway’s
extensions, such as OpenRouter’s provider or Vercel’s
providerOptions, and refusing those would break the client. An
unknown key is never forwarded to a provider, though. The
unrecognized_parameter entry tells you it had no effect.
Parameter support
The columns are the four kinds of route. The first, OpenAI-compatible, covers OpenAI, Mistral, Scaleway, OVHcloud, Vertex model-as-a-service and the other providers that speak OpenAI’s format.
response_formatjson_schema- applied
response_formatjson_object- 400
logprobs,top_logprobs- 400
logit_bias- 400
n> 1- 400
reasoning_effort- disclosed
seed- disclosed
temperature,top_p- applied
top_k- disclosed
presence_penalty,frequency_penalty- disclosed
repetition_penalty,metadata,prediction- disclosed
service_tier- disclosed
user- disclosed
nameon a message (disclosed asmessages.name)- disclosed
cache_control(content-part marker)- applied
tool_resultcarrying an image or document- applied
stream_options.include_usage- same
Forwarded means the parameter is sent as-is. If the provider
refuses an advisory parameter for a model, LowRouter retries without it
and discloses it as rejected_by_provider. After that, the parameter
is dropped up front for that model. If the provider refuses a parameter
that changes the answer, you get its 400.
A provider can also accept a parameter and quietly ignore it. We can’t see that from outside. When the exact shape of the answer matters, check the answer itself.
Providers that refuse unknown fields
Most OpenAI-compatible providers ignore a field they do not use.
Mistral and OVHcloud do not: their request schema is closed, and a
field outside it fails the whole request. LowRouter knows which fields
each of them refuses. On those routes the fields are removed before
the request leaves, and each one is disclosed as
unsupported_on_this_path:
| Provider | Removed and disclosed |
|---|---|
| Mistral | user, name on a message (disclosed as messages.name), a passed-back reasoning trace (messages.reasoning_content) |
| Groq | a passed-back reasoning trace (messages.reasoning_content) |
| OVHcloud | user, metadata, top_k, repetition_penalty, service_tier, prediction |
The answer is unaffected: user only labels the request for the
provider’s own abuse tracking, and a message name only labels a
participant. A field the provider spells differently is applied rather
than removed. Mistral’s random_seed receives your seed.
Some models refuse a field their provider otherwise accepts. For
example, Mistral models served by Berget and OVHcloud refuse a message
name. In that case, or when a provider starts refusing another
advisory field, LowRouter reads the field from the provider’s
validation error. It retries without the field and discloses it as
rejected_by_provider. When the error is a structured report, as
Mistral’s is, the field is also removed up front on later requests.
A refused field that changes the answer, such as logit_bias on
Mistral, is never removed. You get the 400, naming the field.
On /v1/messages, the Anthropic metadata object reaches the provider
as user. The disclosure names it metadata, which is what you sent.
/v1/responses does the same with its own names: see
the Responses API.
cache_control
A cache_control object on a content part is Anthropic’s explicit
prompt-cache breakpoint. Anthropic routes (including Claude on Bedrock
and Vertex) apply it, TTL included, and Bedrock turns it into its own
cache point. The other routes have no per-block breakpoint: an
OpenAI-compatible provider caches automatically or not at all (and
Mistral rejects the key outright), and Gemini’s caching is a separate
resource. On those routes the marker is removed before the
request leaves and disclosed as cache_control /
unsupported_on_this_path. The answer is unaffected; the disclosure
tells you that no cache discount applies on this route for that
reason. The usage block’s prompt_tokens_details.cached_tokens says
what actually was cached.
tool_result with an image or document
A tool result can carry an image or a document: on the OpenAI surface
as image_url / file parts in a tool-role message, on /v1/messages
as image / document blocks inside a tool_result. This is how Claude
Code delivers every screenshot and PDF its Read tool opens. Anthropic
routes (including Claude on Bedrock and Vertex) and Bedrock take those
blocks inside the tool result natively and get them as sent. Every
OpenAI-compatible provider and Gemini have a text-only tool result, so
on those routes the text stays in the result and the image or document
is moved into a user message placed immediately after it. The model
still sees the file, as a user turn rather than as the tool’s own
output, and the move is disclosed as tool_result /
unsupported_on_this_path, with the number of blocks moved.
service_tier
Only auto and default are forwarded. Any other value, such as
priority, flex or scale, is removed before the request leaves
and disclosed as service_tier / unsupported_on_this_path. Those
tiers have their own rates at the provider, and LowRouter bills every
request at the model’s standard rate. The request is served at the
standard tier, and that is what you pay for.
temperature, top_p and top_k on Anthropic
Anthropic’s newer Claude models refuse the sampling parameters: a
request that sets temperature, top_p or top_k gets a 400 from the
provider, whatever the value. Many clients set temperature by
default, so LowRouter does not pass that 400 on. It retries without the
parameter and discloses it as rejected_by_provider. After that, the
parameter is dropped up front for that model. Models that accept the
parameters still receive them.
LowRouter learns this from the provider’s response, not from the model
name, so there is nothing to configure. Two refusals still come back to
you as the provider’s 400, because neither means the model refuses the
parameter: a value outside the provider’s range (Anthropic’s
temperature stops at 1), and a combination the model does not allow
(temperature together with top_p on some models).
stream_options
stream_options.include_usage is accepted for compatibility and has no
effect: the final frame of every stream carries one complete usage
object (token counts, cost, currency and remaining_balance) plus
lowrouter_metadata, and it is sent whether the flag is true, false
or absent. It is the only place a streamed request can report what it
cost, so it is not optional. The frame has an empty choices array; a
client that indexes choices[0] unconditionally must skip it (the SDK
pages show the guard).
response_format
A json_schema schema is passed to the provider without changes.
Anthropic, Gemini and Bedrock apply it natively as structured output.
json_object needs a JSON mode that works without a schema. Anthropic
and Bedrock have none, so on those routes send json_schema with a
schema instead.
reasoning_effort
Accepted values: none, minimal, low, medium, high, xhigh.
Each provider gets its own form of the setting:
- Anthropic runs extended thinking. Claude 4.6 and later get
adaptive thinking at the matching effort level. Earlier models get a
token budget: 1024 / 2048 / 8192 / 16384 / 32768 tokens from
minimalup toxhigh. LowRouter works out which form a model takes from its response and remembers it, so there is nothing to configure. If you did not setmax_tokens, LowRouter raises its default to leave room for the thinking. - Gemini uses thinking levels on Gemini 3 and a thinking budget on Gemini 2.5, chosen the same way.
- OpenAI-compatible routes receive
reasoning_effortunchanged. - OpenAI’s newer reasoning models (the gpt-5.6 and gpt-6 families)
don’t take function tools together with reasoning on chat
completions. LowRouter sends those tool requests to OpenAI’s Responses
API instead, asking OpenAI not to store them, so you keep both the
tools and the reasoning. A request that can’t be expressed there (it
sets
n,logprobs,logit_bias,stop, a penalty,seed,top_korprediction, sends audio, or returns an image or document from a tool) is served with reasoning off, andreasoning_effortis disclosed asrejected_by_provider. These models take notemperatureortop_pwith reasoning on; those are disclosed asrejected_by_providerand the reasoning is kept.
none asks for nothing beyond the model’s default and is never
disclosed. The reasoning tokens are billed as output tokens.
The reasoning trace
On OpenAI-compatible routes, the model’s reasoning trace comes back in
reasoning_content, and reasoning carries the same text. They appear
on the message of a response and on the deltas of a stream. Providers
send the trace in several ways: under either of those names, as a list
of reasoning details, as thinking parts inside content, or as a
<think> block at the start of the answer. LowRouter returns it in the
same form whichever route served the request. content is always a
string, or absent from a delta. A message without a trace carries
neither field, so a client never reads an empty trace in place of a
real one.
To keep a model’s reasoning across tool calls, send the trace back on
the assistant message, under either name. LowRouter passes it to the
model as reasoning_content. Where the trace cannot be sent, the
request goes without it and the trace is disclosed as
messages.reasoning_content:
- Mistral and Groq refuse the field, so it is removed up front
(
unsupported_on_this_path, see the table above). - Anthropic, Gemini, Bedrock and requests served through OpenAI’s
Responses API have no field for it (
unsupported_on_this_path). - Any other route that refuses it with a validation report naming the
field is retried without it (
rejected_by_provider).
What this does not cover: Anthropic and Gemini routes do not return
the trace on chat completions, and neither does a provider that keeps
its trace hidden. In both cases the reasoning tokens are still billed
as output tokens, and are reported in
usage.completion_tokens_details.reasoning_tokens when the provider
counts them separately.
Usage details
usage breaks the token counts down in OpenAI’s own field names, so
clients that show cache hit rates and reasoning cost read them without
any changes:
"usage": {
"prompt_tokens": 8409,
"completion_tokens": 212,
"total_tokens": 8621,
"prompt_tokens_details": { "cached_tokens": 8192, "cache_write_tokens": 0 },
"completion_tokens_details": { "reasoning_tokens": 0 },
"cost": 0.000793,
"currency": "EUR"
}These are the counts the request was billed on, so the cost always
matches them. reasoning_tokens is filled in when the provider reports
reasoning tokens separately; otherwise they are included in
completion_tokens. The 5-minute and 1-hour split of cache writes is
on lowrouter_metadata.
Balance endpoints
OpenRouter-compatible clients display your balance by calling these two endpoints:
GET /v1/keyreturnsusage(all-time spend) andlimit_remaining(your current balance).limitisnullbecause LowRouter keys spend your account balance rather than a per-key cap.GET /v1/creditsreturnstotal_creditsandtotal_usage. The difference between them is your current balance.
Amounts are in your billing currency, named by currency. Unlike
GET /v1/auth/key, an empty balance returns 200 with a zero figure
instead of a 402.
