Choosing the Right Model & Controlling Cost
Pick the smallest model that clears the bar, then cut spend with prompt caching and batching before you reach for anything fancier.
The single biggest lever on quality, latency, and cost in a Claude-powered app is which model you send each request to — and it is a per-feature decision, not a per-app one. A senior habit is to route different features to different models rather than picking one model and using it everywhere.
Smallest-that-works
Start each feature on the cheapest model that could plausibly do the job, measure it against real inputs, and only step up if quality falls short. As a rule of thumb:
- Claude Haiku 4.5 (
claude-haiku-4-5-20251001) — classification, short replies, routing, cheap high-volume work. - Claude Sonnet 5 (
claude-sonnet-5) — the balanced default for most production features. - Claude Opus 4.8 (
claude-opus-4-8) — the hardest reasoning and long agentic tasks.
Only the model string changes; the rest of your code stays identical.
Prompt caching pays for the big prefix
If many requests share a large, stable prefix — a long system prompt, a product catalogue, a document you answer questions about — mark it with cache_control. Cached tokens read at roughly a tenth of the normal input price. The one rule to internalise: caching is a prefix match, so keep everything volatile (timestamps, the user's actual question) after the cached block. A datetime.now() at the top of your system prompt silently invalidates the whole cache every request.
Batch what is not time-sensitive
Nightly summaries, bulk classification, back-fills — anything a human is not waiting on — belongs on the Message Batches API at half price. Verify caching worked by reading usage.cache_read_input_tokens: if it is zero across identical-prefix requests, something is invalidating your prefix.
Example
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// A large, STABLE prefix (system prompt + reference doc) is cached.
// The volatile part — the user's question — comes last, so it never
// invalidates the cached prefix.
const res = await client.messages.create({
model: "claude-sonnet-5", // balanced default; not Opus 'just in case'
max_tokens: 1024,
system: [
{
type: "text",
text: LARGE_STABLE_INSTRUCTIONS, // e.g. 6k tokens, identical every request
cache_control: { type: "ephemeral" },
},
],
messages: [{ role: "user", content: userQuestion }],
});
// Confirm the cache actually hit — zero here means a silent invalidator.
console.log("cached read:", res.usage.cache_read_input_tokens);
console.log("full-price:", res.usage.input_tokens);When to use it
- Route single-label classification requests to claude-haiku-4-5 and complex multi-step reasoning to claude-sonnet-4-5 in the same app.
- Enable prompt caching on a large shared system prompt to cut repeated-request costs by up to 90%.
- Use the Anthropic Message Batches API for nightly report generation to get 50% off standard pricing.
More examples
Route by task complexity
Dispatches each request to the cheapest model that can handle the task, lowering average cost per call.
function chooseModel(taskType) {
const routing = {
classification: "claude-haiku-4-5",
summarisation: "claude-haiku-4-5",
code_generation: "claude-sonnet-4-5",
architecture_review: "claude-sonnet-4-5",
};
return routing[taskType] ?? "claude-haiku-4-5";
}
const msg = await client.messages.create({
model: chooseModel(req.body.task),
max_tokens: 512,
messages: [{ role: "user", content: req.body.prompt }],
});Enable prompt caching on system prompt
Caches the large knowledge-base system prompt so it is billed at ~10% on cache hits.
const SYSTEM = `You are the AcmeCorp support assistant.
${PRODUCT_KNOWLEDGE_BASE}`; // e.g. 6,000 tokens
const msg = await client.messages.create({
model: "claude-haiku-4-5",
max_tokens: 256,
system: [{ type: "text", text: SYSTEM, cache_control: { type: "ephemeral" } }],
messages: [{ role: "user", content: userQuestion }],
});
console.log("Cache read tokens:", msg.usage.cache_read_input_tokens);Batch non-urgent requests overnight
Submits a batch of description-generation jobs that Anthropic processes at 50% off, with results available within 24 hours.
// Use the Batches API for non-real-time work
const batch = await client.beta.messages.batches.create({
requests: products.map(p => ({
custom_id: String(p.id),
params: {
model: "claude-haiku-4-5",
max_tokens: 120,
messages: [{ role: "user", content: `Describe: ${p.name}` }],
},
})),
});
console.log("Batch submitted:", batch.id);
Discussion