Pricing note. The figures below use legacy Anthropic list prices, because they make the proportions easy to see. On codexkey, all Claude Opus and GPT-5 models bill at $0.50 / $0.50 per 1M, and all Sonnet and gpt-5.6-luna models at $0.33 / $0.33 per 1M. Real bills run 5-30× lower.
Haiku is in the catalog too:
claude-haiku-5.5andclaude-haiku-4.5. Check the models page for prices. They run in the Premium channel, not in the Cheap Haiku channel.
Why the Claude API bill grows so fast
Your Claude API bill has two parts: input tokens and output tokens. Teams tend to watch prompt length and miss that the real money is in the output.
Prices as of April 2026 for the three main models:
| Model | Input (per 1M tokens) | Output (per 1M tokens) | Output / Input ratio |
|---|---|---|---|
| Claude Opus 4.8 | $0.50 | $0.50 | 5× |
| Claude Sonnet 4.6 | $0.33 | $0.33 | 5× |
| gpt-5.6-luna | $0.33 | $0.33 | 5× |
Output costs 5× input across the whole lineup. A 500-token answer costs as much as 2,500 tokens of prompt. If you generate long-form text, reasoning chains or bloated JSON and never cap the length, the bill explodes.
Take a typical startup: 10 million Sonnet requests a month, 1,000 input tokens and 500 output tokens per request on average. It pays:
- Input: 10M × 1,000 / 1M × $3.00 = $30,000
- Output: 10M × 500 / 1M × $15.00 = $75,000
- Total: $105,000/month
Output is 71% of the bill. Each strategy below does one of two things: it cuts the token count, or it moves tokens to a cheaper tier. Some do both.
Strategy 1: prompt caching, 90% off repeated context
A system prompt, few-shot examples or RAG context is often identical across thousands of requests, and you pay full price for it every time. Anthropic's prompt caching keeps that prefix on the server and charges 10% of the normal input price when it is hit again.
How it works
You mark the blocks you want cached with cache_control: {'type': 'ephemeral'}. The first request costs a little more: the cache write carries a 25% premium. Every later request inside the cache TTL reads those tokens at a 90% discount. The TTL is currently 5 minutes and is extended on each hit.
Python example
import anthropic
client = anthropic.Anthropic()
# A 4,000-token system prompt — cached after first call
system_blocks = [
{
"type": "text",
"text": LARGE_SYSTEM_PROMPT, # your 4k-token instructions
"cache_control": {"type": "ephemeral"},
}
]
def ask(question: str) -> str:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system=system_blocks,
messages=[{"role": "user", "content": question}],
)
# Check cache performance
usage = response.usage
print(f"Cache read tokens: {usage.cache_read_input_tokens}")
print(f"Cache creation tokens: {usage.cache_creation_input_tokens}")
return response.content[0].text
TypeScript example
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const systemBlocks = [
{
type: "text" as const,
text: LARGE_SYSTEM_PROMPT,
cache_control: { type: "ephemeral" as const },
},
];
async function ask(question: string): Promise<string> {
const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
system: systemBlocks,
messages: [{ role: "user", content: question }],
});
console.log("Cache read:", response.usage.cache_read_input_tokens);
return response.content[0].type === "text" ? response.content[0].text : "";
}
What it saves
A 4,000-token system prompt and 1,000 requests per hour on Sonnet:
- Without caching: 4,000 × 1,000 × $3 / 1M = $12.00/hour
- With caching: first request at $3.75/M (write premium), remaining 999 at $0.30/M = ~$1.21/hour
- Savings: ~90%
No other change pays back this fast. If you ship only one thing from this article, ship the cache.
Strategy 2: model routing, a fitting model for each task
Not every request needs Opus, and gpt-5.6-luna cannot handle all of them. The idea is simple: work out what kind of task has arrived and send it to the cheapest model that can do it.
A practical split:
| Task type | Model | Why |
|---|---|---|
| Classification, tagging, extraction | gpt-5.6-luna | Fast, cheap, >95% accuracy on structured tasks |
| Summarization, generation, RAG answers | Sonnet | Best cost/quality ratio for open-ended generation |
| Complex reasoning, code generation, agentic loops | Opus | The premium pays off only when quality directly affects revenue |
Python router
import anthropic
client = anthropic.Anthropic()
MODEL_MAP = {
"classify": "gpt-5.6-luna",
"generate": "claude-sonnet-5",
"reason": "claude-opus-4.8",
}
def route_and_call(task_type: str, prompt: str, max_tokens: int = 1024) -> str:
model = MODEL_MAP.get(task_type, "claude-sonnet-5")
response = client.messages.create(
model=model,
max_tokens=max_tokens,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
# Classification — runs on gpt-5.6-luna at $0.80/M input
label = route_and_call("classify", "Classify this ticket: 'My order is late'")
# Generation — runs on Sonnet at $3/M input
summary = route_and_call("generate", f"Summarize this document: {doc}")
# Hard reasoning — runs on Opus at $15/M input
analysis = route_and_call("reason", f"Analyze this contract for risks: {contract}")
What it saves
Say 60% of your traffic is classification, 30% is generation and 10% is reasoning. Compared with running everything on Sonnet:
| Scenario | Monthly cost (10M requests) |
|---|---|
| All Sonnet | $105,000 |
| Routed (60% gpt-5.6-luna, 30% Sonnet, 10% Opus) | $55,200 |
| Savings | ~47% |
Add caching and the savings multiply.
Strategy 3: Batch API, half price for work that can wait
If the answer is not needed right away, use the Batch API. Nightly classification, eval sweeps, dataset labeling and content moderation all fit. You submit a JSONL file of requests, Anthropic processes them within 24 hours, and you pay exactly half the normal price on both input and output tokens.
Python example
import anthropic
import json
client = anthropic.Anthropic()
# Build requests
requests = []
for i, item in enumerate(dataset):
requests.append({
"custom_id": f"item-{i}",
"params": {
"model": "claude-sonnet-5",
"max_tokens": 256,
"messages": [
{"role": "user", "content": f"Classify: {item['text']}"}
],
},
})
# Submit batch
batch = client.batches.create(requests=requests)
print(f"Batch ID: {batch.id}, status: {batch.processing_status}")
# Poll for completion
import time
while True:
status = client.batches.retrieve(batch.id)
if status.processing_status == "ended":
break
time.sleep(60)
# Download results
for result in client.batches.results(batch.id):
print(result.custom_id, result.result.message.content[0].text)
When Batch API fits
- Eval runs across hundreds or thousands of test cases
- Overnight classification or labeling of large datasets
- Monthly content moderation sweeps
- Synthetic data generation for training
When to skip it
- Anything user-facing that requires
<5slatency - Streaming chat interfaces
- Agentic tool-use loops where the next step depends on the previous response
Batch API stacks with prompt caching. Cached tokens get the 90% discount, and the 50% batch discount covers the rest. There is no cheaper way to run Claude at scale.
Strategy 4: token budgeting, stop paying for what you don't need
The most direct saving on output is to generate less of it. Three techniques do that.
1. Set max_tokens tightly
When the answer is one word or a short JSON object, 4096 is pointless. Set the smallest plausible value. Claude stops generating when it hits the limit, and you stop paying.
# Bad: paying for up to 4096 output tokens
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=4096,
messages=[{"role": "user", "content": "Is this email spam? Answer yes or no."}],
)
# Good: paying for at most 16 output tokens
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=16,
messages=[{"role": "user", "content": "Is this email spam? Answer yes or no."}],
)
2. Add stop sequences
Tell the model to stop as soon as it emits a delimiter. This works especially well for structured data:
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=512,
stop_sequences=["</answer>"],
messages=[{"role": "user", "content": "Answer in <answer> tags."}],
)
3. Stream and stop early
With streaming, tokens arrive one by one, so you can cancel the request the moment you have what you need:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function classifyWithEarlyStop(text: string): Promise<string> {
let result = "";
const stream = client.messages.stream({
model: "claude-sonnet-5",
max_tokens: 64,
messages: [{ role: "user", content: `Classify: ${text}` }],
});
for await (const event of stream) {
if (
event.type === "content_block_delta" &&
event.delta.type === "text_delta"
) {
result += event.delta.text;
if (result.includes("\n")) {
await stream.abort(); // stop paying
break;
}
}
}
return result.trim();
}
Together, the three techniques cut output token spend by 30-60% on classification and extraction workloads.
Strategy 5: response prefilling, moving tokens from output to input
Claude's API lets you add an assistant message at the end of the message array to "pre-fill" the start of the response. The model continues from where you left off. The prefilled tokens count as input (cheap), not output (expensive).
Example: JSON extraction
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=256,
messages=[
{"role": "user", "content": f"Extract name, email, company from: {text}"},
{"role": "assistant", "content": '{"name": "'},
],
)
# Claude continues: John Doe", "email": "john@example.com", "company": "Acme"}
# The '{"name": "' prefix was charged at input rates, not output rates
Why it matters
Suppose a JSON response is 200 tokens. If you prefill 50 of them (keys, braces, formatting), 25% of the output cost moves to the input tier. On those tokens the price drops 4×.
There is a second benefit: reliability. Once Claude is already inside the JSON, it is far less likely to wrap the answer in a markdown fence or open with a preamble.
Strategy 6: structured output through tool use
You can ask Claude for JSON as plain text and parse it yourself. That is fragile, and the model burns extra tokens on explanations. A sturdier way is to force the structure through tool use (function calling).
Python example
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "classify_ticket",
"description": "Classify a support ticket",
"input_schema": {
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["billing", "technical", "general", "urgent"],
},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 5,
},
"summary": {
"type": "string",
"maxLength": 100,
},
},
"required": ["category", "priority", "summary"],
},
}
]
response = client.messages.create(
model="gpt-5.6-luna",
max_tokens=256,
tools=tools,
tool_choice={"type": "tool", "name": "classify_ticket"},
messages=[
{"role": "user", "content": f"Classify this ticket: {ticket_text}"}
],
)
# Response is guaranteed valid JSON matching the schema
tool_block = next(b for b in response.content if b.type == "tool_use")
result = tool_block.input # {'category': 'billing', 'priority': 3, 'summary': '...'}
TypeScript example
const response = await client.messages.create({
model: "gpt-5.6-luna",
max_tokens: 256,
tools: [
{
name: "classify_ticket",
description: "Classify a support ticket",
input_schema: {
type: "object",
properties: {
category: {
type: "string",
enum: ["billing", "technical", "general", "urgent"],
},
priority: { type: "integer", minimum: 1, maximum: 5 },
summary: { type: "string", maxLength: 100 },
},
required: ["category", "priority", "summary"],
},
},
],
tool_choice: { type: "tool", name: "classify_ticket" },
messages: [{ role: "user", content: `Classify this ticket: ${ticketText}` }],
});
const toolBlock = response.content.find((b) => b.type === "tool_use");
const result = toolBlock?.input;
What you get over free-text JSON:
- Guaranteed valid JSON: no parsing errors, no retry loops
- Fewer output tokens: Claude does not append explanations, caveats or markdown wrappers
- Schema enforcement by the API: enums, required fields and types are validated
Teams that move from "JSON in text" to tool use typically see a 20-40% drop in output tokens on extraction tasks.
Strategy 7: a gateway without extra markup, such as codexkey
The six strategies above reduce the number of tokens you consume. The seventh works on the price per token, or more precisely, strips the unnecessary surcharges around it.
Going straight to Anthropic's Claude API, you need:
- a non-Russian credit card or billing address;
- to cope with USD invoicing and currency conversion fees;
- to meet minimum spend or prepayment requirements on certain tiers.
codexkey is an OpenAI-compatible gateway. It proxies to Anthropic's official API at matching rates with zero markup, and adds:
- Payments in rubles: SBP (the Russian Faster Payments System), cards, crypto
- No minimum spend: pay as you go, even for a small project
- OpenAI SDK compatibility: change the base URL and keep your code
- All models: Sonnet, Opus, gpt-5.6-luna, including the latest releases
from openai import OpenAI
# Point your existing OpenAI SDK at codexkey
client = OpenAI(
base_url="https://api.codexkey.online/v1",
api_key="your-codexkey-key",
)
response = client.chat.completions.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello from codexkey!"}],
)
print(response.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.codexkey.online/v1",
apiKey: "your-codexkey-key",
});
const response = await client.chat.completions.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from codexkey!" }],
});
console.log(response.choices[0].message.content);
This is not about a "cheaper" API: codexkey prices match Anthropic's. It is about friction. Currency conversion overhead, billing complexity and geographic restrictions quietly add cost to any workflow.
Real-world cost calculation: before and after
Take the startup from the start of the article (10 million Sonnet requests a month, 1,000 input and 500 output tokens on average) and apply every strategy in turn.
| Step | Input cost | Output cost | Total | Savings vs baseline |
|---|---|---|---|---|
| Baseline (no changes) | $30,000 | $75,000 | $105,000 | - |
| + Prompt caching (70% of input cached) | $11,700 | $75,000 | $86,700 | 17% |
| + Model routing (60% to gpt-5.6-luna) | $5,148 | $33,000 | $38,148 | 64% |
| + Token budgeting (avg output → 300 tokens) | $5,148 | $19,800 | $24,948 | 76% |
| + Batch API on 30% of traffic | $4,378 | $16,830 | $21,208 | 80% |
| + Prefilling & structured outputs (−20% output) | $4,378 | $13,464 | $17,842 | 83% |
The bill goes from $105,000 to under $18,000. That is an 83% cut, and we have not touched prompt engineering to shorten the inputs.
Exact numbers depend on your workload mix, but the pattern holds: each new strategy multiplies the ones before it, so the savings build up faster than you expect.
FAQ
How is prompt caching priced?
Cache writes cost 25% more than normal input tokens ($3.75/M on Sonnet instead of $3.00/M). Cache reads cost 90% less ($0.30/M on Sonnet). The cache lives for 5 minutes and every hit resets the TTL. If your traffic is steady (at least one request every 5 minutes on the same cached prefix), you start saving in the very first cycle.
Is prompt caching compatible with the Batch API?
Yes. The cache discount applies to the cached part of the input, and the 50% batch discount covers everything else. The discounts multiply. A cached token in a batch request costs roughly 95% less than a regular input token.
What size does a prompt need to be cached?
Anthropic requires at least 1,024 tokens in a cache block (2,048 for Opus). If your system prompt is shorter, caching will not switch on. Add few-shot examples or detailed instructions to cross the threshold.
How do I pick the model for a request?
Start with a simple rule. If the task has a fixed output schema (classification, extraction, yes/no), use gpt-5.6-luna. If it needs open-ended generation at moderate quality, use Sonnet. Keep Opus for tasks where a mistake is expensive: legal analysis, complex code generation, multi-step agentic reasoning. Measure accuracy on your eval set and adjust the thresholds.
Can I use prefilling together with tool use?
No. Prefilling (a partial assistant message) and tool_choice are separate mechanisms. Use prefilling for JSON in free text and tool use for schema-checked output. Do not combine them in one request.
What happens if a batch fails partway through?
The Batch API processes each request on its own. If 5 out of 10,000 requests fail (say, because of a content policy), the other 9,995 go through as usual. Failed requests carry error details in the response JSONL. You pay only for the successful ones.
Do sync and batch share the same rate limit?
No. Batch requests do not count against your real-time rate limits. They run in a separate queue with higher aggregate throughput, so batch absorbs burst workloads that would otherwise force you to ask for higher limits.
How does codexkey differ from going to Anthropic directly?
codexkey proxies to Anthropic's official API, so model behavior, latency and features (caching, batching and tool use included) are the same. The difference is operational: codexkey accepts local payment methods (SBP, Russian cards, crypto), has no minimum spend, and offers an OpenAI-compatible endpoint, so switching takes a one-line change to the base URL.
Every strategy here is production-ready and can be rolled out in half a day. Start with prompt caching: the biggest return for the least work. Then add model routing, and bring in batching and token budgeting as usage grows.
Ready to start saving? Get your codexkey API key and apply these techniques today: pay in rubles, with no markup on Anthropic prices.