How to reduce Claude API cost, eight levers, measured
Most API bills are not high because the rate is bad. They are high because the same context is resent dozens of times, cheap work is done by an expensive model, and outputs are longer than anyone needs. Fix those three and the bill drops without touching quality.
Start with measurement, not with cuts
Before changing anything, pull one day of usage and answer three questions: which model accounts for most spend, which workload drives it, and what fraction is input versus output. Cuts applied blindly usually remove capability, not cost.
| Model | Gateway rate in / out per 1M tokens | Official list in / out per 1M tokens | Diff |
|---|---|---|---|
| claude-haiku-4-5 | $0.5 / $2.5 | $1 / $5 | 50% |
| claude-sonnet-5 | $1 / $5 | $2 / $10 | 50% |
| claude-opus-5 | $2.5 / $12.5 | $5 / $25 | 50% |
| deepseek-v4-pro | $0.66 / $1.98 | — / — | — |
Rates checked 2026-11-01. Gateway rates move with upstream promotions (verify the current number in your dashboard before committing to a budget).
Lever 1 — send less context (usually the biggest)
Every request resends the conversation. Compacting a long thread, starting a fresh one per task, and keeping unrelated files out of the prompt all reduce the same thing: tokens you pay for repeatedly. This is the highest-yield change for agent workloads.
Lever 2 — route by difficulty
Not every call needs the strongest model. Classification, extraction, formatting, and applying a change you already specified are mechanical. Escalate only what is genuinely ambiguous.
# cheap first, escalate only when confidence is low
def handle(task):
out = call("claude-haiku-4-5", task) # cheap pass
if out.confidence < 0.7:
out = call("claude-sonnet-5", task) # escalate
return out
Lever 3 — cap the output
Output tokens cost several times more than input. Set max_tokens to something sane and ask for the format you actually consume, a diff, a JSON object, a one-line answer. Long explanations you do not read are pure cost.
resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=300, # hard ceiling, not a suggestion
messages=[{"role": "user", "content": prompt}],
)
Lever 4 — reuse stable prefixes
When many requests share a large prefix (a fixed system prompt, a document, a codebase context) caching that prefix makes the repeated part much cheaper. It does nothing for one-off requests, so apply it where the prefix is genuinely stable.
Lever 5 — batch what does not need to be interactive
Offline work, backfills, bulk classification, nightly summaries, does not need low latency. Batch it, and it costs less than the same volume sent interactively.
Lever 6 — stop retrying blindly
Retries are the invisible line item. A 5% failure rate with unconditional retries is 5% of spend producing nothing. Retry on transient errors only, with backoff, and log every retry so it stops being invisible.
Lever 7 — shorten the loop
Ten exploratory requests cost more than one well-specified one, and often produce a worse result. Write the constraint into the prompt up front: what to change, what not to touch, what the output should look like.
Lever 8 (compare the rate), not the brand
The same model can cost materially different amounts depending on where you route it. That comparison is the whole point of a pricing index, and it is worth re-checking periodically, because rates move.
Put a number on it
Run your own workload through the cost calculator before and after each change. A lever that does not move that number did not work, however sensible it sounded.
Where the money actually goes, ranked
Cost advice tends to be a list of tricks with no ordering. These five are ordered by how much they typically move a real bill, and each one names the number that tells you whether it worked.
| Lever | Why it works | What to measure to know it worked |
|---|---|---|
| Switch to a cheaper model | Unit price drops across every token you send. | Output quality on your actual task (not on a benchmark). |
| Cut context per request | Input is re-sent every turn on agent workloads; trimming it compounds. | Tokens per task, before and after. |
| Cache the stable prefix | A cached prefix is billed below the uncached rate where the provider supports it. | Cache-hit rate in the usage response. |
| Move non-urgent work to batch | Batch tiers trade latency for a lower rate; the work is identical. | Nothing changes in quality, only in when it finishes. |
| Cap output length | Only helps when output genuinely dominates, which it does on generation tasks. | Whether output is actually the larger half of your bill. |
Concretely: one day at 1200k input / 16k output costs $3.20 on claude-opus-5 and $1.28 on claude-sonnet-5 (a difference of $1).92 per day, or $38.40 over a 20-day month.
The ordering is not universal, it depends on your token shape. On agent loops, context size outranks model choice; on bulk generation, model choice wins because there is no context to trim. Work out which half of the sum your workload sits on before spending effort on the wrong lever. Our output vs input pricing breakdown covers how to tell.
When this is the wrong move
Cost-cutting is not free These are the cases where it backfires.
| Situation | Why it breaks | Do this instead |
|---|---|---|
| Output quality gates a release | A cheaper model that fails 5% more often costs engineer time that exceeds the token saving. | Keep the strong model on the critical path and cut cost elsewhere in the pipeline. |
| Your workload is already context-dominated | Switching models leaves the dominant term, re-sent context, untouched. | Trim context first, then re-evaluate whether a model change still matters. |
| Latency is part of the product | Cheaper routes can be slower, and batch tiers are slower by design. | Optimise for the interactive path and move only offline work to the cheap route. |
| You have not measured tokens per task yet | Without a baseline, every lever looks like it worked. | Instrument tokens and cost per task first, then change one thing at a time. |
How to confirm the change actually took effect
"I set it and nothing happened" is the most common failure here, and it is almost never the endpoint. It is usually the client: a desktop app that reads its own settings file rather than your shell environment, a process started before you exported the variable, or an SDK client instance built once at import time and reused after the config changed. Verify before you debug anything else.
Layer 1 (is the endpoint reachable), and does your key work?
curl -s https://aicomp.ai/v1/models \
-H "Authorization: Bearer $KEY" | head -c 400
A JSON list of model IDs means both are true. An Invalid token body means the
endpoint answered but rejected the credential, the URL is right, the key is not. An HTML page
means something between you and the endpoint (a corporate proxy, a captive portal) answered
instead, and no amount of config change will fix it from inside your app.
Layer 2 — who actually answered?
from openai import OpenAI
client = OpenAI(base_url="https://aicomp.ai/v1", api_key=KEY)
r = client.chat.completions.create(
model="gpt-5.6-luna",
messages=[{"role": "user", "content": "Reply with the single word: ok"}],
max_tokens=8,
)
print(r.model) # 回显的 model 是谁 —— 这是端点身份的唯一硬证据
print(r.usage) # prompt_tokens / completion_tokens
The model field echoed back is the only hard evidence of identity. A request that
silently fell through to a default provider still returns HTTP 200 and still returns sensible
text, which is exactly why this layer exists. If the echoed model is not the one you asked for,
you are talking to someone else.
Layer 3 — did it land in the usage log?
Reachability is not billing. Open the provider's usage view and look for the model ID you just called, on today's date. If the call succeeded but does not appear, you either hit a cached response or you are still on the old path. This layer is also the one that catches the case where two environments share a key and you are reading the wrong one.
Three ways this check lies to you
- A 200 that is not from the API. Proxies and error pages return 200 with HTML. Check that the body parses as JSON before you trust the status code.
- A stale process. Long-running servers and editors capture configuration at startup. Restart the process, not just the shell.
- A client built before the change. SDK clients snapshot
base_urlat construction. If yours is a module-level singleton, the new value never reaches it.
FAQ
What is the single biggest way to reduce Claude API cost?
Sending less context. Because every turn resends the conversation, halving your average context roughly halves input cost (with no change in output quality).
Is switching to a cheaper model safe for production?
For a subset of calls, yes. The safe pattern is to classify requests first: mechanical ones (classification, extraction, formatting, simple edits) go to the cheap model, and only ambiguous or reasoning-heavy ones escalate.
Does prompt caching help?
Yes, when the same large prefix is reused across requests, a big system prompt, a fixed document, or a stable codebase context. It does almost nothing for one-off requests with unique prefixes.
Do shorter outputs really save that much?
Usually more than people expect, because output is priced several times higher than input. Asking for a diff instead of an explanation of the diff is a real saving, not a rounding error.
How do I stop a runaway bill?
Set a top-up ceiling at the account level and add a per-request max_tokens. Neither is a substitute for monitoring, but together they bound the damage.
Related
- What Claude Code costs per month
- How to monitor AI API spend
- Batch vs realtime — when waiting 24h pays
- Prompt caching — what a cache hit actually costs
- Why the same model has two prices
- Output vs input pricing (the 4).0× median ratio
- 226 models ranked by token mix
- Cost calculator — model your own workload
- Full model pricing index