# 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.

## Find models that support reasoning

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

```bash
curl -s https://kenari.id/v1/models | jq -r '.data[] | select(.reasoning == true) | .id'
```

| Field | Meaning |
| --- | --- |
| `reasoning` | `true` when the model can think. |
| `reasoning_options` | The effort levels the model accepts, for example `["low", "medium", "high"]`. Absent when the model has no effort levels. |
| `reasoning_toggle` | `true` 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.

## Request reasoning

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

| Format | How to ask |
| --- | --- |
| Chat completions | `reasoning_effort`, or the `reasoning` object below |
| Messages | `thinking: {"type": "enabled", "budget_tokens": N}`, or `output_config: {"effort": "high"}` |
| Responses | `reasoning: {"effort": "high"}` |

With Chat completions:

```bash
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`:

```python
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](/en/docs/chat). 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.

## The reasoning object

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

| Field | Type | Description |
| --- | --- | --- |
| `effort` | string | One of the levels above. |
| `enabled` | boolean | `false` turns reasoning off. `true` without `effort` or `max_tokens` turns it on at `medium`. |
| `max_tokens` | integer | A token budget for the thinking. Models that take a level instead of a budget may ignore it, so prefer `effort`. |
| `exclude` | boolean | `true` 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.

```bash
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.

## Set the level in the model id

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:

```bash
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.

## Read the reasoning trace

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.

```json
{
  "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:

```json
{
  "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](/en/docs/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.

## Billing

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](/en/docs/billing).

<aside class="warn"><strong>Warning</strong> High effort levels can produce very long traces. Match the level to the task instead of leaving it at the maximum.</aside>

## Small max_tokens and empty answers

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:

| Format | Signal |
| --- | --- |
| Chat completions | `finish_reason` is `"length"` |
| Messages | `stop_reason` is `"max_tokens"` |
| Responses | `status` 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.

## See also

- [Streaming](/en/docs/streaming)
- [Chat completions](/en/docs/chat)
- [Messages](/en/docs/messages)
- [Models and pricing](/en/docs/models)
