← All posts
Mechanics5 min read

1-hour cache writes cost twice what 5-minute writes do — and we priced them wrong

Anthropic bills 1-hour cache writes at 2x base input against 1.25x for the 5-minute tier. Confusing the two understates cost by 37.5% on that line — which we did.

Anthropic's prompt cache has two TTL tiers, and they are priced differently enough that confusing them is a real accounting error:

5-minute write   1.25x base input     (the default)
1-hour write     2x    base input     (cache_control: {type: "ephemeral", ttl: "1h"})
read             0.1x  base input     (either tier)

Price a 1-hour write at the 5-minute multiplier and you record 62.5% of the true cost — a 37.5% understatement on that line. We know the exact number because we made the exact mistake: our cost function had a parameter for 1-hour tokens, no live call site ever passed it, and every 1h write in the system was silently billed at 1.25x. Worse, the proxy requested 1-hour caching by default at the time, so the mispriced tier was the one carrying most of the write volume. And it fed back into our own plan enforcement: spend caps sum recorded cost, so under-recording made limits fire later than they should have.

When the 2x tier is worth it

Before the fix, the arithmetic — because the tier choice is a real decision, not a default to inherit. Whether 1h beats 5m depends on your call cadence.

An agent that calls more often than every five minutes keeps the 5m entry alive through sheer traffic (reads refresh the clock), so the cheap tier behaves like a long one. Paying 2x buys nothing.

An agent that calls every 10–50 minutes is the 1h tier's home ground. On the 5m tier every call finds a dead entry: all writes at 1.25x, no reads, which is roughly 25% more expensive than not caching at all. On the 1h tier the first call writes at 2x and the next several read at 0.1x. With a prefix of P tokens and N calls per hour, the hourly comparison is N × 1.25P against 2P + (N−1) × 0.1P — the 1h tier wins from the second call per hour onward. At N=4 that is 5.0P versus 2.3P: the "expensive" tier costs less than half as much.

An agent that calls less than hourly should not cache at all: every write is at 2x with nothing ever reading it back, the same dead-cache failure at double the premium. The general version of that trap — writes that never become reads — is measured in the write-cost post.

The metering half: you cannot price what you cannot attribute

Fixing the multiplier sounds like "read one more field", and it is not, which is the part worth passing on. Anthropic's response reports cache_creation_input_tokens as a total, and sometimes a nested usage.cache_creation object splitting it into ephemeral_5m_input_tokens and ephemeral_1h_input_tokens. The documented examples show the bare total with no breakdown when only 5-minute caching is in play. So "breakdown missing" is ambiguous, and the two obvious defaults are both wrong in opposite directions:

  • Assume 5m and you reproduce the original bug the first time the field is withheld on a 1h write — hidden behind a plausible number, again.
  • Refuse to price anything without a breakdown and you null the cost of essentially every cached call, taking the whole dashboard down to protect against a rare case.

The resolution is a fact instead of a default: a 1-hour write cannot occur unless something on the request asked for one. A ttl: "1h" breakpoint either exists in the request — ours, injected, or the caller's own, anywhere in the body, including nested inside a tool result — or it does not. If it does not, attributing the whole total to the 5m tier is a derivation from the API contract, not an assumption. Only when a 1h write was genuinely possible and the breakdown is absent do we refuse to price, and that refusal surfaces as a dash rather than a guess — the same insufficient-data rule every other unpriceable call follows. The refusals stay rare and meaningful: a spike in them means the response shape moved on a path where money is at stake, which is exactly when we want the alarm.

One structural detail from the fix that applies to any billing code: the two tiers are now carried as two named values, never a total and a part. A function signature that takes (totalWrites, oneHourWrites) invites the caller to pass the response's total — which is the sum of both tiers — alongside the 1h count, double-charging every 1h token. A signature that takes (fiveMinute, oneHour) makes that mistake unwriteable. Shapes that cannot express the bug beat comments warning about it.

The blast radius was wider than our own feature

The natural assumption was that only injected traffic was affected — we set the TTL, we mispriced it. False, and worth checking in your own system: a caller managing its own cache_control: {ttl: "1h"} breakpoints is forwarded untouched, on agents whose optimisation is off, and was mispriced identically. The defect lived in the metering path, not the injection path, so the fix lives there too — resolved per call from the request body itself, not from our injection metadata. If you meter AI spend and support caller-managed caching, your exposure is every caller who discovered the 1h tier on their own. This mispricing was found in the same audit as eight other definition defects, and what Vigil now meters per call is on the FAQ.

What to do

Check whether anything in your stack requests 1-hour caching — grep your request builders for ttl and check what your framework defaults to — and then check what multiplier your cost tracking applies to cache writes. If it applies one multiplier to all writes, you have this bug in one direction or the other. Then run the cadence arithmetic on your own agents: calls per hour against prefix size decides the tier, and the answer changes at roughly one call per five minutes and again at one call per hour.