The anatomy of a usage block

TL;DR. Every API response carries a usage object that is the ground truth of what you were billed, and most people misread its most important field. input_tokens is not your prompt size; it is only the uncached remainder. The full prompt is input_tokens + cache_creation_input_tokens + cache_read_input_tokens, each priced at a different rate (1x, 1.25x or 2x, 0.1x), plus output_tokens at five times the input rate. This chapter dissects every field, prices a six-turn agent session block by block with a runnable lab, and shows the same fields inside a real Claude Code transcript, so a usage block stops being a mystery number and becomes something you can predict before the response arrives.

Contents

Chapter 2 established what a token costs and Chapter 6 established why cached tokens cost a tenth as much. This chapter is where those prices meet the wire: the usage object the API attaches to every single response. It is the receipt for one request, the input to every cost dashboard, and the number that /usage, ccusage, and every observability tool ultimately aggregates. If you can read one usage block precisely, everything in the measurement chapters that follow is just summing them.

Where the usage block lives

Every call to the Messages API returns a Message object, and every Message carries usage:

# Illustrative: requires the anthropic SDK and an API key.
import anthropic
client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    system=[{"type": "text", "text": BIG_SYSTEM_PROMPT,
             "cache_control": {"type": "ephemeral"}}],
    messages=[{"role": "user", "content": "Summarize the design doc."}],
)
print(response.usage)

The same object shows up in more places than the SDK. It is written verbatim into Claude Code's transcript files for every assistant turn (Chapter 25 mines those), it arrives inside streaming events, and batch results carry one per request. Anywhere tokens were billed, this block is the record.

The four core fields, and the identity

FieldWhat it countsPrice (opus-4-8)
input_tokensPrompt tokens processed at the full input rate: everything not covered by the cache$5.00 / Mtok (1x)
cache_creation_input_tokensPrompt tokens written into the prompt cache this request1.25x (5-minute TTL) or 2x (1-hour TTL)
cache_read_input_tokensPrompt tokens served from the cache0.1x
output_tokensTokens the model wrote, including any thinking$25.00 / Mtok (5x)

The identity that makes the input side make sense:

$$\text{full prompt size} = \text{input} + \text{cache_creation} + \text{cache_read}$$

Every prompt token lands in exactly one of the three buckets. Which bucket depends entirely on the cache state and the breakpoint placement from Chapter 6: tokens covered by a warm cache entry are cache_read, tokens newly stored are cache_creation, and whatever sits after the last breakpoint (or missed the cache) is plain input.

Don't be confused. input_tokens does not mean "how big my prompt was". An agent two hours into a session can show input_tokens: 118 on a 200,000-token prompt, because 199,882 of those tokens were served from cache. If you monitor prompt growth, alert on the sum of all three fields. If you monitor cost, weight each field by its own rate. Reading input_tokens alone gives you a number that is simultaneously too small to be the prompt and too crude to be the bill.

The cost of one block is therefore a four-term formula, not a two-term one:

$$\text{cost} = \text{in} \cdot r + \text{write}{5m} \cdot 1.25r + \text{write}{1h} \cdot 2r + \text{read} \cdot 0.1r + \text{out} \cdot 5r$$

where $r$ is the model's per-token input rate, and the write terms come from the cache_creation sub-object described below.

The lab: a six-turn session, priced block by block

The lab rebuilds this accounting for a small agent session: a 14,000-token stable prefix, six turns, a cache breakpoint on the latest turn (the standard multi-turn pattern from Chapter 6). It emits one usage block per turn and prices each with the formula above.

"""The anatomy of a usage block, from scratch.

Every response from the Anthropic API carries a `usage` object that is the
ground truth of what you were billed. This lab rebuilds that accounting for a
small multi-turn agent session, turn by turn, so each field stops being a
mystery number and becomes something you can predict before the response
arrives.

The four core fields, and the one identity that makes them make sense:

  input_tokens                 tokens processed at the FULL input rate
  cache_creation_input_tokens  tokens WRITTEN to the prompt cache (premium)
  cache_read_input_tokens      tokens READ from the prompt cache (~0.1x)
  output_tokens                tokens the model wrote (the 5x-priced half)

  full prompt size = input_tokens + cache_creation_input_tokens
                     + cache_read_input_tokens

That identity is the single most misread thing in the API. `input_tokens` is
NOT "how big my prompt was"; it is only the uncached remainder. An agent that
has been running for an hour can show input_tokens=118 on a 200,000-token
prompt, because the other 199,882 were served from cache.

Newer responses also break the cache write down by TTL:

  cache_creation.ephemeral_5m_input_tokens   written with the 5-minute TTL (1.25x)
  cache_creation.ephemeral_1h_input_tokens   written with the 1-hour TTL  (2x)

Run with the standard library only.
"""

import json

# Real Anthropic prices, per token (claude-opus-4-8: $5 in / $25 out per 1M).
IN_RATE = 5.00 / 1_000_000
OUT_RATE = 25.00 / 1_000_000
READ_RATE = IN_RATE * 0.10       # a cache hit costs ~0.1x the input rate
WRITE_5M_RATE = IN_RATE * 1.25   # writing with the default 5-minute TTL
WRITE_1H_RATE = IN_RATE * 2.00   # writing with the 1-hour TTL


def cost_of(usage):
    """Price one usage block, the same way the bill does."""
    w5 = usage["cache_creation"]["ephemeral_5m_input_tokens"]
    w1 = usage["cache_creation"]["ephemeral_1h_input_tokens"]
    return (
        usage["input_tokens"] * IN_RATE
        + w5 * WRITE_5M_RATE
        + w1 * WRITE_1H_RATE
        + usage["cache_read_input_tokens"] * READ_RATE
        + usage["output_tokens"] * OUT_RATE
    )


# ----------------------------------------------------------------------------
# The session we will account for: a 6-turn agent loop.
#
# The prompt each turn is three parts, in render order:
#   PREFIX   tools + system prompt + CLAUDE.md   (stable, 14,000 tokens)
#   HISTORY  everything said and read so far     (grows every turn)
#   NEW      this turn's user message or tool result
#
# The harness puts a cache breakpoint on the latest turn, the standard
# multi-turn pattern. So on each request:
#   - the PREFIX and all PRIOR history are found in the cache  -> cache_read
#   - the tokens appended since the last request are written   -> cache_creation
#   - anything after the last breakpoint is plain              -> input_tokens
# We use the 5m TTL here; the coding-agent harnesses use 1h (same math, 2x
# write premium instead of 1.25x).
# ----------------------------------------------------------------------------

PREFIX = 14_000
TURNS = [
    # (what happened, new prompt tokens appended this turn, output tokens)
    ("user asks for the feature",           220,   350),
    ("tool result: read main.py",         4_100,   420),
    ("tool result: read tests",           3_500,   380),
    ("tool result: ran test suite",       2_800,   510),
    ("tool result: applied the edit",       900,   460),
    ("user: 'also update the docs'",         60,   300),
]


def simulate(cached=True):
    """Return the list of per-turn usage blocks for the session."""
    blocks = []
    history = 0          # prompt tokens accumulated in the conversation
    cached_upto = 0      # how much of the prompt is already in the cache
    for _, new_tokens, out_tokens in TURNS:
        prompt = PREFIX + history + new_tokens
        if cached:
            read = cached_upto                 # everything cached so far
            write = prompt - cached_upto       # the newly appended part
            plain = 0
            cached_upto = prompt               # breakpoint moves to the end
        else:
            read, write, plain = 0, 0, prompt  # no caching: all full price
        blocks.append({
            "input_tokens": plain,
            "cache_creation_input_tokens": write,
            "cache_read_input_tokens": read,
            "output_tokens": out_tokens,
            "cache_creation": {
                "ephemeral_5m_input_tokens": write,
                "ephemeral_1h_input_tokens": 0,
            },
        })
        # The model's output becomes part of the next turn's prompt too.
        history += new_tokens + out_tokens
    return blocks


def main():
    print("=== One agent session, six usage blocks ===")
    print(f"Stable prefix {PREFIX:,} tok; breakpoint on the latest turn; 5m TTL.\n")
    header = (f"{'turn':<34}{'input':>7}{'write':>8}{'read':>9}"
              f"{'out':>6}{'prompt':>9}{'cost':>9}")
    print(header)
    print("-" * len(header))
    blocks = simulate(cached=True)
    total = 0.0
    for (label, _, _), u in zip(TURNS, blocks):
        prompt = (u["input_tokens"] + u["cache_creation_input_tokens"]
                  + u["cache_read_input_tokens"])
        c = cost_of(u)
        total += c
        print(f"{label:<34}{u['input_tokens']:>7,}"
              f"{u['cache_creation_input_tokens']:>8,}"
              f"{u['cache_read_input_tokens']:>9,}"
              f"{u['output_tokens']:>6,}{prompt:>9,}{c:>9.4f}")
    print("-" * len(header))
    print(f"{'session total':<34}{'':>7}{'':>8}{'':>9}{'':>6}{'':>9}{total:>9.4f}\n")

    print("Read the table columns like an auditor:")
    print("  - 'prompt' (the real prompt size) GROWS every turn; 'input' stays 0")
    print("    because the breakpoint pattern leaves nothing after the marker.")
    print("  - 'read' is last turn's 'read' + 'write': the cache accretes.")
    print("  - The expensive column is 'out': it is 5x per token, and unlike")
    print("    the prompt it is paid at full price every single turn.\n")

    uncached_total = sum(cost_of(u) for u in simulate(cached=False))
    print("=== The same session with caching off ===")
    print(f"  with caching:    ${total:.4f}")
    print(f"  without caching: ${uncached_total:.4f}"
          f"   ({uncached_total / total:.1f}x more)\n")

    # The identity, verified on the last turn's block.
    u = blocks[-1]
    print("=== The identity, checked on the final turn ===")
    print(json.dumps(u, indent=2))
    lhs = (u["input_tokens"] + u["cache_creation_input_tokens"]
           + u["cache_read_input_tokens"])
    print(f"\n  input + write + read = {lhs:,} tokens  (the FULL prompt)")
    print("  Never read input_tokens alone as 'prompt size'. Sum all three.")


if __name__ == "__main__":
    main()

Running it:

=== One agent session, six usage blocks ===
Stable prefix 14,000 tok; breakpoint on the latest turn; 5m TTL.

turn                                input   write     read   out   prompt     cost
----------------------------------------------------------------------------------
user asks for the feature               0  14,220        0   350   14,220   0.0976
tool result: read main.py               0   4,450   14,220   420   18,670   0.0454
tool result: read tests                 0   3,920   18,670   380   22,590   0.0433
tool result: ran test suite             0   3,180   22,590   510   25,770   0.0439
tool result: applied the edit           0   1,410   25,770   460   27,180   0.0332
user: 'also update the docs'            0     520   27,180   300   27,700   0.0243
----------------------------------------------------------------------------------
session total                                                               0.2878

Read the table columns like an auditor:
  - 'prompt' (the real prompt size) GROWS every turn; 'input' stays 0
    because the breakpoint pattern leaves nothing after the marker.
  - 'read' is last turn's 'read' + 'write': the cache accretes.
  - The expensive column is 'out': it is 5x per token, and unlike
    the prompt it is paid at full price every single turn.

=== The same session with caching off ===
  with caching:    $0.2878
  without caching: $0.7412   (2.6x more)

=== The identity, checked on the final turn ===
{
  "input_tokens": 0,
  "cache_creation_input_tokens": 520,
  "cache_read_input_tokens": 27180,
  "output_tokens": 300,
  "cache_creation": {
    "ephemeral_5m_input_tokens": 520,
    "ephemeral_1h_input_tokens": 0
  }
}

  input + write + read = 27,700 tokens  (the FULL prompt)
  Never read input_tokens alone as 'prompt size'. Sum all three.

Three things in that table are worth staring at, because they recur in every real session you will ever audit:

  1. The write column is the delta, the read column is the history. Each turn writes only what was appended since the last request (the previous turn's output plus the new tool result), and reads everything before it. read on turn $n$ equals read + write on turn $n-1$: the cache accretes, and the usage block shows you the accretion directly.
  2. input_tokens can legitimately be zero. With a breakpoint on the latest turn, nothing sits after the marker, so the plain-rate bucket is empty. Zero is not an error and not "free"; the money moved into the write and read columns.
  3. Per turn, output dominates the cost even though it is 100x smaller than the prompt. On the final turn, 27,700 prompt tokens cost about $0.017 (mostly at 0.1x) while 300 output tokens cost $0.0075. That is Chapter 2's 5x asymmetry compounded by Chapter 6's 0.1x reads: on a warm cache, a prompt token is effectively fifty times cheaper than an output token.

The rest of the fields, from a real block

The four core fields are the accounting; the rest of the block is the metadata that explains the accounting. Here is an unedited usage object from an assistant turn in this machine's own Claude Code transcript (how to find these files is Chapter 25's subject):

"usage": {
  "input_tokens": 6199,
  "cache_creation_input_tokens": 6052,
  "cache_read_input_tokens": 15853,
  "output_tokens": 318,
  "server_tool_use": { "web_search_requests": 0, "web_fetch_requests": 0 },
  "service_tier": "standard",
  "cache_creation": {
    "ephemeral_1h_input_tokens": 6052,
    "ephemeral_5m_input_tokens": 0
  },
  "inference_geo": "not_available",
  "iterations": [ { "type": "message", "input_tokens": 6199, ... } ],
  "speed": "standard"
}

Field by field:

FieldWhat it tells you
cache_creationThe write, split by TTL. This block shows 6,052 tokens written with the 1-hour TTL and none with the 5-minute one: Claude Code caches on the 1-hour tier, paying 2x on writes to keep the entry alive through pauses in your work. The two sub-fields sum to cache_creation_input_tokens.
server_tool_useCounts of server-side tool invocations (web search, web fetch) this request, which carry their own per-use pricing on top of tokens.
service_tierWhich capacity tier served the request (standard, priority, or batch). Batch runs at half price; the tier changes the multiplier on everything above.
speedWhether fast mode served the request (standard here). Fast mode is premium-priced, so this field is load-bearing for cost math.
iterationsOne entry per model attempt inside the call, each with its own four-field breakdown. With server-side fallbacks, a declined attempt and the rescue each appear here; the per-attempt list is the billing source of truth when they differ.
inference_geoWhere inference ran, when data-residency routing is in use.

The lesson of the TTL split deserves its own sentence: you can read a harness's caching strategy straight out of its usage blocks. The 1h/5m sub-fields told us, without any documentation, that Claude Code buys the doubled write premium for hour-long durability. Chapter 24 measures exactly when that trade wins.

count_tokens: the pre-flight instrument

The usage block is the receipt after the fact. Its pre-flight twin is count_tokens (Chapter 2 introduced it): the same request shape, no generation, free, and it counts what the request will actually bill: the system prompt, the tool schemas, and the message framing are all included, so the number matches the wire rather than your raw text.

# Illustrative: requires the anthropic SDK and an API key.
n = client.messages.count_tokens(
    model="claude-opus-4-8",
    system=SYSTEM_PROMPT,
    tools=TOOLS,
    messages=[{"role": "user", "content": QUESTION}],
).input_tokens

The two instruments answer different questions and are worth pairing deliberately:

  • Before the call, count_tokens tells you the full prompt size, so you can enforce a budget, pick a model, or refuse a request that will not fit.
  • After the call, usage tells you how that prompt was billed: how much of it hit the cache, what the write premium was, what the output cost.
  • The difference between them is your cache diagnosis. If count_tokens says 40,000 and the usage block shows cache_read_input_tokens: 0 on the second identical request, a silent invalidator is rewriting your prefix. That test costs nothing and catches the most expensive class of caching bug there is.

Remember. count_tokens counts the request; usage bills the response. Neither includes the other's job: count_tokens cannot know the cache state (it never splits into the three buckets), and usage arrives too late to stop an oversized prompt. Pre-flight with one, reconcile with the other.

Usage under streaming

Streaming responses report usage in two installments, and dashboards that read only one of them under-count. The message_start event carries the input-side fields (they are known as soon as the prompt is processed, before any output exists), while the final message_delta event carries the authoritative output_tokens once generation ends:

event: message_start
data: {"type":"message_start","message":{..., "usage":{"input_tokens":6199,
       "cache_creation_input_tokens":6052,"cache_read_input_tokens":15853,
       "output_tokens":3}}}

... content_block_delta events ...

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},
       "usage":{"output_tokens":318}}

The output_tokens in message_start is a placeholder for the few tokens generated so far; the one in message_delta is the bill. The SDK helpers (get_final_message() in Python, finalMessage() in TypeScript) merge the two for you, which is one more reason to use them instead of accumulating events by hand. If you do meter by hand, take input-side fields from message_start and output from the last message_delta, never the reverse.

Further reading

  • The Anthropic Messages API reference (platform.claude.com/docs), for the authoritative usage field list, and the prompt-caching page for the write-multiplier and TTL details the fields reflect.
  • The Anthropic token-counting documentation, for what count_tokens includes (system, tools, message structure) and its rate limits.
  • Chapter 2 for the price table and the 5x output asymmetry, and Chapter 6 for why the three input buckets exist at all.
  • Chapter 25, where these same blocks, thousands of them, are mined out of Claude Code's transcripts into a full ledger.

Takeaways

  • Every response carries usage, the billing ground truth. The full prompt is input + cache_creation + cache_read; input_tokens alone is only the uncached remainder and is routinely near zero in healthy agent sessions.
  • Each field has its own rate: 1x, 1.25x or 2x (by TTL, split out in cache_creation), 0.1x, and 5x for output. Cost math that ignores the multipliers is wrong in both directions.
  • In the lab session, the write column is each turn's delta and the read column is the accreted history; caching cut the session cost 2.6x, and per turn the 5x output tokens outweighed a prompt 100x their size.
  • The metadata fields explain the bill: the TTL split exposes the caching strategy (Claude Code uses the 1-hour tier), service_tier and speed change the multipliers, and iterations itemizes multi-attempt calls.
  • Pair the instruments: count_tokens before the call for size and budget, usage after it for cache health and cost. Identical prefix twice with cache_read still zero means a silent invalidator.
  • Streaming splits usage across message_start (input side) and the final message_delta (output side); the SDK's final-message helpers merge them correctly.

👉 You can now read a single receipt. The next chapter turns to the machinery that decides which bucket your tokens land in: a from-scratch model of the prompt cache, and the experiments that show exactly how prompt shape makes or silently breaks the hit.