# Reasoning

Models that think before they answer: how much they think, how to cap it, and where the thinking comes back.

Most chat models here reason: they write their thinking first, then the answer. You choose how hard they think, can cap how long, and get the thinking back beside the answer.

## How hard to think

| Route | The control |
| --- | --- |
| `/v1/chat/completions` | `reasoning.effort`, or the shorthand `reasoning_effort` |
| `/v1/responses` | `reasoning.effort` |
| `/v1/messages` | `output_config.effort`, or `thinking` (below) |

Effort runs from `minimal` through `low`, `medium`, `high`, `xhigh` and `max` to `ultra`. Leave it out and the model decides. Each model lists the levels it takes on [its page](/docs/models), and a level it does not take is a 400.

```bash
curl https://api.vultrinference.com/v1/chat/completions \
  -H "Authorization: Bearer $VULTR_INFERENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4.1-flash",
    "reasoning": {"effort": "low"},
    "messages": [{"role": "user", "content": "Is 221 a prime number?"}]
  }'
```

```python
import os

from openai import OpenAI

client = OpenAI(base_url="https://api.vultrinference.com/v1", api_key=os.environ["VULTR_INFERENCE_API_KEY"])

reply = client.chat.completions.create(
    model="deepseek-v4.1-flash",
    messages=[{"role": "user", "content": "Is 221 a prime number?"}],
    # Not one of OpenAI's own fields, so the SDK sends it as extra body
    extra_body={"reasoning": {"effort": "low"}},
)
message = reply.choices[0].message
print("thought:", getattr(message, "reasoning", None))
print("answer:", message.content)
print("reasoning tokens:", reply.usage.completion_tokens_details.reasoning_tokens)
```

To turn thinking off, send effort `none` or `"reasoning": {"enabled": false}`. A few models think whatever you send.

## A cap on thinking

A budget caps how many tokens the model may spend thinking. It must be below the cap on the whole answer.

| Route | The budget |
| --- | --- |
| `/v1/chat/completions`, `/v1/responses` | `reasoning.max_tokens` |
| `/v1/messages` | `thinking: {"type": "enabled", "budget_tokens": n}`, at least 1,024 |

Not every model can hold to a budget; those that can say `supports_max_tokens` in their catalog entry. The others ignore it on chat completions and responses, and refuse it with a 400 on messages. On messages, `thinking: {"type": "adaptive"}` leaves the amount to the model.

## Where the thinking comes back

| Route | The thinking | Its tokens |
| --- | --- | --- |
| `/v1/chat/completions` | `message.reasoning` | `usage.completion_tokens_details.reasoning_tokens` |
| `/v1/responses` | a `reasoning` output item | `usage.output_tokens_details.reasoning_tokens` |
| `/v1/messages` | a `thinking` block | |

On chat completions it is always `reasoning`, whichever engine serves the model, and `delta.reasoning` in a stream. [SDKs and compatibility](/docs/get-started/sdks#one-chat-shape) shows a whole answer.

On messages, each `thinking` block comes with a `signature`. Send the block back on the next turn and the model sees its earlier thinking. With `display: "omitted"` the block arrives with empty text, but still with its signature.

## Billing

Thinking tokens are output tokens: `completion_tokens` already includes them, and they are billed at the model's output price.
