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:

JSON
"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.

reasonMeaning
unsupported_on_this_pathThe serving provider has no equivalent control.
rejected_by_providerThe provider refused the parameter for this model. The request was retried without it.
conflicts_with_samplingExtended 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_smallYour max_tokens leaves no room for a thinking budget.
unrecognized_parameterLowRouter 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_format json_schema
applied
response_format json_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
name on a message (disclosed as messages.name)
disclosed
cache_control (content-part marker)
applied
tool_result carrying 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:

ProviderRemoved and disclosed
Mistraluser, name on a message (disclosed as messages.name), a passed-back reasoning trace (messages.reasoning_content)
Groqa passed-back reasoning trace (messages.reasoning_content)
OVHclouduser, 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 minimal up to xhigh. LowRouter works out which form a model takes from its response and remembers it, so there is nothing to configure. If you did not set max_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_effort unchanged.
  • 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_k or prediction, sends audio, or returns an image or document from a tool) is served with reasoning off, and reasoning_effort is disclosed as rejected_by_provider. These models take no temperature or top_p with reasoning on; those are disclosed as rejected_by_provider and 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:

JSON
"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/key returns usage (all-time spend) and limit_remaining (your current balance). limit is null because LowRouter keys spend your account balance rather than a per-key cap.
  • GET /v1/credits returns total_credits and total_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.