How to Cut Your Claude API Bill: A Practical Token-Cost Guide
A single “summarize this ticket” call can quietly turn into a 20,000-token bill if you keep shipping the same giant system prompt, three prior messages, full tool schemas, and a 2,000-token answer when you only needed 120 words. In practice, that’s how teams end up blaming the model when the real problem is request shape.
If you want to cut your Claude API bill, the biggest wins usually come from engineering discipline, not model mysticism: reuse stable prefixes, trim context aggressively, choose the smallest model that fits, cap outputs, and measure waste at the request level. The goal is simple: pay for useful tokens, not accidental ones.
The Token Budget Mental Model
Every request has two cost drivers:
- Input tokens: system prompt, user message, conversation history, tool definitions, retrieved docs, and any cached prefix that still counts when uncached.
- Output tokens: the model’s response, including accidental verbosity and retries.
A useful mental formula is:
request_cost ≈ input_tokens × input_rate + output_tokens × output_rate
That sounds obvious, but it changes how you debug spend. If the average request is “cheap” on output but expensive on input, then shaving 400 tokens from the system prompt may matter more than asking the model to be terser.
Start With Model Choice
Not every task deserves the most capable model. In real systems, model selection is often the cheapest optimization.
| Task shape | Good default | Why it works | When to avoid |
|---|---|---|---|
| Fast classification, extraction, routing | Haiku | Low latency, smaller bills, usually enough reasoning | Multi-step analysis, messy docs |
| Standard coding help, summaries, transforms | Sonnet | Strong balance of quality and cost | Long-horizon planning, ambiguous reasoning |
| Hard reasoning, deep synthesis, fragile instructions | Opus | Best when failure is costly | Routine tasks with repetitive structure |
| Huge-context retrieval-heavy work | Larger-context model when required | Reduces chunking complexity | Only if the task truly needs it |
A common gotcha is “defaulting up.” Teams often send every prompt to the top-tier model because it feels safer. In practice, that’s expensive insurance on tasks that don’t benefit from it. A routing layer is usually worth the effort:
- Haiku for quick classification, JSON extraction, log labeling, and first-pass triage.
- Sonnet for most agentic or developer-tool workloads.
- Opus only when the task really needs stronger reasoning, nuanced synthesis, or you’ve measured failure cost.
A simple policy that works well is:
- Try Haiku for cheap structured tasks.
- Escalate to Sonnet if confidence is low or the output is malformed.
- Escalate to Opus only for genuinely hard cases.
That kind of fallback chain is usually cheaper than paying Opus rates on every request.
Trim the System Prompt Ruthlessly
The system prompt is where token waste hides in plain sight. It tends to grow like technical debt: a policy line here, a style rule there, five examples nobody removed.
What to remove
- Duplicate instructions that the application already enforces.
- Long “be helpful” prose that doesn’t change behavior.
- Old examples for edge cases that no longer matter.
- Policy text repeated in every request.
- Verbose tool descriptions when a compact schema is enough.
What to keep
- Non-negotiable behavioral constraints.
- Output format rules.
- Safety-critical instructions.
- Minimal examples that materially improve reliability.
In practice, I’ve seen teams cut hundreds or thousands of tokens from the repeated prefix without changing model quality. The trick is to treat the system prompt like production code: delete dead text, dedupe rules, and keep only what moves behavior.
A better pattern is to encode structure instead of prose:
{
"style": "concise",
"output": "json",
"must_include": ["root_cause", "next_steps"],
"must_avoid": ["speculation", "apologies"]
}
That is easier to reason about than three paragraphs of narrative instructions.
Use Prompt Caching for Stable Prefixes
If your stack supports prompt caching, use it for anything that repeats across requests: system prompts, tool schemas, few-shot examples, and long policy blocks.
The important idea is not “caching is free.” It isn’t. The win is that you stop re-paying full price for the same prefix on every call.
Good caching candidates
- Product-wide system prompts
- Large function/tool definitions
- Reused retrieval instructions
- Static examples for formatting or style
- Agent scaffolding that rarely changes
Bad caching candidates
- Per-request user context
- Fresh retrieved documents
- Rapidly changing instructions
- Session-specific scratchpad content
The common mistake is to put highly variable content in the prefix and then wonder why cache hits are poor. To get value, keep the cached segment stable and move request-specific data after it.
Example request shape:
{
"system": "Stable application prompt + tool definitions + output contract",
"messages": [
{ "role": "user", "content": "New ticket text here" }
]
}
If you keep the stable part stable, you give caching a chance to do real work. If the prefix changes every request, you’re just paying cache management overhead for no benefit.
Set max_tokens Like You Mean It
One of the easiest silent costs is an oversized completion cap. If you set max_tokens to something huge “just in case,” you invite long responses, especially when the model is uncertain or verbose.
Use the smallest cap that still covers the task.
Practical ranges
- Extraction / classification: 32–128 tokens
- Short summaries: 128–256 tokens
- Code diffs / explanations: 256–800 tokens
- Open-ended generation: only as high as necessary
If the task is “return one JSON object,” give it a hard ceiling. If the task is “explain this architecture,” don’t leave the model room to write a novel unless you want one.
A lot of teams also forget to cap retry loops. If a parse fails and you automatically ask the model to try again with the same generous output limit, you can double or triple output spend on a single user action.
Batch Small Jobs Together
Batching helps when you have many small, independent tasks with similar instruction overhead.
Instead of this:
- 50 separate classification requests
- 50 repeated system prompts
- 50 repeated tool definitions
Do this:
- One request containing 50 items
- One shared instruction block
- One compact output schema
Example prompt pattern:
Classify each item into one of: bug, feature, question, billing.
Items:
1. "App crashes after login"
2. "Can you add dark mode?"
3. "How do I reset my password?"
And the response contract:
[
{"id": 1, "label": "bug"},
{"id": 2, "label": "feature"},
{"id": 3, "label": "question"}
]
This cuts repeated overhead and often improves throughput. The trade-off is failure blast radius: if one item breaks format, the whole batch may need recovery. For high-value or user-facing latency-sensitive paths, smaller batches may still be better.
Streaming Helps Responsiveness, Not Token Cost
Streaming is great for UX and sometimes for cost control, but it does not magically reduce billed tokens by itself. You still pay for whatever the model generates.
Where streaming does help is in early stopping:
- The user sees progress sooner.
- Your application can cancel once it has enough content.
- You can stop a runaway answer before it reaches the configured cap.
That last one matters. If you’re generating an answer for a downstream parser and the first 120 tokens already contain the needed JSON, don’t keep collecting another 800 tokens of explanation.
In practice, streaming is worth it when your app can react mid-generation. If you just stream to a log and wait for the whole answer anyway, you get the UX benefit but not much cost reduction.
Measure Cost Per Request, Not Just Per Month
Monthly spend tells you you have a problem. Request-level metrics tell you where it is.
Track at least:
input_tokensoutput_tokenscache_hit_tokensif available- model name
- endpoint / feature path
- latency
- retry count
- truncation or parse failure rate
A simple logging payload might look like this:
usage = response["usage"]
record = {
"model": response["model"],
"input_tokens": usage["input_tokens"],
"output_tokens": usage["output_tokens"],
"latency_ms": latency_ms,
"cache_hit_tokens": usage.get("cache_read_input_tokens", 0),
"request_type": "ticket_summary",
}
print(record)
Then compute cost centrally:
def estimate_cost(input_tokens, output_tokens, input_rate, output_rate):
return (input_tokens * input_rate) + (output_tokens * output_rate)
The exact rates come from your provider and model, but the workflow stays the same. Once you can rank requests by cost, the culprits usually jump out:
- A “small” prompt with a giant hidden prefix
- A tool call that re-sends full documents every turn
- A completion cap that is 10x what the task needs
- A retry loop that silently doubles usage
- A route that should be Haiku but is always Opus
Avoid Silent Waste
Most token waste is boring, which is why it survives code review.
Common offenders
- Re-sending the same long conversation history every turn
- Including full source files when only a diff is needed
- Asking for prose when structured JSON would do
- Returning verbose explanations to machine consumers
- Passing tool schemas that are larger than the actual task
- Letting auto-retries re-run the same expensive prompt
- Keeping logs or internal notes inside the model context
A particularly nasty gotcha is “helpful context expansion.” Retrieval systems often fetch too much because someone wanted to be safe. If the model only needs the top three relevant paragraphs, do not feed it the whole document just because the chunking layer made that easy.
Another one: if you wrap the model with a second prompt that says “be concise,” you may have just added tokens to solve a verbosity problem that should have been solved by a tighter output schema.
A Practical Request Design
Here’s a pattern that works well for many production tasks:
request = {
"model": "claude-sonnet-4.6",
"max_tokens": 180,
"system": "Extract the root cause and next action. Return strict JSON.",
"messages": [
{
"role": "user",
"content": "Investigate this incident summary: ... "
}
]
}
Why this shape works:
- The model choice is strong enough for moderate reasoning.
- The output cap limits runaway verbosity.
- The system prompt is short and specific.
- The output format is machine-friendly.
- The user payload contains only the necessary task input.
If you need more context, add it intentionally. Don’t let context accrete by accident.
What Actually Moves the Bill
If I had to rank the biggest wins, it would be:
- Choose a cheaper model for easy tasks
- Cut prompt bloat
- Cap
max_tokensaggressively - Use prompt caching for stable prefixes
- Batch small independent jobs
- Measure request-level usage
- Stop retry loops from doubling spend
That order matters. Teams often start with exotic optimization before fixing the obvious: they are simply asking too much, too often, from the most expensive model with too much context.
Practical Takeaways
- Use Haiku for simple extraction and routing, Sonnet for most product work, and Opus only when the task justifies it.
- Treat the system prompt as a cost center; delete repeated prose and keep only behavior-changing instructions.
- Turn on prompt caching for stable prefixes like policies, tool schemas, and few-shot examples.
- Set
max_tokensto the smallest useful ceiling and revisit it per task. - Batch small independent jobs when the instruction overhead dominates.
- Track input tokens, output tokens, cache hits, retries, and latency per request.
- Watch for silent waste: repeated history, oversized retrieval, verbose outputs, and auto-retry loops.
If you want, I can turn this into a companion post with a concrete cost dashboard example and a few routing rules for Haiku/Sonnet/Opus.
One API key for Claude Opus 4.8, Sonnet 4.6, Haiku 4.5, Fable 5, plus GPT & Gemini — up to 80% off official pricing, pay-as-you-go.
Get Your API Key →