← all articles

How to cut your LLM API bill in half

If you’re running any kind of production LLM workload, you’ve probably had the same moment I did: you open the Anthropic console, look at the monthly total, and wonder how a “just call the API” project turned into a four-figure line item. Most teams don’t overspend because the model is expensive. They overspend because they’re re-sending the same context on every call, using a bigger model than the task needs, and running everything synchronously when half of it could wait an hour.

This tutorial is for anyone calling the Claude API in production, whether it’s a content pipeline, a support bot, a data extraction job, or an internal tool, and wants to cut spend without cutting quality. I’m not going to tell you to “use a smaller model” and leave it there. We’re going to go through the actual levers, in order of impact: prompt caching, batch processing, model selection, and token discipline. Done together, these routinely cut a bill by 50% or more, and I’ve seen worse-architected pipelines drop by 70-80% just from caching alone.

The outcome by the end: you’ll have a checklist and working code you can run against your own usage, plus a sense of what to watch as your call volume goes from hundreds a day to millions.

what you need

  • An Anthropic API key with billing set up (console.anthropic.com)
  • The anthropic Python SDK (pip install anthropic) or the equivalent TypeScript/Go/Ruby SDK
  • At least one existing workload calling messages.create() so you have something to measure and optimize
  • Access to your own request logs, or a willingness to add basic usage logging if you don’t have it yet
  • No extra infrastructure cost for most of this — caching and batching are request-level parameters, not new services

step by step

1. Measure before you touch anything

Before optimizing, find out where the money is actually going. Pull your last week of requests and bucket them by: which model, average input tokens, average output tokens, and whether the same context (system prompt, retrieved documents, few-shot examples) repeats across calls.

If you don’t have per-request cost logging, the usage object on every response gives you input_tokens, output_tokens, cache_creation_input_tokens, and cache_read_input_tokens. Log all four from day one.

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "..."}],
)
print(response.usage)

Expected output: a usage breakdown per call, e.g. input_tokens=1200, output_tokens=340, cache_read_input_tokens=0. If cache_read_input_tokens is zero across every request, you have zero caching in production right now, which is usually the single biggest opportunity.

If it breaks: if usage looks empty or your SDK version doesn’t expose these fields, upgrade the SDK. Older SDK versions predate the newer usage fields.

2. Pick the cheapest model that clears your quality bar

As of writing, current Anthropic per-million-token pricing is:

Model Input Output
Claude Opus 4.8 $5.00 $25.00
Claude Sonnet 5 $3.00 (intro $2.00 through 2026-08-31) $15.00 (intro $10.00)
Claude Haiku 4.5 $1.00 $5.00

(Full current pricing is on Anthropic’s pricing page.)

Most pipelines default to the flagship model out of habit and never revisit it. Classification, tagging, simple extraction, and short-form summarization are usually fine on Haiku 4.5 at a fifth of Opus pricing. Reserve Opus 4.8 for the steps where reasoning quality actually changes the outcome, and Sonnet 5 as the default for coding and agentic work where you need near-Opus quality without Opus cost.

Expected output: run the same eval set (even 20-30 real examples) against Haiku, Sonnet, and Opus and compare accuracy. Most teams find at least one pipeline stage can drop a tier with no measurable quality loss.

If it breaks: if quality drops noticeably on a cheaper model, don’t force it — a bad answer that needs a human to redo the work costs more than the tokens you saved. Move only the stages that survive the eval.

3. Turn on prompt caching for anything repeated

This is usually the single highest-leverage change. If your system prompt, a retrieved document, or a set of few-shot examples repeats across calls, caching lets you pay full price once and roughly 10% of input price on every read after that.

response = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=1024,
    system=[{
        "type": "text",
        "text": LONG_SYSTEM_PROMPT,
        "cache_control": {"type": "ephemeral"}
    }],
    messages=[{"role": "user", "content": user_question}]
)
print(response.usage.cache_read_input_tokens, response.usage.cache_creation_input_tokens)

Cache writes cost about 1.25x the base input price (5-minute TTL) or 2x (1-hour TTL); cache reads cost about 0.1x. That means the write pays for itself after roughly two reads on the short TTL, or three on the long one. For a support bot or content pipeline with a stable system prompt getting hit dozens of times an hour, this is close to free money.

Expected output: cache_read_input_tokens populated on the second and subsequent calls with the same prefix.

If it breaks: if cache_read_input_tokens stays at zero, something in your prompt prefix is changing byte-for-byte between calls — a timestamp, a user ID, or unsorted JSON keys interpolated into the system prompt. Caching is a strict prefix match; move anything volatile to the end of the prompt, after the cached block. See Anthropic’s prompt caching docs for the full mechanics.

4. Move non-interactive workloads to the Batch API

If a workload doesn’t need a response in real time, an overnight classification job, bulk summarization, dataset labeling, use the Message Batches API instead of synchronous calls. It’s a flat 50% off every token, on top of whatever caching savings you already have, in exchange for results landing within an hour (up to 24 hours max).

batch = client.messages.batches.create(
    requests=[
        {
            "custom_id": f"item-{i}",
            "params": {
                "model": "claude-haiku-4-5",
                "max_tokens": 200,
                "messages": [{"role": "user", "content": text}]
            }
        }
        for i, text in enumerate(items)
    ]
)

Expected output: a batch ID and processing_status. Poll client.messages.batches.retrieve(batch.id) until status is ended, then stream results with client.messages.batches.results(batch.id).

If it breaks: results come back in arbitrary order, keyed by custom_id — if your downstream code assumes response order matches request order, that’s the bug, not the API.

5. Stop overstuffing the context window

Bigger context windows made it tempting to just dump everything in. But every token you send is billed whether the model needed it or not. Audit what’s actually going into each request: are you sending an entire document when a retrieved excerpt would do? Are you replaying a full multi-turn conversation history when only the last few turns are relevant?

For long-running agent sessions specifically, look at context editing (clearing stale tool results) and compaction (server-side summarization of old turns) rather than manually truncating and losing state. Both keep a long-running session from re-billing the same bloated history on every turn.

Expected output: a measurable drop in average input_tokens per call once you trim retrieval chunks or cap conversation history.

If it breaks: if trimming context degrades answer quality, you cut too aggressively — add back only the minimum that restores it, and re-test.

6. Set max_tokens and effort deliberately

Every output token is billed at 5x the input rate on Sonnet 5 and Opus 4.8. An oversized max_tokens doesn’t cost you anything by itself, but an unconstrained prompt that rambles because you didn’t ask for brevity does. For models that support the effort parameter, set it explicitly rather than leaving it at the default — low or medium is often enough for routine extraction and classification work, with high reserved for the steps that actually need deep reasoning.

Expected output: shorter, on-target responses and a visible drop in output_tokens in your usage logs without a drop in answer quality.

If it breaks: if responses start getting cut off mid-answer, that’s max_tokens too low, not effort — raise the cap and stream the response so you’re not stuck waiting for a huge synchronous reply.

7. Count tokens before you send, not after you’re billed

Use the token counting endpoint to estimate cost before a request goes out, especially anywhere you’re assembling a prompt dynamically (retrieved docs, injected examples).

count = client.messages.count_tokens(
    model="claude-sonnet-5",
    system=system_prompt,
    messages=messages,
)
print(count.input_tokens)

This is also how you catch cost regressions early: if a code change to your prompt-building logic suddenly doubles input_tokens, you want to know before it shows up on next month’s invoice, not after. Do not estimate with a generic tokenizer like tiktoken — it’s built for OpenAI models and undercounts Claude tokens meaningfully, especially on code.

Expected output: a token count that matches what usage.input_tokens reports on the real call.

If it breaks: if counts drift from what you’re actually billed, double check you’re passing the exact same system, messages, and model to count_tokens as to messages.create — it’s model-specific and prompt-shape-specific.

common pitfalls

  • Caching a prompt that changes every request. If you interpolate a timestamp, request ID, or user-specific string into the system prompt, you invalidate the cache on every single call and pay the 1.25x write premium with no read benefit, ever. Move dynamic content after the cached block.
  • Defaulting every stage to the flagship model. It’s the easiest mistake to make and the easiest to fix. Not every step in a pipeline needs Opus-tier reasoning; test each stage independently.
  • Batching workloads that actually need low latency. Batch API results can take up to 24 hours. Don’t route anything user-facing through it just to chase the 50% discount — that’s a support ticket waiting to happen.
  • Ignoring the 20-block cache lookback window. In long agentic loops with many tool calls, a single turn can add more content blocks than the cache lookback can walk back through, and the next request silently misses the cache. Place intermediate cache breakpoints in long tool-calling sessions.
  • Never re-measuring after a prompt change. Teams optimize once, then a colleague adds a new instruction to the system prompt six weeks later and nobody notices input_tokens crept back up. Bake usage logging into your pipeline, not a one-time audit.

scaling this

At 10x your current volume, the fixes above are still enough. Caching and batching scale linearly with call volume, so the percentage savings you measured at your current scale roughly holds.

At 100x, start segmenting by workload type rather than treating every call the same. Route real-time user-facing traffic through cached, low-latency calls on the cheapest viable model, and push everything that can tolerate delay (nightly re-indexing, backfills, evaluation runs) into batch jobs. This is also the point where a stale cache entry or a broken prefix costs real money fast, so add automated alerting on cache_read_input_tokens dropping unexpectedly.

At 1000x, you’re running enough volume that per-model rate limits and RPM/TPM ceilings start to matter as much as per-token pricing. Spread load across models deliberately, negotiate custom rate limits if you’re on a scaling plan, and treat token-budgeting (via the effort parameter and explicit max_tokens caps) as a first-class part of your architecture rather than an afterthought. If you’re running this behind a content pipeline instead of a single app, the same batching logic that saves money on LLM calls also applies to your outbound crawling and scraping infrastructure, worth a look at how proxyscraping.org covers cost-efficient request routing at scale.

where to go next

Written by Xavier Fok

disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-07-17.

for builders
Running agents or scrapers at scale?

AI pipelines that crawl, research, or automate the web hit rate limits and geo-blocks fast. Singapore Mobile Proxy runs real 4G/5G mobile IPs that carriers still trust.

see plans →
read on
More from the Gazette

Tool reviews, model and pricing news, and build guides for people shipping real things with AI.

browse all articles →