Cost engineering

Claude Prompt Caching Explained: How to Reduce API Costs

Understand Claude cache writes, reads, TTLs, hit rates, and routing. Calculate break-even costs and reproduce a two-request prompt caching test in Python.

Cache the stable prefix, then measure what was reused

Claude prompt caching reuses a processed prompt prefix. It is not a saved answer: each call can generate a new response, and output tokens still cost money. Put stable instructions, tools, and reference material before changing user input. Decide what to cache by how often that prefix is reused within its lifetime.

This article uses Sonnet 4.6 and a 20,000-token prefix for arithmetic. It makes no claim about your actual cache hit rate. For provider selection, read the Claude API pricing comparison; for client wiring, use the Claude Code configuration guide.

Technical behavior and external rates checked October 17, 2026 against Anthropic’s prompt caching reference. The examples below are intentionally limited to Sonnet 4.6; do not apply its rates or minimum length to every Claude model.

Cache write, cache read, and uncached input

On Anthropic-format responses, input_tokens counts uncached input; cache_creation_input_tokens counts newly cached input; cache_read_input_tokens counts reused input. Treat these as separate categories. If present, cache_creation.ephemeral_5m_input_tokens and cache_creation.ephemeral_1h_input_tokens split the aggregate write count by TTL; do not add both the aggregate and its breakdown to the bill.

Sonnet 4.6 direct API cache rates (USD per 1M tokens)
CategoryRateBudget implication
Uncached input$3.00Paid on input not served from or written to cache
5-minute write$3.75A write costs more than normal input
1-hour write$6.00A longer lifetime has a larger initial cost
Cache read$0.30A hit is charged at the read rate
Output$15.00Caching the prompt does not remove output cost

Source: Anthropic standard global pricing. For API Route, use the current model/group rates, including both write durations. An upstream’s pricing multiplier is not a substitute for your provider’s actual rate card.

Choose 5 minutes or 1 hour based on reuse timing

The default ephemeral cache TTL is five minutes; a hit refreshes the lifetime. Where supported, cache_control: {"type":"ephemeral","ttl":"1h"} selects one hour. Sonnet 4.6’s documented minimum cacheable prefix is 1,024 tokens. Matching content and a sufficient prefix are prerequisites, not a guarantee of a hit through every gateway.

For planning, write down the gap between requests. A workflow that pauses for twenty minutes may repeatedly pay for 5-minute writes, while an active conversation may reuse the shorter cache. Choose the longer duration only when reuse can justify its extra write cost. Changing the prefix can make the next call cold even before the TTL expires.

Keep dates, request IDs, and user-specific changes after the stable prefix when your application permits it. Preserve tool definitions and ordering. Avoid adding filler just to cross a caching threshold in production; the test below uses generated text only to make the behavior easy to inspect.

Calculate break-even and token-based hit rate

Let P be the cached prefix token count, N the number of uses, W the write rate, R the read rate, and I the ordinary input rate, all prices per million tokens. For one write and N−1 hits, the prefix cost is P × (W + (N−1)R) / 1,000,000. Compare that with N × P × I / 1,000,000. This calculation excludes unique input and output, which must be added to the full bill.

20,000-token prefix, ten uses, Sonnet 4.6 direct rates:
Uncached: 10 * 0.02 * 3 = $0.6000
5m: 0.02 * (3.75 + 9 * 0.30) = $0.1290
1h: 0.02 * (6.00 + 9 * 0.30) = $0.1740

5m, two uses: $0.0810 vs $0.1200 uncached
1h, two uses: $0.1260 vs $0.1200 uncached
1h, three uses: $0.1320 vs $0.1800 uncached

For a workload with changing prefix sizes, count tokens rather than requests. Define read share = read_tokens / (uncached_input_tokens + write_tokens + read_tokens). That is the fraction of all input tokens served from cache. If you separately measure cache-eligible prefix tokens, define and report a prefix hit rate too; the two denominators answer different questions.

With no uncached suffix, equal-sized prefixes, and each miss becoming a write, the average prefix rate is hR + (1−h)W. For the direct Sonnet 4.6 rates, 5-minute caching beats uncached input when h exceeds 21.74%; 1-hour caching needs more than 52.63%. These thresholds are mathematical scenarios, not empirical hit rates. Recalculate if your prices or billing behavior differ.

Reproduce a two-request cache test

This Python 3 script uses only the standard library. Save it as cache_probe.py. It sends two sequential, identical Messages requests with an explicit cache breakpoint on a fixed system prefix. Both calls can incur charges. Do not use confidential data in the initial test.

Set ANTHROPIC_BASE_URL and ANTHROPIC_MODEL for the route. For API Route, use https://global.api-route.com and an enabled Claude model, then set ANTHROPIC_AUTH_TOKEN privately. For Anthropic direct, use https://api.anthropic.com and ANTHROPIC_API_KEY instead. Set exactly one credential variable. The shell examples show masked key entry.

import json
import os
import urllib.request
import urllib.error

base = os.environ.get("ANTHROPIC_BASE_URL", "https://global.api-route.com").rstrip("/")
model = os.environ.get("ANTHROPIC_MODEL", "claude-sonnet-4-6")
token = os.environ.get("ANTHROPIC_AUTH_TOKEN")
api_key = os.environ.get("ANTHROPIC_API_KEY")
if bool(token) == bool(api_key):
    raise SystemExit("Set exactly one of ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY")

# Fixed, non-sensitive prefix, comfortably above Sonnet 4.6's minimum.
# Use the same bytes on both requests; no timestamps or random IDs.
prefix = "Follow the project rules below.\n" + "\n".join(
    f"Rule {i}: Validate inputs, document assumptions, and keep errors actionable."
    for i in range(400)
)
payload = {
    "model": model,
    "max_tokens": 32,
    "system": [{"type": "text", "text": prefix,
                "cache_control": {"type": "ephemeral"}}],
    "messages": [{"role": "user", "content": "Reply with OK."}],
}
headers = {"Content-Type": "application/json", "anthropic-version": "2023-06-01"}
headers.update({"Authorization": f"Bearer {token}"} if token else {"x-api-key": api_key})
for attempt in (1, 2):
    req = urllib.request.Request(base + "/v1/messages",
        data=json.dumps(payload).encode(), headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=120) as res:
            result = json.load(res)
    except urllib.error.HTTPError as exc:
        # Error bodies can contain provider details; inspect them privately.
        raise SystemExit(f"HTTP {exc.code}; inspect provider logs privately") from None
    usage = result.get("usage", {})
    print(json.dumps({"attempt": attempt, "model": result.get("model"), "usage": usage}))
    if "cache_read_input_tokens" not in usage or "cache_creation_input_tokens" not in usage:
        print("Cache fields absent: result is inconclusive; check provider billing logs.")
python cache_probe.py

Inspect the two actual usage objects. A first response with positive cache creation and a second with positive cache reads is evidence of reuse on that route at that time. If the prefix was already warm from an earlier run, the first request can also read from cache. No fixed token count or cache outcome is asserted here.

Verify the charge, not just the response fields

  1. Record the model, route/group, timestamp, returned token categories, and request ID when available. Keep credentials out of the record.
  2. Find each request in Usage Logs. Multiply the separate token categories by that route’s rates and compare with the settled charge, allowing for documented rounding.
  3. After a known idle interval beyond the TTL, repeat the probe to observe whether a new write occurs. Perform that later run yourself; the script deliberately does not wait or run indefinitely.
  4. Change the system prefix and repeat to examine a cold-prefix case. Keep the model and route fixed so you can interpret the difference.
  5. If you need a no-cache comparison, remove cache_control and run the same prompt, but check actual usage: a gateway may add caching itself. Count that baseline only if logs confirm the intended behavior.

Missing cache fields are inconclusive. Zero reads could reflect a short prefix, expiry, a changed prefix, routing changes, unsupported controls, or fields omitted by a translator. Ask the provider to reconcile a specific request rather than treating a missing field as free input or proof that caching is broken.

Why changing routes can reduce reuse

A fallback can send the next call to a different upstream cache. Do not assume a cache written on one provider, account, or endpoint exists on another. A route change may be useful for availability while still increasing cold-prefix costs. Evaluate total charged cost across retries and fallbacks.

OpenRouter documents cache-aware sticky routing, with fallback and provider-order caveats. That behavior should not be assumed for another service. For API Route, compare repeated calls using the same key, model, and selected route, then inspect the actual cache accounting. This guide does not promise cache affinity or an API Route hit rate.

For a production decision, sample your normal session lengths and idle gaps. Sum input, read, write, and output charges over the whole sample; include failures that generated a charge. Compare that with a clearly defined baseline. A high read share can still leave an output-heavy workload expensive.

Create an API Route account to test a representative prompt, review current Pricing, and use the quickstart to verify your first call. Fund ongoing usage only after the logs confirm your expected cost model.