> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sunra.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Token usage

Every LLM response carries a `usage` object reporting the tokens the request consumed. Sunra reports usage in the semantics of the endpoint you called: a Messages response follows the Anthropic contract, and a Chat Completions or Responses response follows the OpenAI contract.

These shapes differ, deliberately. An OpenAI SDK reading a Chat Completions response assumes OpenAI semantics, and an Anthropic SDK reading a Messages response assumes Anthropic semantics. Each endpoint honors what its own specification promises rather than being forced into one shared shape.

| Endpoint               | Prompt count field | Are cached tokens included in it?                                     |
| ---------------------- | ------------------ | --------------------------------------------------------------------- |
| `/v1/messages`         | `input_tokens`     | No — the three input buckets are mutually exclusive                   |
| `/v1/chat/completions` | `prompt_tokens`    | Yes — cached tokens are a subset, detailed in `prompt_tokens_details` |
| `/v1/responses`        | `input_tokens`     | Yes — cached tokens are a subset, detailed in `input_tokens_details`  |

<Warning>
  `/v1/messages` and `/v1/responses` both use the field name `input_tokens`, and it does not mean the same thing on both. On Messages it is fresh input only. On Responses it is the entire prompt, cached tokens included.
</Warning>

## Messages

On `/v1/messages`, the three input buckets are **mutually exclusive**. No token is counted twice, so the prompt total is their sum:

```
total prompt = input_tokens + cache_creation_input_tokens + cache_read_input_tokens
```

<ResponseField name="input_tokens" type="integer">
  Fresh input tokens only. Excludes both cache buckets.
</ResponseField>

<ResponseField name="cache_creation_input_tokens" type="integer">
  Tokens written to the cache by this request.
</ResponseField>

<ResponseField name="cache_read_input_tokens" type="integer">
  Tokens served to this request from the cache.
</ResponseField>

<ResponseField name="output_tokens" type="integer">
  Tokens generated by the model.
</ResponseField>

<ResponseField name="total_tokens" type="integer">
  The three input buckets plus `output_tokens`. Present on non-streaming responses.
</ResponseField>

A bucket that is absent or `null` counts as zero.

### Worked example

The same 19,000-token prefix sent twice against `claude-opus-4-8`. The first request writes the cache; the second reads it.

```json Cold request (cache write) theme={null}
{
  "usage": {
    "input_tokens": 8,
    "cache_creation_input_tokens": 19349,
    "cache_read_input_tokens": 0,
    "output_tokens": 2,
    "total_tokens": 19359,
    "sunra_usage_semantics": "anthropic.exclusive.v1"
  }
}
```

```json Warm request (cache read) theme={null}
{
  "usage": {
    "input_tokens": 8,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 19349,
    "output_tokens": 2,
    "total_tokens": 19359,
    "sunra_usage_semantics": "anthropic.exclusive.v1"
  }
}
```

`input_tokens` stays at 8 across both requests, because 8 is the genuinely new input each time. The 19,349-token prefix moves from the creation bucket to the read bucket. Both requests sum to the same 19,359 total tokens but do not cost the same: cache writes and cache reads are priced at their own per-token rates, which is why they are reported as separate buckets.

## Chat Completions and Responses

These endpoints report OpenAI semantics, unchanged. The prompt count is the **whole** prompt, and cached tokens are a **subset** of it reported separately.

```json /v1/chat/completions theme={null}
{
  "usage": {
    "prompt_tokens": 19357,
    "completion_tokens": 2,
    "total_tokens": 19359,
    "prompt_tokens_details": {
      "cached_tokens": 19349
    }
  }
}
```

Summing `prompt_tokens` and `cached_tokens` double-counts. To get the fresh input on these endpoints, subtract:

```
fresh input = prompt_tokens - prompt_tokens_details.cached_tokens
```

The same rule applies to `/v1/responses` with `input_tokens` and `input_tokens_details.cached_tokens`.

## The `sunra_usage_semantics` marker

Responses that Sunra has normalized carry a marker inside `usage`:

```json theme={null}
"sunra_usage_semantics": "anthropic.exclusive.v1"
```

The marker is a promise about the **response**, not about any individual event or field. On a non-streaming response it appears in the root `usage` object; on a streamed response it appears in `message_start`, the event that carries the input buckets. When it is present with this value, the guarantees on this page hold: the three input buckets are mutually exclusive, and `total_tokens` — where present — is their sum plus `output_tokens`.

**Its absence is also a promise.** Sunra normalizes only response shapes it has measured. Anything else is forwarded from the upstream provider untouched and left unmarked, and a response without the marker is one whose buckets Sunra does not vouch for.

Assert on the marker rather than inspecting the numbers to guess which convention a response follows. Shape sniffing is what this field exists to replace — a heuristic such as "the buckets must be exclusive because they sum to more than the prompt count" is satisfied by more than one convention and will eventually read a response wrong.

```python theme={null}
usage = response["usage"]

if usage.get("sunra_usage_semantics") == "anthropic.exclusive.v1":
    total_prompt = (
        usage["input_tokens"]
        + usage.get("cache_creation_input_tokens", 0)
        + usage.get("cache_read_input_tokens", 0)
    )
else:
    # Not normalized by Sunra. Treat the upstream shape as unverified
    # and consult that provider's own documentation.
    total_prompt = None
```

Only `/v1/messages` responses ever carry the marker. Chat Completions and Responses follow the OpenAI specification and are not marked.

The value is versioned. A breaking change to the meaning of these fields ships under a new value, so an equality check against `anthropic.exclusive.v1` will not silently start reading a different contract.

## Streaming

On a streamed Messages request, usage arrives across two events. `message_start` carries the input side and the marker. The terminal `message_delta` carries `output_tokens` only. Assert on the marker in `message_start`, then merge the two events as usual — apply the later event's usage over the earlier one — to arrive at the values documented above.

`total_tokens` is omitted from streamed responses. No single event knows both the input and output side, so any total computed mid-stream would be wrong. Sum the buckets yourself once the stream completes.

## Migrating from the previous behavior

Sunra previously forwarded the upstream `usage` object verbatim on `/v1/messages`. The translation layer in front of some providers folds cache-creation tokens **into** `input_tokens`, so a caller who followed the Anthropic contract and summed the three buckets counted the cache-creation tokens twice.

* **If you sum the three buckets**, as the Anthropic contract describes, you are now correct. No change is required on your side.
* **If you compensated for the old behavior** — subtracting `cache_creation_input_tokens` out of `input_tokens` yourself, or otherwise reverse-engineering the fold — **stop**. That correction now subtracts tokens that were already excluded and will understate your input.
* **If you read `total_tokens`**, it continues to report the full count: fresh input, both cache buckets, and output.

Gate the change on the marker rather than on a deploy date, so the same code path is correct against both marked and unmarked responses.
