Reasoning
One dial, three spellings. reasoning, reasoning_effort and thinking all work on every reasoning model here, whichever wire format serves it.
The field
Three fields mean the same thing, depending on whose client library you started from: reasoning (OpenRouter’s), reasoning_effort (OpenAI’s) and thinking (Anthropic’s). All three are accepted here, and all three reach the provider in the spelling that provider accepts — so the same request body works whichever route serves it. Send one of them: a body carrying two is refused rather than guessed at.
{
"model": "anthropic/claude-sonnet-5",
"messages": [{ "role": "user", "content": "Prove it." }],
"max_tokens": 10000,
"reasoning": { "effort": "high" }
}| The reasoning object | Description |
|---|---|
| reasoning.effort | One of none, minimal, low, medium, high, xhigh, max — narrowed to the set the model publishes. A level the model does not publish is a 400 naming the ones it has, rather than an upstream error that says only “invalid value”. |
| reasoning.max_tokens | A thinking budget in tokens. Honoured exactly on the providers that have such a dial, and refused — not rounded into an effort level — on the ones that do not. |
| reasoning.exclude | Think, but leave the reasoning out of the response. The tokens are still generated and still billed; you simply do not receive the text. |
| reasoning.enabled | false turns reasoning off where the model has an off switch. On a model that always reasons it changes nothing, which is the same answer as sending no field at all. |
What each provider receives
- OpenRouter understands
reasoningnatively, so whatever you sent is normalised into that one field and forwarded — budget andexcludeincluded. - OpenAI and the strict clones (Groq, Together, DeepInfra) take
reasoning_effortand nothing else. An effort level is translated to it; a token budget is refused, because these APIs have no field that expresses one. - Anthropic routes take
thinking.budget_tokens. An effort level becomes a budget by a published fraction of yourmax_tokens—high80%,medium50%,low20%, withminimal,xhighandmaxcontinuing the same ramp — clamped to the model’s own ceiling and to a 1,024-token floor. Those are OpenRouter’s fractions, matched deliberately so a client moved across gets the same amount of thinking for the same word.
high on the default 4,096-token ceiling leaves about 800 tokens to answer in; max leaves about 200, and the answer is truncated with finish_reason: "length". Raise max_tokens alongside the effort rather than expecting the budget to be capped for you — a cap would make three settings produce one number, and hide the trade instead of showing it.What comes back
Reasoning is returned beside the answer, never mixed into it: message.reasoning on a completion and delta.reasoning on a stream. Concatenating the two would show your users the model talking to itself as though it were talking to them.
{
"choices": [{
"message": {
"role": "assistant",
"content": "Four.",
"reasoning": "Let me count them one at a time…",
"reasoning_details": [{
"type": "reasoning.text",
"text": "Let me count them one at a time…",
"signature": "EqQBCgIYAhIm…",
"format": "anthropic-claude-v1",
"index": 0
}]
}
}]
}reasoning_details is the same content in the form a provider needs back. Anthropic signs each thinking block and requires the signed block be returned verbatim on the next turn of a tool-using conversation, so a conversation that drops it works exactly once. Send the array back on the assistant message and it is put where it belongs; an unsigned block is dropped rather than forged. On a stream the text arrives as reasoning and again inside reasoning_details, and the signature follows in a later frame carrying the same index.
{
"model": "anthropic/claude-sonnet-5",
"messages": [{ "role": "user", "content": "Prove it." }],
"max_tokens": 16000,
"reasoning": { "max_tokens": 8000, "exclude": true }
}What is refused, and why
A dropped reasoning parameter is invisible: the answer comes back fast, cheap and un-reasoned, and looks exactly like one that took the long route. So anything that cannot be honoured is a 400 that names it — and the route filter tries every other candidate first, so you only see the error when no route can serve the request as written.
- A model that does not reason, or an effort level it does not publish.
- A token budget on a provider whose API has no budget field; or one at or above your own
max_tokens; or one below the 1,024-token minimum. None of the three is quietly adjusted — each would spend a different amount of your money than the number you wrote down. - On Anthropic routes, extended thinking alongside
temperatureother than 1, anytop_k,top_poutside 0.95–1, or a forcedtool_choice. Those are the Messages API’s own constraints; dropping the offending field instead would silently un-set a control you are relying on.
What it costs
Reasoning tokens are completion tokens, and are billed as completion tokens by every provider and by us. They are reported in usage.completion_tokens_details.reasoning_tokens where the provider breaks them out, and counted in completion_tokens either way — which is why an empty answer that thought for 50,000 tokens is not covered by zero-completion insurance: those tokens were real and were billed to us.
The dial is part of the cache key, so the same question at low and at high are two questions and return two answers. That is deliberate: benchmarking a model at one effort and then another used to return the first answer twice.
Something here disagrees with what the API actually did? That is a bug in this page, and worth reporting.
Report it