top of page

Cutting Your Claude API Bill: Caching, Routing, and Batching That Actually Work

Writer: MacSmithAI
MacSmithAI
Jul 1
5 min read

Most teams overspend on the Claude API for the same three reasons: they reprocess identical context on every call, they send everything to the biggest model, and they run real-time requests that could have waited. Fix those and a typical production workload drops to a fraction of its unoptimized cost. Here's how each lever works, with current pricing and the pitfalls that quietly kill the savings.


All prices below are from Anthropic's official pricing page as of mid-2026, per million tokens (MTok). I'm using the current lineup — Opus 4.8 at $5/$25, Sonnet 4.6 at $3/$15, Haiku 4.5 at $1/$5 — and flagging the newer Sonnet 5 introductory rate where it matters. Verify against claude.com/pricing before you build a model around a number; rates move.


Lever 1: Prompt caching (the big one)

Every API call re-sends and reprocesses your full input — system prompt, tool schemas, documents, conversation history — even when 95% of it is byte-for-byte identical to the last call. Prompt caching stores the processed version of that static prefix so subsequent calls read it back at a fraction of the price instead of reprocessing it.

The economics, straight from the pricing multipliers:

  • Cache read (hit): 0.1x base input — a 90% discount on cached tokens.

  • 5-minute cache write: 1.25x base input. Pays for itself after a single read.

  • 1-hour cache write: 2x base input. Pays for itself after two reads.

On Sonnet 4.6, that turns $3/MTok input into $0.30/MTok on every cache hit. The write premium is small and one-time; the reads are where you live. The catch is that caching is prefix-based and requires an exact match — cache hits need the cached segment to be 100% identical, byte for byte, up to the cache breakpoint.

That single fact is the whole game. Structure your request so everything static comes first and everything dynamic comes last:

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    # tool schemas are cacheable — put them up front
    tools=tool_definitions,
    system=[
        {
            "type": "text",
            "text": SYSTEM_PROMPT,          # static: identical every call
            "cache_control": {"type": "ephemeral"}
        }
    ],
    messages=[
        {"role": "user", "content": user_message}  # dynamic: comes last
    ]
)

Then confirm it's working. The response's usage object tells you exactly what happened:

usage = response.usage
print(usage.cache_creation_input_tokens)  # tokens written to cache
print(usage.cache_read_input_tokens)      # tokens served from cache (cheap)
print(usage.input_tokens)                 # tokens after the last breakpoint

One thing that trips people up: input_tokens only counts tokens after your last cache breakpoint, not your whole input. If you're caching a 100K-token document and sending a 50-token question, input_tokens reads 50 — which is correct, not a bug, and matters for how you read both cost and rate limits.


You have two ways to turn it on. Automatic caching — a single cache_control at the top level — manages breakpoints as conversations grow and is the right default for multi-turn work. Explicit breakpoints let you cache sections with different change frequencies independently. Adding more breakpoints doesn't cost more; you still pay only for what's actually cached and read.


The most underrated lever: audit your prefix for dynamic content

A single moving value — a timestamp, a per-user field, a bit of stray whitespace — near the front of your "static" prefix invalidates the cache on every call. There's a widely cited case of a deployment whose hit rate jumped from single digits to the mid-70s after relocating one dynamic field to the back of the prompt. If your cache hit rate is under 20% after a week, it's almost always a prefix-design problem, not a model problem. And be aware caching can fail silently: if the cacheable section is below the minimum length (1,024 tokens on current models), the request still succeeds but the usage fields show zero cache activity. Log them, or you won't know.


Where caching earns the most: large fixed documents (50K+ tokens), high call volume, long multi-turn conversations, and — relevant if you're building on MCP — agents with big tool schemas. An MCP-connected agent can carry thousands of tokens of tool definitions on every single request, and those definitions are cacheable. (More on MCP in the fleet-management series.)


Lever 2: Model routing

Sending every request to Opus is the most common form of overspend. Output tokens cost 5x input across the tiers, and Opus output is $25/MTok against Haiku's $5 — so a classification task that lands on Opus costs five times what it should for zero quality gain. There is rarely a reason to send classification, extraction, or routing decisions to your most expensive model.

The pattern that works: a cheap model triages, and only genuinely hard work escalates.

# Haiku classifies; Sonnet or Opus only when needed
triage = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=10,
    messages=[{"role": "user",
               "content": f"Simple or complex? Reply one word.\n\n{task}"}]
)

if "complex" in triage.content[0].text.lower():
    model = "claude-opus-4-8"     # hard reasoning
else:
    model = "claude-haiku-4-5"    # everything routine

The triage call itself is nearly free — a handful of Haiku tokens — and it keeps the expensive model off work that doesn't need it. A rough division of labor: Haiku 4.5 for high-volume classification, extraction, and routing; Sonnet 4.6 as the default for most production work (it's the price/quality sweet spot); Opus 4.8 reserved for the genuinely hard reasoning and long-horizon agent tasks.


One current wrinkle worth knowing: Opus 4.7 and later, plus Sonnet 5, use a newer tokenizer that produces roughly 30% more tokens for the same text. The per-token price didn't change, but your token counts can, so measure real usage on the exact model you ship rather than assuming parity with an older one.


Lever 3: The Batch API

If a workload doesn't need an answer this second, the Batch API processes requests asynchronously — returned within 24 hours — at a flat 50% off both input and output. That's Opus 4.8 dropping to $2.50/$12.50, Sonnet 4.6 to $1.50/$7.50, Haiku 4.5 to $0.50/$2.50.

It fits anything offline: overnight document processing, bulk classification, dataset generation, evaluation runs, backfills. The mental test is simply whether a human is waiting on the response. If not, batch it and take the discount.


Better still, batching and caching stack — the 50% batch discount applies on top of cached-token pricing. Two caveats: batch results aren't real-time (don't put a user-facing chat endpoint through it), and batch mode can't be combined with Fast mode.


Stacking them, and the one rule underneath all three

These levers compound. A pipeline that caches its stable prefix, routes by task complexity, and batches its offline work can run at a small fraction of a naive all-Opus, no-cache implementation. Three moves are worth doing this week even without a full optimization pass: add caching to your largest system prompt, instrument cache hit rate in whatever dashboard you already have, and audit your prefixes for the timestamp-and-whitespace anti-patterns that silently break caching.


But none of it works without measurement. Every optimization here depends on numbers you can only get from logging cache_creation_input_tokens, cache_read_input_tokens, input_tokens, and output_tokens per request. Published savings figures — "60% off," "90% off" — are real for the workloads they describe and meaningless as a promise for yours. A cache hit rate depends entirely on how repetitive your traffic is; a routing win depends on how much of your volume is actually simple. Treat any headline number as a target to validate against your own logs, not a result you'll automatically get.


The honest version of the pitch: the API isn't expensive, unoptimized usage is. The teams that spend the least aren't on a secret rate — they just stopped paying full price for identical tokens.


Where to start

Take your single highest-volume endpoint and add automatic prompt caching to its system prompt and tool definitions today — one cache_control field. Then log cache_read_input_tokens for a day and check the hit rate. If it's low, you've almost certainly got a dynamic value sitting in your prefix — move it to the back before you touch anything else. That one change, measured, tells you more about your bill than any pricing table will.

Next in the series: Part 3 — Managing Token Costs in Claude Code on Your Mac. The built-in controls — /usage, /clear, /compact, plan mode — and how to keep a coding agent's context lean.

Comments


bottom of page