Setting Up Prompt Caching Breakpoints in the Claude API: A Working Example

A technician working on a server rack inside a data center, representing backend infrastructure for API requests.

Claude’s prompt caching lets you mark a prefix of a request so a later call can reuse it instead of reprocessing it from scratch. You turn it on by adding a cache_control field to a content block, and Claude bills a cache read at a fraction of the normal input price once that prefix matches exactly. This walkthrough sets up a real breakpoint, sends two requests against it, and reads the usage fields that confirm whether the cache actually fired.

Why a breakpoint matters before you add one

A cache breakpoint is a single marker, not a setting you flip for the whole request. Anthropic’s documentation describes caching as working on a strict prefix: the system caches content in the order tools, then system, then messages, up to and including whichever block you mark with cache_control. Nothing after that block is cached, and nothing before it is skipped. The whole prefix up to the marker has to match exactly, character for character, on a later call for the cache to pay off.

That strictness is why placement decides whether caching saves money or quietly does nothing. If your system prompt holds a large static reference document, such as a contract, a style guide, or a codebase excerpt, plus a short dynamic instruction, the breakpoint belongs right after the static block and before the dynamic one. Move it one block too late, past the part that changes on every call, and the hash never matches twice. You still pay the write price on every request and never see a cheap read.

The four-breakpoint limit, and what the lookback window actually checks

You can set up to four cache_control blocks in a single request. Anthropic’s documentation also describes a lookback window: for each breakpoint, the system checks at most 20 earlier positions, counting the breakpoint itself as the first, to see whether an earlier request already wrote a cache entry ending at one of those positions. If your prefix is built from many small blocks, a cache entry written 25 blocks back sits outside that window and will not be found, even when the content is identical.

In practice, this means breakpoints belong at structurally meaningful seams: the end of a long static system prompt, the end of a set of tool definitions, or the end of a conversation history you want to keep warm. Scattering them every few messages wastes the limit of four without buying you anything.

A working example: caching a legal-document system prompt for an hour

Here is a complete request that caches a long system prompt for an hour instead of the five-minute default, following the pattern in Anthropic’s own documentation.

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    system=[
        {
            "type": "text",
            "text": LONG_CONTRACT_TEXT,  # the static part: thousands of tokens, same on every call
            "cache_control": {"type": "ephemeral", "ttl": "1h"},
        }
    ],
    messages=[
        {"role": "user", "content": "What are the termination clauses in section 4?"}
    ],
)

print(response.usage.cache_creation)

Walking through why each part exists:

The contract text sits in its own block, separate from the dynamic question, so the breakpoint can sit after it without pulling the question into the cached prefix. The cache_control field with ttl set to 1h is what actually creates the breakpoint; omitting ttl defaults to the five-minute cache, and adding it is the only way to opt into the one-hour version. The one-hour write costs twice the base input price instead of 1.25 times, which is worth it the moment you expect more than one follow-up question within the hour. The question itself lives in the messages array, outside the cached block, so it can change on every call without invalidating the cache. The system still matches everything up to and including the marked block, and the question comes after it, so it never has to match anything. Order matters if you mix TTLs in one request: Anthropic’s documentation states that a one-hour entry must appear before any five-minute entry in the same request. A second, shorter-lived breakpoint for something like recent chat history has to go after this one, not before it.

A second call, with the same contract text and a different question, reuses this same cache entry:

response_2 = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    system=[
        {
            "type": "text",
            "text": LONG_CONTRACT_TEXT,  # must be byte-identical to the first call
            "cache_control": {"type": "ephemeral", "ttl": "1h"},
        }
    ],
    messages=[
        {"role": "user", "content": "Does section 7 mention a renewal clause?"}
    ],
)

Reading usage to confirm the cache actually fired

The response’s usage object is the only reliable way to know whether a request hit the cache, wrote to it, or did neither. Three fields matter. cache_creation_input_tokens counts tokens written to the cache on this call, and is non-zero on the first call in the example above. cache_read_input_tokens counts tokens read from an existing cache entry, and is non-zero on the second call if the prefix matched. input_tokens counts tokens processed that were not covered by any cache, counted only from after the last breakpoint.

Anthropic documents the identity that total input tokens equal the sum of cache_read_input_tokens, cache_creation_input_tokens, and input_tokens. If both cache fields come back as zero on a call you expected to hit, do not assume caching is broken outright. Check the causes below first.

Why a cache miss happens even when the text looks the same

Three causes account for most silent misses.

The prompt can fall below the minimum token count for the model you called. Anthropic’s documentation lists different minimums by model family, shown in the table below, and a prompt under the threshold for whichever model you are calling is silently not cached. No error is raised, so the only sign is a cache_creation_input_tokens value of zero you were not expecting.

Something before the breakpoint can have changed. The hash is cumulative and covers every block up to and including the marked one. A different timestamp, a reordered tool definition, or even a whitespace change produces a different hash and the lookup fails.

The first response may not have started yet. A cache entry becomes available only after the first request’s response begins streaming. Firing two near-simultaneous requests with the same prefix, expecting the second to hit the first one’s cache, often races past that point, and both miss.

Minimum cacheable prompt length by model

Minimum tokens to cacheApplies to
512Claude Fable 5.1, Claude Mythos 5.1, Claude Opus 5.5, Claude Opus 5, Claude Sonnet 5.5, Claude Fable 5, Claude Mythos 5, Claude Haiku 5.5
1,024Claude Opus 4.8, Claude Sonnet 5, Claude Sonnet 4.6, Claude Sonnet 4.5, Claude Opus 4.1, Claude Opus 4, Claude Sonnet 4
2,048Claude Mythos Preview, Claude Opus 4.7, Claude Haiku 3.5
4,096Claude Opus 4.6, Claude Opus 4.5, Claude Haiku 4.5

Several of the models in the 1,024 and 2,048 token rows are marked deprecated or retired in Anthropic’s own documentation as of this writing. Check the current model list before relying on any specific minimum staying attached to a given model name.

What caching actually costs, by operation

OperationPrice relative to base input tokens
5-minute cache write1.25x base input price
1-hour cache write2x base input price
Cache read (most models)0.1x base input price
Cache read, Claude Fable 5.1 / Mythos 5.10.025x base input price
Cache read, Claude Opus 5.5 / Sonnet 5.50.05x base input price

Breakpoints themselves cost nothing to define. You only pay when a write or a read actually happens. To estimate your own savings without relying on a specific dollar figure that can go stale, take your account’s per-token input price for the model you are using, then apply the multipliers above: one write at the 1.25x or 2x rate, followed by as many reads at the 0.1x (or lower, on the newest Opus and Sonnet models) rate as you expect within the cache’s lifetime. A system prompt reused twenty times within an hour costs roughly one full-price-equivalent write plus two reads’ worth of extra tokens for the other nineteen calls, against twenty uncached calls that would each pay full price for the same prefix.

Where this fits next to interleaved thinking and model choice

If you are also using extended thinking, interleaved thinking changes how the API threads tool calls and reasoning together, and a cached system prompt sits underneath either mode without conflict. And if you are still deciding which model to cache against, the minimum-token thresholds above are one more input on top of Claude’s current pricing tiers and its model deprecation schedule. A model you cache against today may carry a different minimum, or be retired outright, within a few release cycles, so revisit this table when you change models rather than assuming it carries over.

Checking a cache hit from the command line, without writing a script

If you want to confirm a breakpoint is working before wiring it into application code, the same two requests above translate directly to curl. Send the first call, note the cache_creation_input_tokens value in the response, then send the second call with the identical system block and a different message, and look for cache_read_input_tokens instead:

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 1024,
    "system": [{
      "type": "text",
      "text": "'"$LONG_CONTRACT_TEXT"'",
      "cache_control": {"type": "ephemeral", "ttl": "1h"}
    }],
    "messages": [{"role": "user", "content": "What are the termination clauses in section 4?"}]
  }' | python3 -c "import sys,json; print(json.load(sys.stdin)['usage'])"

A non-zero cache_creation_input_tokens on the first call and a non-zero cache_read_input_tokens on the second, with input_tokens on the second call covering only the new question, is the signal that the breakpoint is placed correctly. If the second call’s cache_creation_input_tokens is non-zero instead of cache_read_input_tokens, something before the breakpoint changed between the two calls, and it is worth diffing the two system blocks byte for byte rather than assuming the API is at fault.

Stacking caching with other pricing modifiers

Anthropic’s documentation notes that the cache pricing multipliers stack with other pricing modifiers rather than replacing them: the Batch API’s discount, long-context pricing for very large prompts, and data residency fees all apply on top of whichever cache multiplier is in effect for a given request. A cached, batched request with a long context window combines all three adjustments on the same call, which is worth checking in your own usage logs rather than assuming any one discount cancels out another.

Where this actually pays off versus where it does not

Caching earns back its write cost fastest in a few recognizable shapes of workload: a support or research tool that reuses the same long system prompt across many different user questions in a session, a document-analysis pipeline that asks several questions against the same source text before moving to the next document, or an agent loop that re-sends the same tool definitions and instructions on every turn while only the conversation tail changes. In each case the static part is large relative to the dynamic part, and the dynamic part is what actually needs a fresh answer each time.

It pays off far less, or not at all, for workloads where the prompt changes substantially on every call, where the total prompt is already under the minimum token threshold for the model in use, or where a given prefix is genuinely only ever sent once. Adding a breakpoint to a short, one-off prompt adds a small write cost for no later read to offset it, since there is no second call left to benefit from the cache before it expires.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top