A8gent
HomeBlogHow to Cut Your Claude API Bill 70% with Prompt Caching
How to Cut Your Claude API Bill 70% with Prompt Caching
Technical · 2026-07-09 · Last verified 2026-07-09

How to Cut Your Claude API Bill 70% with Prompt Caching

Cached input tokens on the Claude API cost 0.1x the base rate, and in an agent loop 80-95% of every request is repeated prefix. This guide covers exactly how the cache works (prefix matching), where to put cache_control breakpoints, the write-cost break-even math, the silent invalidators that make caching do nothing, and how to verify hits from the usage object.

D
Deep · ML Architect & Full Stack Engineer

10+ years shipping production ML across TensorFlow, PyTorch, AWS, and GCP. Ships every A8gent agent before it becomes a lesson. GitHub

Key takeaways
  • Cache reads cost roughly 0.1x the base input price on the Claude API - a 90% discount on every token served from cache. For agents, where the system prompt, tool definitions, and conversation history are re-sent on every loop iteration, this is the single highest-ROI cost lever.
  • Caching is a strict prefix match. One changed byte anywhere in the prefix - a timestamp in the system prompt, an unsorted JSON key, a reordered tool - invalidates everything after it. Most 'caching does not work' reports are silent invalidators, not API problems.
  • Cache writes cost extra: 1.25x base for the default 5-minute TTL, 2x for the 1-hour TTL. Break-even is 2 requests on the 5-minute cache and 3+ on the 1-hour cache, so bursty-but-gappy traffic needs the math, not a reflex.
  • There is a minimum cacheable prefix (1,024-4,096 tokens depending on model). Shorter prefixes silently do not cache - no error, just cache_creation_input_tokens: 0.
  • Verify with the usage object, not vibes: cache_read_input_tokens should be large and input_tokens small on repeat requests. If reads stay at zero, diff the rendered prompt bytes between two requests.
  • The realistic whole-bill saving for a production agent is 60-75%, because output tokens and the uncached suffix still bill at full rate. The 90% figure applies to the cached portion only.

Why Prompt Caching Is the Biggest Cost Lever for Agents

An AI agent re-sends its entire context on every step of its loop: the system prompt, every tool definition, and the full conversation so far, including every previous tool result. On a 10-step run, the system prompt and tools alone get billed 10 times. That repetition is exactly what prompt caching eliminates.

On the Claude API, input tokens served from the cache bill at roughly 0.1x the base input rate. At current pricing that means:

ModelBase input /1MCached read /1M (~0.1x)Output /1M
Claude Opus 4.8$5.00~$0.50$25.00
Claude Sonnet 4.6$3.00~$0.30$15.00
Claude Haiku 4.5$1.00~$0.10$5.00

Do the napkin math for a typical agent: a 6,000-token system prompt plus tools, a conversation that grows to 40,000 tokens, 10 loop iterations. Uncached, that is roughly 250,000 input tokens. With the prefix cached, you pay full price roughly once and 0.1x for the rest - the input side of the bill drops by 85-90%. Because output tokens still bill at full rate, the whole-bill saving for a real agent workload usually lands at 60-75%. That is where the 70% in the title comes from - measured on the total invoice, not cherry-picked on the cached portion.

How the Cache Actually Works: One Rule Explains Everything

Everything about Claude prompt caching follows from one invariant: the cache is a prefix match on the exact rendered bytes of your request. The API renders your request in a fixed order - tools, then system, then messages - and the cache key is derived from the bytes up to each breakpoint you mark. A single byte difference at position N invalidates the cache for everything at positions after N.

You mark breakpoints with cache_control on a content block:

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=2048,
    system=[
        {
            "type": "text",
            "text": LONG_STABLE_SYSTEM_PROMPT,
            "cache_control": {"type": "ephemeral"},  # 5-minute TTL
        }
    ],
    messages=[{"role": "user", "content": user_question}],
)

Because tools render before system, a breakpoint on the last system block caches the tool definitions and the system prompt together - you do not need a separate marker on the tools. You get at most 4 breakpoints per request, and there is a simpler option when you do not need fine-grained placement: a top-level cache_control on the request auto-places a breakpoint on the last cacheable block.

Two constraints people miss:

  • Minimum cacheable prefix. Short prefixes silently do not cache - no error, the usage report just shows cache_creation_input_tokens: 0. The minimum is model-dependent: 1,024 tokens on Sonnet 4.5, 2,048 on Sonnet 4.6, 4,096 on Opus 4.8 and Haiku 4.5. A 3,000-token prompt caches on one model and silently does not on another.
  • Caches are model-scoped. Switching models mid-conversation starts from a cold cache, and the first request on the new model pays the write premium again.

The Break-Even Math: When Caching Pays and When It Does Not

Cache writes are not free. Writing to the default 5-minute cache costs 1.25x the base input rate; the 1-hour TTL costs 2x. That premium buys you 0.1x reads afterwards, so break-even depends on how many times the prefix is re-read before it expires:

TTLWrite costCost after N readsBreak-even
5 minutes (default)1.25x1.25x + N×0.1x2 requests (1.35x vs 2x uncached)
1 hour2x2x + N×0.1x3+ requests (2.2x vs 3x uncached)

What this means in practice:

  • Agent loops always win. A single agent run makes 5-30 requests within minutes, all sharing the prefix. The 5-minute cache pays for itself on the second loop iteration.
  • Multi-turn chat wins if users reply within the TTL. Each turn re-reads the whole prior conversation. If replies typically arrive within 5 minutes, the default TTL is enough; if your users think for 20 minutes between messages, the 1-hour TTL keeps the cache alive but needs at least 3 turns to beat no caching.
  • One-shot requests with unique prompts lose. If the first 1,000+ tokens differ on every request, there is no reusable prefix. Adding cache_control just pays the 1.25x write premium with zero reads. Leave it off.

Also note the cache only becomes readable once the first response begins streaming. If you fan out 10 parallel requests with the same prefix, all 10 pay full price - none can read what the others are still writing. The fix: send one request, await the first streamed token, then fire the other nine. They read the cache the first one just wrote.

Where to Put Breakpoints: Three Patterns Cover 95% of Cases

Pattern 1 - big shared system prompt. One breakpoint on the last system block. Tools and system cache together; the varying user question comes after and bills normally.

Pattern 2 - multi-turn conversation. Put the breakpoint on the last content block of the most recently appended turn. Each request then reuses the entire prior conversation as cached prefix, and hits accrue incrementally as the conversation grows:

# before sending, mark the newest turn as the cache frontier
messages[-1]["content"][-1]["cache_control"] = {"type": "ephemeral"}

Pattern 3 - shared preamble, varying question. Many requests share a large fixed block (few-shot examples, retrieved documents, instructions) and differ only at the end. Put the breakpoint at the end of the shared portion, not the end of the whole prompt - otherwise every request writes a distinct cache entry and nothing is ever read:

messages = [{
    "role": "user",
    "content": [
        {
            "type": "text",
            "text": SHARED_CONTEXT,  # same every request
            "cache_control": {"type": "ephemeral"},
        },
        {"type": "text", "text": varying_question},  # no marker
    ],
}]

One agent-specific edge case: each breakpoint looks backward at most 20 content blocks to find a prior cache entry. Agent turns with many parallel tool calls can add more than 20 blocks in one turn, at which point the next request silently misses the previous cache. If your agent does heavy parallel tool use, place an intermediate breakpoint every ~15 blocks.

Silent Invalidators: Why Your Cache Hit Rate Is Zero

The most common caching failure is not a missing breakpoint - it is a byte that changes on every request somewhere early in the prompt. The request succeeds, no error is raised, and the cache never gets read. Grep your prompt-assembly code for these:

PatternWhy it kills the cache
datetime.now() / Date.now() in the system promptPrefix changes every request; nothing after the timestamp ever caches
uuid4() / request IDs early in contentEvery request renders a unique prefix
json.dumps() without sort_keys=TrueKey order varies run to run; bytes differ even when data is identical
Interpolating user or session ID into the system promptPer-user prefixes - no sharing across users, cold cache per session
Conditional system sections (if flag: system += ...)Every flag combination is a distinct prefix
Tool list built per user or per requestTools render at position 0; any variation invalidates the entire cache

The fixes are mechanical: move dynamic values after the last breakpoint (put the current date in the user message, not the system prompt), make serialization deterministic (sort keys, sort tools by name), and freeze the system prompt. If you need per-request context like a date or a mode flag, inject it as a message near the end of the conversation - a message at turn 5 invalidates nothing before turn 5.

The same logic explains two architectural rules: never swap the tool set mid-conversation (add a mode parameter to a message instead), and when you fork a conversation for a side task like summarization, copy the parent's system, tools, and model verbatim so the fork reads the parent's cache instead of writing its own.

Verifying It Works: Read the Usage Object, Not the Invoice

Every response reports exactly what the cache did. Log these three fields on every call:

u = response.usage
print("written to cache:", u.cache_creation_input_tokens)  # paid ~1.25x
print("served from cache:", u.cache_read_input_tokens)      # paid ~0.1x
print("uncached:", u.input_tokens)                           # paid full rate

A healthy cached agent shows a big write on the first request, then large cache_read_input_tokens and small input_tokens on every subsequent one. Total prompt size is the sum of all three fields - if your agent ran for an hour but input_tokens shows 4,000, the rest was cache reads, which is the goal.

If cache_read_input_tokens stays at zero across repeated identical-prefix requests, do not guess: render two requests, dump the raw bytes, and diff them. The first differing byte is your invalidator - in my experience it is a timestamp about 80% of the time and an unsorted dict most of the rest.

One more production trick: to remove the cold-cache latency on the first real request, you can pre-warm at startup with a max_tokens: 0 request. The API runs prefill, writes the cache at your breakpoint, and returns immediately with empty content - zero output tokens billed. Only worth it when first-request latency is user-visible and the prefix is large; if traffic is continuous the first real request warms the cache on its own.

Worked Example: A Support Agent, Before and After

Numbers from a realistic setup on Claude Sonnet 4.6 ($3/M input, $15/M output): a support agent with a 5,000-token system prompt + tool definitions, averaging 8 loop iterations per ticket, with conversation context growing to ~20,000 tokens by the final step. Roughly 100,000 input tokens and 3,000 output tokens per ticket.

UncachedCached (5-min TTL)
Input cost per ticket100k × $3/M = $0.30~12k full + ~5k write×1.25 + ~83k reads×0.1 = ~$0.080
Output cost per ticket3k × $15/M = $0.045$0.045 (unchanged)
Total per ticket$0.345~$0.125

That is a 64% reduction from one change that touches no product behavior. At 1,000 tickets a day the difference is roughly $6,600/month. Stack model routing on top (send easy tickets to Haiku 4.5 at a third of the input price) and the combined saving crosses 80% - the full stack of levers is in our production cost-optimization playbook.

If you are weighing this against running your own models, the caching discount also changes the self-hosting math - a cached Claude workload is much harder for a self-hosted GPU box to beat than the sticker prices suggest. We run that comparison honestly in the self-host vs API break-even analysis.

The Mistakes That Waste the Most Money

Marking everything. More breakpoints is not more caching. You get 4; use them at stability boundaries (end of tools+system, end of shared context, end of the latest turn). A breakpoint on content that differs per request is a pure write premium.

Caching below the minimum. A 900-token system prompt does not cache on any current model. Either accept it (the absolute spend is small) or move more stable content - style guides, examples, tool usage notes - into the system prompt to cross the threshold. This is one of the few cases where making a prompt longer makes it cheaper.

Choosing the 1-hour TTL by default. The 2x write premium means it needs 3+ reads to beat no caching at all, and more than that to beat the 5-minute TTL kept warm by steady traffic. Choose 1-hour only when you have measured gaps longer than 5 minutes between requests that share a prefix.

Rebuilding the prompt differently in different code paths. The retry path, the streaming path, and the eval harness must assemble byte-identical prefixes. A subtle difference - one adds a trailing newline, one orders tools differently - and your production traffic and your evals populate two separate caches.

Not logging cache fields. If your observability does not record cache_read_input_tokens per call, you cannot see regressions. A deploy that adds one dynamic line to the system prompt will silently multiply your bill by 5x, and you will find out from the invoice. Wire the three usage fields into whatever tracing you already run - if you have none, start with our agent observability guide.

FAQ

How much does Claude prompt caching actually save?

Cached input tokens bill at roughly 0.1x the base input rate - a 90% discount on the cached portion. For agent workloads where 80-95% of each request is repeated prefix, the input side of the bill typically drops 85-90%, and the whole bill (including output tokens, which are never discounted) drops 60-75%. One-shot workloads with unique prompts save nothing and pay a small write premium.

What is the difference between the 5-minute and 1-hour cache TTL?

The default ephemeral cache lives 5 minutes and costs 1.25x base input to write; the 1-hour TTL costs 2x to write. Break-even is 2 requests for the 5-minute cache and at least 3 for the 1-hour one. Use 1-hour only when requests sharing a prefix arrive with gaps longer than 5 minutes - steady traffic keeps the 5-minute cache warm for free because every read effectively refreshes usefulness.

Why is my cache_read_input_tokens always zero?

Almost always a silent invalidator: a timestamp or UUID rendered into the system prompt, JSON serialized without sorted keys, a tool list that varies per request, or a prefix below the model's minimum cacheable size (1,024-4,096 tokens depending on model). Caching is an exact byte-level prefix match, so diff the rendered bytes of two requests - the first differing byte is the culprit.

Does prompt caching work with tool use and agents?

Yes, and agents are the best case for it. Tools render first in the request, so a breakpoint on the last system block caches tool definitions and system prompt together, and a breakpoint on the newest message caches the growing conversation. Two cautions: never add or remove tools mid-conversation (that invalidates everything), and if one turn adds more than 20 content blocks of parallel tool calls, add an intermediate breakpoint so the lookback window can find the previous cache entry.

Do I need to change my code to read from the cache?

No separate read call exists - you send the same request shape every time, and the API automatically serves any matching cached prefix. Your only jobs are placing cache_control breakpoints at stability boundaries, keeping the prefix byte-stable, and verifying via usage.cache_read_input_tokens that reads are actually happening.

Is prompt caching worth it on small prompts?

Below the minimum cacheable prefix (1,024 tokens on some models, up to 4,096 on others) the marker is silently ignored, so no. Between the minimum and a few thousand tokens it works but the absolute savings are small. Caching earns its keep when the stable prefix is large: long system prompts, many tool definitions, big retrieved documents, or long conversations.

All posts
2026-07-09

Done for you

Want this built for your business?

Tell us the workflow. We scope, build, and ship the agent with guardrails and the numbers to prove it worked. The scoping call is free.

Free scoping call. You own the code.

Request a scoping call