I sat down in early September to update a pricing table and stopped almost immediately. None of the per-token prices had moved.
What had moved was the multiplier. My cost helper carried a line reading cacheRead = input * 0.1, and I had quietly filed that 0.1 under "constants that never change." I had a habit of checking prices. I had no habit of checking multipliers.
Across the Lab sites I run a number of jobs that re-read a long operating guideline on every turn. Those are exactly the workloads this change should reward — and there had to be shapes it barely touches. So before pasting anything in, I worked out the numbers for myself.
The price did not change. The read multiplier did.
Prompt caching is billed as a multiplier on the base input price, and that is where the change landed.
| Cache operation | Multiplier | Duration |
|---|---|---|
| 5-minute cache write | 1.25x base input | 5 minutes |
| 1-hour cache write | 2x base input | 1 hour |
| Cache read (hit) | 0.1x base input Fable 5.1 and Mythos 5.1 only: 0.025x | Same as the preceding write |
So a cache read on Fable 5.1 went from $1.00 to $0.25 per million tokens. Base input at $10, output at $50, and the two write rates at $12.50 and $20 all stayed exactly where they were. The exception currently covers those two models only — Opus 5, Sonnet 5, and Haiku 4.5 are still at 0.1x. The figures come from the Claude Platform pricing page.
If you compress this into "Fable 5.1 is 75% cheaper," you will hand someone a broken estimate. What got cheaper is one line of the invoice, the cache read line.
Estimate your own saving before you migrate
You only need four numbers, and they are already sitting in the usage block of your responses. Pull them from real traffic rather than guessing.
# usage from a single request
usage = {
"input_tokens": 2000, # fresh input that missed the cache
"cache_read_input_tokens": 60000, # read back from cache
"cache_creation_input_tokens": 0, # written this turn
"output_tokens": 1500,
}
def request_cost(usage, base_in, base_out, read_mult, write_mult=1.25):
"""Cost of one request in USD. Prices are per million tokens."""
return (
usage["input_tokens"] * base_in
+ usage["cache_read_input_tokens"] * base_in * read_mult
+ usage["cache_creation_input_tokens"] * base_in * write_mult
+ usage["output_tokens"] * base_out
) / 1_000_000
before = request_cost(usage, 10.0, 50.0, read_mult=0.1)
after = request_cost(usage, 10.0, 50.0, read_mult=0.025)
print(f"{before:.6f} -> {after:.6f} ({(before - after) / before:.1%} lower)")
# 0.155000 -> 0.110000 (29.0% lower)The part that matters is that the multiplier is a parameter. Bake it in as a literal and your estimate drifts the moment you switch models — silently, which is the worse kind of wrong.
Three shapes, and a tenfold difference
I ran three requests of genuinely different character through the same function, holding the Fable-tier prices of $10 and $50 constant.
| Workload shape | cache read / new input / output | 0.1x | 0.025x | Reduction |
|---|---|---|---|---|
| Agent re-reading a long preamble each turn | 60,000 / 2,000 / 1,500 | $0.1550 | $0.1100 | 29.0% |
| Classification and summarisation batch | 20,000 / 500 / 300 | $0.0400 | $0.0250 | 37.5% |
| Short preamble, long generated output | 8,000 / 1,000 / 4,000 | $0.2180 | $0.2120 | 2.8% |
The top row and the bottom row differ by roughly a factor of ten. Both received the identical 75% discount.
What separates them is the share of the bill that cache reads occupy in the first place: 38.7% for the agent, 50.0% for the batch, and 3.7% for the generation-heavy call. When output tokens carry the invoice, trimming the input side barely registers.
The announcement sets the discount rate. Your token mix sets the invoice. My first estimate took the headline figure as my own expected saving, and on the generation-heavy jobs it was nowhere close.
Move the multiplier out of your code and into the table
The fix is small. Keep the multiplier alongside the price, per model, rather than in the arithmetic.
PRICING = {
# base_in, base_out, cache_read_mult (USD per million tokens)
"claude-fable-5-1": (10.0, 50.0, 0.025),
"claude-fable-5": (10.0, 50.0, 0.1),
"claude-opus-5": (5.0, 25.0, 0.1),
"claude-sonnet-5": (2.0, 10.0, 0.1),
"claude-haiku-4-5-20251001": (1.0, 5.0, 0.1),
}
def cost_for(model, usage):
if model not in PRICING:
# never let an unpriced model slide through as zero
raise ValueError(f"no pricing registered for model: {model}")
base_in, base_out, read_mult = PRICING[model]
return request_cost(usage, base_in, base_out, read_mult)To find the risky spots in your own repository, one line is enough.
grep -rn "0\.1\s*\*\|\* 0\.1\b" --include='*.py' --include='*.ts' src/ | grep -i cacheBecause I believed prices move and multipliers do not, I had externalised the price table and left the multiplier in the code. These days I keep the price and the multiplier in the same record, so that updating one without the other is not possible.
This does not change how you choose between 5m and 1h
I expected it to. A read that costs a quarter of what it did ought to shift the TTL decision too.
When your calls sit more than five minutes apart but inside an hour, a 5-minute cache is rewritten every time. Solving for the number of calls per hour, K, at which the two options break even gives this:
| Read multiplier | Break-even K (calls per hour) | Conclusion |
|---|---|---|
| 0.1x | 1.65 | Two or more calls favour the 1-hour TTL |
| 0.025x | 1.61 | Two or more calls favour the 1-hour TTL |
The boundary hardly moves, because the write multipliers of 1.25x and 2x dominate the comparison. The same holds for "is caching worth it at all" — a single read repays the write under either multiplier.
A cheaper read thins the bill of a setup where caching already works. It is not a reason to redesign your caching. If your hit rate is weak, switching models will not rescue it; I wrote up how to measure that in halving a monthly bill with prompt caching.
One next step
Take a single day of responses, sum cache_read_input_tokens, and work out what fraction of your total cost that line represents. Multiply that fraction by 0.75 and you have the ceiling on what a migration can give you.
I nearly skipped that step and spent an afternoon puzzled that the invoice had barely moved. Thank you for reading — I hope it saves you a round of rework.