Back to Knowledge
New

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."

(roughly three-quarters of a word), priced per million, and input and output are priced separately. For every current Claude model, an output token costs 5x an input token. Thinking counts as output too. So a model that rambles is expensive in a way a long prompt usually isn't.

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."

, all tool definitions, and turns 1 through 9. An agent loop does the same on every step. Input tokens grow with every turn, which is exactly what caching fixes.

Relative price per token across the Claude 5 family (Haiku = 1x), as of September 2026:

Haiku 4.5
1xClassify, tag, route, extract short fields
Sonnet 5
2xMost chat, summaries, everyday coding help
Opus 5.5
4xHard reasoning, agents, big refactors
Fable 5.1
10xThe gnarliest long-horizon work

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."

is "the usual." The provider saves the processed start of your prompt, and later requests that begin with exactly the same bytes reuse it at a fraction of the price.

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.

tools (stable, sorted)
system prompt + docs (stable)
cache_control breakpoint
chat history + new question (changes)

The math (Claude API)

Normal input token1x
Cache write (5-minute TTL)1.25x
Cache write (1-hour TTL)2x
Cache read (Sonnet 5, Haiku 4.5)0.1x
Cache read (Opus 5.5)0.05x
Cache read (Fable 5.1)0.025x

Multipliers of the model's base input price. One cached reuse already beats the write premium.

Two lifetimes. 5 minutes by default, or 1 hour. Each cache hit resets the clock.
Up to 4 breakpoints per request, e.g. after tools, after system, after older history.
Minimum size. 512 tokens on Opus 5.5 and Fable 5.1; 1,024 on Sonnet 5 and Haiku 4.5. Shorter prefixes silently don't cache.

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);
Cache reads stuck at zero? Something in your prefix changes every request. The usual suspects: a timestamp or request ID in the system prompt ("Today is ..."), tools built from an unsorted object, a different tool list per user, or switching models (caches are per model). Move the volatile bit after the breakpoint.

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."

just means picking the model per task instead of per app. "Is this email spam?" doesn't need the same model as "plan this database migration." A lookup table is a perfectly good router to start with.

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. maxOutputTokens on 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."

, budgets can be set per team, project, or API key. The 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 ls

Add 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."

per signed-in user protects everyone else from one heavy (or malicious) user. OWASP lists this as LLM10, "Unbounded Consumption."

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 : '');
    }
  }
}
Building with Claude Code? Run /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.