Stop Paying for the Same Tokens Twice: A Practical Guide to Prompt Caching A developer's guide explains how prompt caching in Anthropic's Claude API can cut input costs by up to 90% on repeated conversation prefixes, requiring only a single `cache_control` parameter. The technique caches tokens from the start of a request up to a marked block, with cache reads priced at 0.1x the base input rate, and it also improves response speed and effective throughput. You've built a chatbot. Every turn, you re-send the whole conversation — the 8,000-token system prompt, the uploaded PDF, the 15 messages of history — just so the model can answer "and what about Mars?" The model re-reads all of it. Every. Single. Time. You pay full price for all of it. Every. Single. Time. Prompt caching fixes this. It's roughly one extra line of JSON, and it can cut your input costs by ~90% on the repeated part while making responses noticeably faster. Add cache control at the top level of your request: response = client.messages.create model="claude-opus-5", max tokens=1024, cache control={"type": "ephemeral"}, <-- this is the whole trick system="You are an AI assistant tasked with analyzing literary works...", messages= {"role": "user", "content": "Analyze the major themes in 'Pride and Prejudice'."} , print response.usage That's automatic caching . The API caches everything up to and including the last cacheable block in your request. Next time you send a request that starts with the same content, that prefix is read from cache instead of reprocessed. Same thing in curl, if that's more your speed: curl https://api.anthropic.com/v1/messages \ -H "content-type: application/json" \ -H "x-api-key: $ANTHROPIC API KEY" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-opus-5", "max tokens": 1024, "cache control": {"type": "ephemeral"}, "system": "You are an AI assistant tasked with analyzing literary works.", "messages": {"role": "user", "content": "Analyze the major themes in Pride and Prejudice."} }' This is the single most important thing to internalize. Your prompt is read in a fixed order: tools → system → messages Caching works on prefixes of that sequence. When you mark a block with cache control , you're saying: "cache everything from the start of the request up to and including this block." Which means: Three price tiers instead of one: | Multiplier vs. base input | | |---|---| 5-minute cache write | 1.25× | 1-hour cache write | 2× | Cache read hit | 0.1× | So for Claude Opus 5 $5/MTok input : "usage": { "cache read input tokens": 100000, "cache creation input tokens": 0, "input tokens": 50 } input tokens is not your total input. It's only the tokens after your last cache breakpoint. Total is: total = cache read input tokens + cache creation input tokens + input tokens So the example above processed 100,050 tokens, not 50. Handy side effect: cache hits don't count against your rate limits the way fresh input does, so effective throughput goes up too. Quick sanity check: if both cache creation input tokens and cache read input tokens are 0 , nothing was cached. Most likely you're under the minimum length see below — the API won't error, it just silently skips caching. Prompts shorter than a per-model floor simply won't cache: | Model | Minimum cacheable tokens | |---|---| | Opus 5, Fable 5, Mythos 5 | 512 | | Opus 4.8, Sonnet 5 / 4.6 / 4.5 | 1,024 | | Opus 4.7, Haiku 3.5 | 2,048 | | Opus 4.6, Opus 4.5, Haiku 4.5 | 4,096 | If you're just under the line, it's often worth padding the cached section more examples, more context to get over it. Cache reads are cheap enough that the extra tokens pay for themselves. With automatic caching, the breakpoint walks forward on its own as the conversation grows: | Request | What happens | |---|---| | 1 | System + U1 + A1 + U2 → everything written to cache | | 2 | ... + A2 + U3 → System→U2 read from cache, A2+U3 written | | 3 | ... + A3 + U4 → System→U3 read from cache, A3+U4 written | No bookkeeping. No moving markers around. Each turn reads the whole prior conversation from cache and only writes the new bit. This is the single best default for chat apps. Put cache control on individual blocks when different parts of your prompt change at different rates. You get up to 4 breakpoints . Classic RAG-agent layout: { "tools": / ... /, { "name": "get document", "cache control": {"type": "ephemeral"} } , "system": { "type": "text", "text": "You are a research assistant...", "cache control": {"type": "ephemeral"} }, { "type": "text", "text": " Knowledge Base\n Doc 1...", "cache control": {"type": "ephemeral"} } , "messages": { "role": "user", "content": { "type": "text", "text": "Tell me about Perseverance.", "cache control": {"type": "ephemeral"} } } } Four independent segments: Here's the bug I want you to remember, because it's expensive and silent. Your prompt: blocks 1–5 are a big static system context. Block 6 is f" {timestamp} {user message}" . You put cache control on block 6, because it's the end and that seems right. cache control to A related gotcha. When looking for a cache hit, the system checks your breakpoint's position and then walks backward — but only 20 blocks . Default TTL is 5 minutes , and it refreshes for free every time you hit the cache. An active chat session basically keeps itself warm. "cache control": { "type": "ephemeral", "ttl": "1h" } Reach for 1h when: Latency-sensitive app? The first user of the day eats the cache-miss penalty. Unless you warm it up first: SYSTEM PROMPT = { "type": "text", "text": "You are an expert software engineer...", "cache control": {"type": "ephemeral"}, } def prewarm cache : client.messages.create model="claude-opus-5", max tokens=0, no output generated system=SYSTEM PROMPT, messages= {"role": "user", "content": "warmup"} , max tokens: 0 reads your prompt in, writes the cache at your breakpoint, and returns immediately with an empty content array and stop reason: "max tokens" . Zero output tokens billed. You still pay the cache write, naturally. Two things to get right: "warmup" placeholder — otherwise the entry is keyed to the placeholder and real traffic never hits it. This is why pre-warming needs an explicit breakpoint rather than automatic caching. effort setting as your real requests. Those get rendered into the prompt, so a mismatched pre-warm writes an entry nobody uses. max tokens: 0 is rejected with stream: true , extended thinking, structured outputs, forced tool choice , or inside a Batches request.Cache hits need a 100% byte-identical prefix. Things that invalidate: | Change | Blast radius | |---|---| | Tool definitions | Everything | | Toggling web search / citations | System + messages | | Switching fast mode | System + messages | tool choice | Messages | | Adding/removing images anywhere | Messages | Thinking config or effort | Messages and sometimes more | One sneaky one: some languages Go, Swift randomize map key order when serializing JSON. If your tool use blocks come out with shuffled keys, your cache never hits and you'll have no idea why. Pin the ordering. Also: caches are isolated per organization, and per workspace on the Claude API. And a cache entry only becomes available after the first response begins — so firing 10 parallel requests with the same prefix gives you 10 misses. Send one, wait, then fan out. Cache not hitting? Run down this list: tool choice , image presence, thinking config, and effort Got a caching setup that surprised you — good or bad? Drop it in the comments. 🚀