Prompt Caching & Cost Control
AI bills don't creep up. They jump, usually the week you launch. The good news: the three biggest levers are boring and cheap to pull. Cache the parts of your prompt that never change, send easy work to a cheap model, and put a hard cap on spend before anyone else finds your endpoint.
1How token pricing actually works
You pay per token Tokens The units AI uses to process text. Roughly 1 token = 4 characters. You pay per token, and context windows are measured in tokens. "Like words on a meter. The more you write (or the AI writes), the more tokens tick by."
The part that surprises everyone
Models have no memory between calls. Turn 10 of a chat resends the system prompt System Prompt Instructions that set the model's role, rules, and tone for a whole conversation, separate from what the user types. In AI SDK 7 it goes in a top-level `instructions` field; CLAUDE.md plays a similar role for Claude Code. Assume users can eventually extract it, so never put secrets there. "Like the briefing an actor gets before improv: who you are, what the scene is, what's off-limits."
Relative price per token across the Claude 5 family (Haiku = 1x), as of September 2026:
Exact prices change. Check Anthropic's models page before you budget.
2Prompt caching, explained like a coffee order
You order the same complicated drink every morning. The first day you spell out all eleven modifications. After that, the barista says "the usual?" and you just add "and a muffin." Prompt caching Prompt Caching Letting the AI provider reuse the processed beginning of a prompt you send repeatedly (system prompt, docs, tool definitions) so later calls are cheaper and faster. With Claude you mark a breakpoint with `"cache_control": {"type": "ephemeral"}` (5-minute default, 1-hour option); cache reads cost a small fraction of normal input tokens. "Like a barista who remembers your usual. You only explain the new part of the order."
It's a prefix match
The request is read in order: tools, then system, then messages. Everything up to your cache breakpoint must be identical. Change one character early on and everything after it misses.
The math (Claude API)
Multipliers of the model's base input price. One cached reuse already beats the write premium.
3Turn it on (and prove it's working)
With the Anthropic SDK
Put the stable stuff first, mark the end of it with cache_control, and put the changing question after. Then log the usage fields: on the second identical-prefix request, cache_read_input_tokens should be large.
lib/docs-bot.ts
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const PRODUCT_DOCS = '...10,000 tokens of help-center articles...'; // stable, reused every request
const question = 'How do I reset my password?';
const response = await client.messages.create({
model: 'claude-sonnet-5',
max_tokens: 1024,
system: [
{ type: 'text', text: 'You answer questions about Taco Tracker using only the docs below.' },
{
type: 'text',
text: PRODUCT_DOCS,
cache_control: { type: 'ephemeral' }, // cache everything up to and including this block
},
],
messages: [{ role: 'user', content: question }], // changes every time: goes AFTER the breakpoint
});
const u = response.usage;
console.log({
uncached: u.input_tokens,
cacheWrite: u.cache_creation_input_tokens, // first request: written to cache
cacheRead: u.cache_read_input_tokens, // later requests: should be > 0
});Choosing a TTL
// Default: 5-minute cache. Good for chat bursts.
cache_control: { type: 'ephemeral' }
// 1-hour cache. Costs more to write, pays off when traffic is spread out.
cache_control: { type: 'ephemeral', ttl: '1h' }Automatic caching
// Don't want to pick breakpoints? One top-level cache_control
// caches up to the last cacheable block automatically.
const response = await client.messages.create({
model: 'claude-sonnet-5',
max_tokens: 1024,
cache_control: { type: 'ephemeral' },
system: PRODUCT_DOCS,
messages: [{ role: 'user', content: question }],
});With the AI SDK
Set the breakpoint through providerOptions.anthropic.cacheControl. In AI SDK 7, cache stats live in usage.inputTokenDetails for every provider.
lib/docs-bot-aisdk.ts
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';
const PRODUCT_DOCS = '...';
const question = 'How do I reset my password?';
const result = await generateText({
model: anthropic('claude-sonnet-5'),
instructions: {
role: 'system',
content: `Answer using only these docs:\n\n${PRODUCT_DOCS}`,
providerOptions: {
anthropic: { cacheControl: { type: 'ephemeral' } }, // breakpoint on the system prompt
},
},
prompt: question,
});
console.log('cache read tokens:', result.usage.inputTokenDetails.cacheReadTokens);
console.log('cache write tokens:', result.usage.inputTokenDetails.cacheWriteTokens);4Model routing: stop paying genius rates for easy work
Model routing Model Routing Sending each request to the model that fits it best: a small fast model for simple classification, a frontier model for hard reasoning, a fallback when a provider is down. Done well it cuts cost and latency without hurting quality. Claude Code's `opusplan` alias is a simple example: Opus while planning, Sonnet while executing. "Like a hospital triage desk. Sprained ankles go to urgent care, chest pains go straight to the specialist."
lib/run-task.ts
import { generateText } from 'ai';
type Task = 'classify' | 'summarize' | 'plan-refactor';
// Cheap, fast model for easy jobs; big model only where it pays off.
const MODEL_FOR: Record<Task, string> = {
classify: 'anthropic/claude-haiku-4.5',
summarize: 'anthropic/claude-sonnet-5',
'plan-refactor': 'anthropic/claude-opus-5.5',
};
export async function runTask(task: Task, input: string, userId: string) {
const { text, usage } = await generateText({
model: MODEL_FOR[task],
prompt: input,
maxOutputTokens: task === 'classify' ? 50 : 2000, // cap output: output tokens cost the most
providerOptions: { gateway: { user: userId, tags: [task] } }, // spend per user and per feature
});
console.log(task, usage.inputTokens, usage.outputTokens);
return text;
}- Cap output.
maxOutputTokenson short tasks, and ask for terse answers. Output is the pricey side. - Measure before downgrading. Run your eval set on the cheaper model first. See evals and guardrails.
- Judge cost per finished task, not per request. A cheap model that needs three retries isn't cheap.
- Trim context. Don't paste the whole database into the prompt; retrieve the few rows that matter.
5Budgets, usage tracking, and batch jobs
Set a hard ceiling first. If you use the AI Gateway AI Gateway A single endpoint that sits between your app and many AI providers, handling keys, routing, fallbacks, budgets, and usage tracking. With Vercel AI Gateway you pass a plain `"provider/model"` string like `'anthropic/claude-sonnet-5'` to the AI SDK and authenticate with `AI_GATEWAY_API_KEY` (or OIDC on Vercel). "Like a travel agent who books any airline for you. One contact, one bill, and they rebook you if a flight is cancelled."user and tags options in the routing example above make the gateway's spend reports show cost per customer and per feature. Going direct? Set spend limits and alerts in your provider's console.
Terminal
# Cap AI Gateway spend for one project (pick your own number)
vercel ai-gateway budgets set project my-project --limit 200
# See what's configured
vercel ai-gateway budgets lsAdd per-user limits in your app. A budget protects your wallet; a rate limit Rate Limit A cap on how many requests someone can make in a time window, like 10 login attempts per minute. You add rate limits to protect your API and your AI bill from abuse, and AI providers apply their own limits to you. A shared store such as Redis keeps counts consistent across serverless instances. "Like a bartender cutting someone off. Everyone still gets served, just not 50 drinks in a minute."
Batch processing: half price if you can wait
Anthropic's Message Batches API is 50% off. You submit many requests at once and collect results later (results can come back in any order, so key them by custom_id). Perfect for nightly tagging, backfills, and eval runs. Useless for chat.
scripts/tag-reviews.ts
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const reviews = [{ id: 'r1', text: 'Loved it' }, { id: 'r2', text: 'Cold tacos' }];
// Submit: each request gets your own custom_id so you can match results later.
const batch = await client.messages.batches.create({
requests: reviews.map((r) => ({
custom_id: r.id,
params: {
model: 'claude-haiku-4-5',
max_tokens: 100,
messages: [{ role: 'user' as const, content: `One-word sentiment for: ${r.text}` }],
},
})),
});
// Later (a cron job or workflow step): check status, then read results.
const status = await client.messages.batches.retrieve(batch.id);
if (status.processing_status === 'ended') {
for await (const item of await client.messages.batches.results(batch.id)) {
if (item.result.type === 'succeeded') {
const first = item.result.message.content[0];
console.log(item.custom_id, first?.type === 'text' ? first.text : '');
}
}
}/usage (or its alias /cost) to see token usage for your session. That's your dev spend; your app's spend lives in the gateway or provider dashboard.6Common traps
Dynamic text at the top
“Current time: 10:42:07” in the system prompt changes every request and kills the cache. Put it in the latest user message.
Caching tiny prompts
Below the minimum size nothing is cached, and nothing errors. Check cache_read_input_tokens, don't assume.
1-hour TTL everywhere
Writes cost 2x. If requests arrive seconds apart, the 5-minute cache is cheaper.
Unbounded agent loops
No step cap + a confused model = a very long, very expensive conversation with itself. Always set stopWhen.
One global model
Running everything on the biggest model because it was the default in a tutorial. Route by task.
No budget until the bill
Set a spend cap and alerts the day you get an API key, not the day after launch.
7When caching pays off (and when it doesn't)
Great fit
- Long system prompts or docs reused on every request
- Multi-turn chat (cache the growing history)
- Agents with many tools and many steps
- "Chat with this PDF" where many questions hit one document
Little or no benefit
- Short one-off prompts under the minimum size
- Every request has a different prefix
- Traffic so sparse the cache always expires first
- Output-heavy tasks (caching only discounts input)
Reference: Claude prompt caching docs · AI Gateway docs
Cheap is good. Safe is better.
The most expensive AI incident isn't a big bill. It's an agent that got talked into leaking data. Close that gap next.