Skip to content

Reasoning

One dial, three spellings. reasoning, reasoning_effort and thinking all work on every reasoning model here, whichever wire format serves it.

6 min read

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.

Request body
{
  "model": "anthropic/claude-sonnet-5",
  "messages": [{ "role": "user", "content": "Prove it." }],
  "max_tokens": 10000,
  "reasoning": { "effort": "high" }
}
The reasoning objectDescription
reasoning.effortOne 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_tokensA 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.excludeThink, but leave the reasoning out of the response. The tokens are still generated and still billed; you simply do not receive the text.
reasoning.enabledfalse 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 reasoning natively, so whatever you sent is normalised into that one field and forwarded — budget and exclude included.
  • OpenAI and the strict clones (Groq, Together, DeepInfra) take reasoning_effort and 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 your max_tokenshigh 80%, medium 50%, low 20%, with minimal, xhigh and max continuing 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.
Thinking comes out of max_tokens
On every provider with this dial, reasoning tokens are spent from the same ceiling as the answer. 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.

Response
{
  "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.

A budget, with the working hidden
{
  "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 temperature other than 1, any top_k, top_p outside 0.95–1, or a forced tool_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