Skip to content
kenari.

Reasoning

Some models think before they answer. They produce a reasoning trace first, then the final text. kenari gives you one set of parameters for this on every chat format, returns the trace next to the answer, and bills the thinking as Output tokens. More thinking usually means better answers on hard problems. It can also mean a slower reply and more tokens, which cost money wherever Output tokens are billed.

GET /v1/models describes reasoning for each model. The call is public, so you can filter it without a key:

Terminal window
curl -s https://kenari.id/v1/models | jq -r '.data[] | select(.reasoning == true) | .id'
FieldMeaning
reasoningtrue when the model can think.
reasoning_optionsThe effort levels the model accepts, for example ["low", "medium", "high"]. Absent when the model has no effort levels.
reasoning_toggletrue when you can switch reasoning on or off per request.

The levels are none, minimal, low, medium, high, xhigh and max. none turns reasoning off, and minimal is treated as low. If you ask for a level the model does not list, kenari uses the highest listed level at or below it. If the model lists no level at or below the one you asked for, the level you asked for is sent unchanged. On a model that does not support reasoning, the reasoning fields are ignored.

Each format has its own field. kenari maps all of them to the native reasoning control of the model’s provider.

FormatHow to ask
Chat completionsreasoning_effort, or the reasoning object below
Messagesthinking: {"type": "enabled", "budget_tokens": N}, or output_config: {"effort": "high"}
Responsesreasoning: {"effort": "high"}

With Chat completions:

Terminal window
curl https://kenari.id/v1/chat/completions \
-H "Authorization: Bearer $KENARI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "step-3-7-flash:free",
"reasoning_effort": "medium",
"messages": [
{"role": "user", "content": "What is 17 * 24? Show the steps."}
]
}'

With Messages. Point the Anthropic SDK at https://kenari.id, without /v1:

import os
import anthropic
client = anthropic.Anthropic(
base_url="https://kenari.id",
api_key=os.environ["KENARI_API_KEY"],
)
message = client.messages.create(
model="step-3-7-flash:free",
max_tokens=4096,
thinking={"type": "enabled", "budget_tokens": 2048},
messages=[{"role": "user", "content": "What is 17 * 24? Show the steps."}],
)
for block in message.content:
if block.type == "thinking":
print("Reasoning:", block.thinking)
elif block.type == "text":
print("Answer:", block.text)

thinking: {"type": "disabled"} turns reasoning off on Messages only for models served through an Anthropic-style API. For other models it removes the effort setting, and the model’s default applies. To turn reasoning off on a model that supports it (reasoning_toggle is true), use reasoning: {"enabled": false} on Chat completions. A budget must be at least 1,024 and below max_tokens. At a max_tokens of 1,024 or less, models served through an Anthropic-style API get no thinking at all.

Chat completions also accepts an OpenRouter-style reasoning object, which you can send on every chat model:

FieldTypeDescription
effortstringOne of the levels above.
enabledbooleanfalse turns reasoning off. true without effort or max_tokens turns it on at medium.
max_tokensintegerA token budget for the thinking. Models that take a level instead of a budget may ignore it, so prefer effort.
excludebooleantrue hides the trace from the response. The thinking is still generated and billed.

When a request has both reasoning and reasoning_effort, the reasoning object wins.

Terminal window
curl https://kenari.id/v1/chat/completions \
-H "Authorization: Bearer $KENARI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "step-3-7-flash:free",
"reasoning": {"enabled": false},
"messages": [
{"role": "user", "content": "In one word, what is the capital of Indonesia?"}
]
}'

kenari turns enabled: false into the control the provider needs, so your code does not change per model. A model without reasoning_toggle set to true may keep thinking.

Some coding tools offer only a few levels, or send none for a model they do not know. To pick the level anyway, add it to the model id after a colon:

Terminal window
curl https://kenari.id/v1/chat/completions \
-H "Authorization: Bearer $KENARI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4-1-flash:free:max",
"messages": [
{"role": "user", "content": "What is 17 * 24? Show the steps."}
]
}'

kenari removes the suffix and runs the model at that level, whatever the request asks for. It writes the level into reasoning_effort, the reasoning object, thinking and output_config, and it also overrides a request that turns reasoning off. Everything else sees the plain model id: the model in the response, your key’s model restrictions, billing and the usage log. The x-kenari-gated-effort response header shows the level kenari sent.

  • The level must be one the model lists in reasoning_options. A level the model does not list, such as deepseek-v4-pro:medium, is refused with a 400 that names the levels the model has. none works only on a model that lists it, and turns reasoning off.
  • kenari/auto and a route accept low, medium, high, xhigh and max. Each model they call gets its highest listed level at or below that one. A byok/ id sends the level to your provider unchanged.
  • The suffix combines with :free in either order. deepseek-v4-1-flash:free:max and deepseek-v4-1-flash:max:free are the same model.
  • Ids with a level are not listed in GET /v1/models. Type the id into your tool as a custom model name. Some tools look up the context window by model name, so set it by hand if your tool has that option.
  • A route name cannot end in a level, for example fast:high.

The suffix is a kenari extension. OpenRouter uses the same colon form for its model variants, but none of its variants sets a reasoning level.

Where the trace appears depends on the format.

On Chat completions it is in choices[].message.reasoning. The same text is also in reasoning_content, for tools that read that name.

{
"choices": [
{
"message": {
"role": "assistant",
"reasoning": "17 * 24 = 17 * 20 + 17 * 4 = 340 + 68 ...",
"reasoning_content": "17 * 24 = 17 * 20 + 17 * 4 = 340 + 68 ...",
"content": "17 * 24 = 408."
},
"finish_reason": "stop"
}
]
}

On Messages it is a thinking block before the text block:

{
"content": [
{"type": "thinking", "thinking": "17 * 24 = 17 * 20 + 17 * 4 ..."},
{"type": "text", "text": "17 * 24 = 408."}
]
}

On Responses it is a reasoning item at the start of output, with the text in summary[].text. In a stream the trace arrives before the answer. See Streaming for the events of each format.

kenari does not shorten or filter the trace. It is the text the model produced. Some models return no trace, even when they think.

Reasoning tokens are billed as Output tokens, at the same rate as answer tokens. A higher effort level or a larger budget can mean more tokens, and so a higher charge on models that bill them. :free models cost nothing. exclude: true changes nothing about the bill, because the thinking still happens. To spend less, lower effort or turn reasoning off. See How billing works.

Thinking uses part of max_tokens. If max_tokens is small, the thinking can use all of it before the answer starts, and the response has no answer text. kenari reports this as a cut-off, not as a normal stop:

FormatSignal
Chat completionsfinish_reason is "length"
Messagesstop_reason is "max_tokens"
Responsesstatus is "incomplete" with incomplete_details: {"reason": "max_output_tokens"}

The thinking tokens are still billed. Give a reasoning model enough max_tokens for the thinking and the answer together, or lower the effort.