| name | prompt-caching |
| description | Optimize Claude API costs and latency with prompt caching strategies |
Prompt Caching
When to activate
Claude API usage with repeated large context blocks — system prompts, prepended documents, large tool definitions — or when the user wants to reduce API costs or latency for workloads that reuse the same context across multiple calls.
When NOT to use
- Single-shot API calls with no repeated context
- Conversations where the system prompt changes every request
- Contexts smaller than 1024 tokens (Claude 3) or 2048 tokens (Claude 3.5+ Haiku) — below these thresholds, caching has no effect
Instructions
How Prompt Caching Works
Cache breakpoints mark content blocks as eligible for caching. When Anthropic's infrastructure sees the same prefix again (up to the breakpoint), it reads from cache instead of reprocessing.
- Cache write cost: 1.25× standard input token price
- Cache read cost: 0.1× standard input token price
- Break-even point: ~9 reads of the same content
- Default TTL: 5 minutes
- Extended TTL: 1 hour — set
ENABLE_PROMPT_CACHING_1H=1 as an environment variable (beta)
cache_control Syntax
Add "cache_control": {"type": "ephemeral"} to the last content block you want included in the cache prefix:
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
system=[
{
"type": "text",
"text": "<your large system prompt here — must be >1024 tokens to cache>",
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "What does the system prompt say about X?"}],
)
Where to Place Breakpoints
System prompt (always cache if >1024 tokens):
system=[
{"type": "text", "text": base_instructions},
{"type": "text", "text": large_doc, "cache_control": {"type": "ephemeral"}},
]
Large document prepended to conversation:
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": large_reference_doc, "cache_control": {"type": "ephemeral"}},
{"type": "text", "text": "Now answer this question about the document above: ..."},
],
}
]
Tool definitions (cache when using many tools):
tools=[
{
"name": "last_tool",
"description": "...",
"input_schema": {...},
"cache_control": {"type": "ephemeral"},
}
]
Multi-Turn Caching
In a multi-turn conversation, the messages array grows with each turn. Place the cache breakpoint at the end of the tools array or system prompt — the messages array is handled automatically by Anthropic's caching infrastructure as the conversation grows.
Do not add cache_control to every message — add it only to the large static content at the top of the context. Anthropic caches everything up to the last breakpoint in the prefix.
Measuring Cache Effectiveness
Check usage in the response:
print(response.usage)
A healthy multi-turn session should show cache_creation_input_tokens > 0 only on the first call and cache_read_input_tokens > 0 on all subsequent calls.
Cost Calculation Example
System prompt: 15,000 tokens. 50 user messages processed per hour.
| Scenario | Cost per call | Cost/hour (50 calls) |
|---|
| No caching | 15,000 × $3/MTok | $2.25 |
| With caching (after break-even) | 15,000 × $0.30/MTok | $0.225 |
10× cost reduction for large, frequently-reused context.
Common Mistakes
- Placing the breakpoint too early — content after the breakpoint is re-processed every call
- Adding breakpoints inside the messages array on short turns — the minimum cacheable size won't be hit
- Forgetting that cache TTL is 5 minutes — a 10-minute batch job will lose the cache mid-run (use extended TTL)
- Using caching for unique per-request content — caching only helps when the prefix is identical across calls
Example
A document Q&A system processes 200 questions against the same 80-page PDF per day:
doc_text = extract_text_from_pdf("report.pdf")
def ask_question(question: str) -> str:
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=500,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": f"<document>\n{doc_text}\n</document>",
"cache_control": {"type": "ephemeral"},
},
{"type": "text", "text": question},
],
}
],
)
return response.content[0].text