Chapter 3 of 7 · 18 min read
Token categories and their rates
On this page
The usage object a provider returns is not a single number. A request to Claude Opus 5 comes back with four counters, and the reason they are reported separately is that the provider bills them separately:
{
"input_tokens": 1842,
"cache_read_input_tokens": 12000,
"cache_creation_input_tokens": 0,
"output_tokens": 232
}Each of those numbers counts tokens — the fragments of text a model reads and writes, a few characters apiece, and the unit every provider meters on. What the four counters have in common is that unit. What they do not have in common is the price, and the reason a single request needs four numbers rather than one is that a provider is not selling text by the yard. It is selling four different pieces of work that happen to be measured with the same ruler.
Two of those refer to prompt caching, where the provider keeps the processed form of a prompt's leading section in its own store so the next request beginning with the same bytes is served from it instead of being processed again: a cache read charges for reusing that stored prefix, and a cache write charges for putting it there. This chapter is about why a cost row has to carry all four counters in their own columns rather than a total — and the argument is not tidiness, it is that two of the most expensive regressions a model-backed feature suffers are invisible in any figure derived from a sum.
What one request costs, category by category
At the published Opus 5 rates — $5.00 per million input tokens, $25.00 per million output, cache reads at a tenth of the input rate and cache writes at 1.25× it for the five-minute TTL, the time to live after which the stored prefix expires and has to be written again — the request above costs 2.10 cents:
RATES = { # USD per million tokens, claude-opus-5
"input": 5.00,
"output": 25.00,
"cache_read": 0.50, # 0.1 x input
"cache_write_5m": 6.25, # 1.25 x input
}
def cost_usd(u) -> float:
return (
u.input_tokens / 1e6 * RATES["input"]
+ u.output_tokens / 1e6 * RATES["output"]
+ u.cache_read_input_tokens / 1e6 * RATES["cache_read"]
+ u.cache_creation_input_tokens / 1e6 * RATES["cache_write_5m"]
)
# 0.00921 + 0.00580 + 0.00600 + 0.0 = $0.02101The four terms in that sum are worth reading rather than trusting, because their relative sizes drive everything below. Each term is a token count divided by a million and multiplied by a price quoted per million, and the division is there for no reason except that rates are published per million tokens while counts arrive per request. Of the three non-zero terms the uncached input leg is the largest, the cache read leg comes next, and the output leg is the smallest of the three even though output carries the highest rate on the card. Twelve thousand tokens read from cache cost what twelve hundred uncached ones would, since the cache-read rate is a tenth of the input rate, and that ratio is the whole reason caching is worth configuring.
A cost table that stores a single total_tokens of 14,074 and multiplies by a
blended rate derived from last month's mix — $0.0210 ÷ 14,074 tokens, or $1.49
per million — reproduces that 2.10 cents exactly. It also keeps reproducing
something very close to 2.10 cents through changes that alter the bill by
factors rather than percentages.
That it reproduces the figure exactly is not evidence that the method works. The blended rate was obtained by dividing this request's cost by this request's token count, and multiplying it back by that same count is the inverse operation on the same pair of numbers. Division and multiplication by the same quantity cancel, so the answer was settled before any modelling took place. What the method assumes — and naming the assumption makes everything below predictable rather than surprising — is that reported cost is a fixed multiple of the token count. Under that assumption reported cost moves if and only if the count moves, and anything that changes which category a token landed in is invisible by construction rather than by oversight.
The mix moves and the total does not
Take the smaller of the two regressions a blended rate hides. Someone adds "be thorough and explain your reasoning" to a draft-reply feature's instructions. It is nine words of input, so the input count goes from 1,842 to 1,851 and nobody notices. The output count goes from 232 to 410, because that is what the instruction was for.
| Before | After | Change | |
|---|---|---|---|
| Input tokens (uncached) | 1,842 | 1,851 | +0.5% |
| Cached input tokens read | 12,000 | 12,000 | 0% |
| Output tokens | 232 | 410 | +77% |
| Total tokens | 14,074 | 14,261 | +1.3% |
| Reported cost at blended $1.49/MTok | 2.10¢ | 2.13¢ | +1.3% |
| Actual cost from the four counters | 2.10¢ | 2.55¢ | +21.4% |
Two of the numbers in that table are worth deriving rather than reading. The reported figure moves by 1.3% because it is the token total times a constant and the token total moved by 1.3%; that is the entire computation, and it would have returned the same answer whatever the nine added words had been. The real figure moves by 21.4% for a different reason with two parts to it. The output leg was a little over a quarter of the bill before the change, and raising a leg of that size by 77% raises the whole by a little over a fifth.
The multiplication in that last step is the part to be careful with. A percentage change in one leg moves the total in proportion to that leg's share of the total, so the same 77% landing on a leg carrying a tenth of the bill would have moved the whole by less than a tenth. The size of a change and the share it lands on are separate quantities, and only their product is a cost.
The blended figure is not slightly wrong, it is wrong by a factor of sixteen on the thing that matters, and it cannot be corrected by recalibrating the blend. Recalibrating from this month's mix would make the level right again and leave the change just as invisible, because a blended rate is by construction an average that absorbs mix shifts. Behind the gap is that output tokens cost five times what input tokens cost on Opus 5 and fifty times what a cached input token costs, so a token moving from the input column to the output column is a 5× repricing of that token, and a token that stops being served from cache is a 10× repricing of that one. A counter that adds all four columns together and multiplies once has thrown away the only information distinguishing those events from ordinary traffic noise.
The second regression is worse, and it moves the total in the wrong direction entirely. Prompt caching is a prefix match, so a byte that changes on every request invalidates the cached prefix on every request, and the 12,000 tokens that were being read at $0.50 per million are written at $6.25 per million instead:
{
"input_tokens": 1842,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 12000,
"output_tokens": 232
}Total tokens: 14,074, exactly what it was before. Cost: 9.00 cents, up from
2.10 — a 4.3× increase, entirely invisible to any metric derived from a token
total, and detectable in the split counters by a single condition a junior
engineer can write: cache_read_input_tokens = 0 on a feature whose baseline
is 12,000.
That 4.3× is worth arriving at rather than accepting. Nothing about the request changed except which column those 12,000 tokens landed in, so two of the three legs are untouched — the uncached remainder of the prompt is the same prompt, and the answer is the same answer. The third leg is multiplied by the ratio of the two rates, $6.25 against $0.50, which is twelve and a half. That leg was a little over a quarter of the old bill. Multiplying a little over a quarter of a bill by twelve and a half while leaving the rest of it alone takes the bill to a bit over four times its old size. And because the same tokens are still being sent, the count they are counted in does not move at all, so a threshold on any metric derived from that count is not slow to fire. There is nothing for it to fire on.
The four counters and the rates that apply to them
The categories are not a modelling convenience. They are the categories the provider bills on, and their relative prices differ enough that treating them as interchangeable is like treating a wire transfer and a currency conversion as the same line item.
Underneath the rate card is a difference in how much machine time each category takes, and it is worth knowing, because it is what makes the ratios stable enough to design around. Every token of a prompt is already known at the moment the request arrives, so the model can process all of them in one pass, and the hardware spends that pass doing a small number of very large matrix multiplications, which is the operation it exists to do. An output token cannot be produced that way. Each one needs its own pass, and that pass cannot begin until the previous token exists, because the token just produced is part of the input to the step that produces the next one.
So the same quantity of text is a different quantity of work depending on which side of the request it sits on. A thousand tokens of prompt is one pass over a thousand positions. A thousand tokens of answer is a thousand passes run one after another, each occupying the hardware for its own turn. What the provider is renting out is that occupancy, and the rate card is where the difference gets written down. Output priced at five times input is a commercial decision rather than a law of nature, and it will not be exactly five on every card — but it is a decision tracking a real asymmetry, which is why it points the same way across every row of the table below.
The two cache categories are the same accounting applied to a pass that has already happened. When a prefix is cached the provider has kept the processed intermediate state that pass produced, so serving the next request beginning with the same bytes means loading that state instead of recomputing it. What remains to charge for is holding the state and fetching it, which is why a cache read is priced at a fraction of the input rate rather than as a discount off it. A cache write runs the other way: the pass still has to happen and the result additionally has to be stored, so it costs more than plain input rather than less. The two TTLs then differ only in how long the store stays occupied, and the longer one carries the larger premium for that reason and no other.
A token is therefore a unit of text and not a unit of work, and that is the sentence a token total quietly denies. The four counters measure four different kinds of work which happen to be denominated in the same unit. Adding them gives a correct answer to the question of how much text moved, and there is no price per unit of text by which that answer could be multiplied.
| Category | Rate, relative to input | Opus 5 | Sonnet 5 | Haiku 4.5 |
|---|---|---|---|---|
| Uncached input | 1× | $5.00 | $3.00 | $1.00 |
| Cache read | 0.1× | $0.50 | $0.30 | $0.10 |
| Cache write, 5-minute TTL | 1.25× | $6.25 | $3.75 | $1.25 |
| Cache write, 1-hour TTL | 2× | $10.00 | $6.00 | $2.00 |
| Output (including thinking) | 5× | $25.00 | $15.00 | $5.00 |
All figures are USD per million tokens at published list rates. The spread from cheapest to dearest within one model is fifty to one, which is why "we're on Haiku, cost isn't a concern" stops being true the moment a caching change lands: Haiku 4.5 uncached input at $1.00 per million is ten times Haiku cache-read, and a feature that loses cache reuse gets ten times more expensive on its input leg whichever model it runs on.
The two cache-write rows deserve their own column in a fact table rather than being folded together, because the TTL is a decision someone makes and then forgets. Take the 12,000-token prefix above on Opus 5. Serving it uncached costs $0.060 every time. Writing it at the five-minute TTL costs $0.075 once and $0.006 per subsequent read, so two requests cost $0.081 against $0.120 uncached and the write has paid for itself. Writing it at the one-hour TTL costs $0.120 once, so two requests cost $0.126 — more than not caching at all — and it takes a third read to come out ahead.
| Reads within the TTL | Uncached | 5-minute TTL | 1-hour TTL |
|---|---|---|---|
| 1 | $0.060 | $0.075 | $0.120 |
| 2 | $0.120 | $0.081 | $0.126 |
| 3 | $0.180 | $0.087 | $0.132 |
| 10 | $0.600 | $0.129 | $0.174 |
Each cached column is one write plus one cache read for every request after the first, so each of its rows differs from the row above by a single cache read of the same 12,000 tokens, while the uncached column differs by a whole uncached read every time. Stated that way the break-even points stop being table lookups. A read after the first costs a tenth of what it would uncached, since the cache-read rate is a tenth of the input rate, so each reuse hands back nine tenths of an uncached read. What has to be recovered is the premium paid to create the entry, which is a quarter of one uncached read at the five-minute TTL and a full uncached read at the one-hour TTL. A quarter is recovered on the first reuse with a great deal to spare, which is why two requests already come out ahead; a whole read is not recovered by one reuse of nine tenths and is recovered by two, which is why the one-hour option needs a third request before it stops being a loss.
Both TTLs are enormous wins at ten reads and the one-hour option is a loss at
one, which is exactly the situation a team creates when they flip everything to
ttl: "1h" during a latency push and then traffic thins out. The doubled write
premium is then paid on prefixes read once, and the total token count looks
fine throughout. Separate columns for read tokens, write tokens and the TTL
make that a query someone can run; a single "cache tokens" column makes it an
argument someone can only win with an anecdote.
Reasoning tokens are the awkward one
Thinking tokens, also called reasoning tokens, are the ones a model produces
while working a problem through before writing the answer the caller sees. They
are billed at the output rate and are already counted inside output_tokens.
There is no fifth counter on Anthropic's usage object separating them out,
which has two consequences worth stating plainly rather than discovering later.
The first is that a schema with a reasoning_tokens column will have that
column sitting at zero or null for this provider, and it should be nullable
rather than defaulting to zero — a zero asserts that no thinking happened,
which is false and will be believed by whoever writes the next query. Other
providers do emit a separate reasoning count, and a multi-provider accounting
layer needs the column; what it must not do is assume the four category counts
sum to a separately reported total, because on some providers they do and on
others the reasoning count is a subset of output rather than a sibling of it.
The second is the mistake that costs real money: thinking.display looks like
a cost control and is not one. Setting display: "omitted" — the default on
Opus 5, Opus 4.8, Sonnet 5 and Fable 5 — means thinking blocks arrive with
empty text. The thinking still happened and is billed identically. The lever
that changes thinking spend is effort, a different parameter in a different
place, and a team that switched display to save money has bought nothing
while making their own traces unreadable. Because the spend shows up as
ordinary output growth, the only way to see it in the accounting is to record
the request's effort level on the cost row alongside the counters, so a spend
shift can be joined against a configuration change.
The belief behind that mistake is a reasonable one held one layer too high. In
most metered APIs you are charged for what is delivered to you, so suppressing a
field is a way of not paying for it. Here the billing attaches to production
rather than to delivery. The thinking tokens are generated by the same loop that
generates the answer, they occupy the hardware for the same duration whether or
not anybody reads them, and display governs only whether their text is copied
into the response you receive. The provider's cost was incurred before the
display setting was consulted at all, which is why the only parameter that can
move the bill is one that changes how much thinking gets produced rather than
how much of it gets shown.
ALTER TABLE llm_calls
ADD COLUMN cache_read_tokens integer NOT NULL DEFAULT 0,
ADD COLUMN cache_write_tokens integer NOT NULL DEFAULT 0,
ADD COLUMN cache_write_ttl text, -- '5m' | '1h' | null
ADD COLUMN reasoning_tokens integer, -- nullable: unreported here
ADD COLUMN effort text, -- low | medium | high | xhigh | max
ADD COLUMN thinking_mode text; -- adaptive | disabledKeeping the counts, not only the money
Storing the four counters alongside the computed cost looks like duplication and is the only thing that makes counterfactual questions answerable. "What would last month have cost on Sonnet 5 instead of Opus 5" is a straightforward calculation from counts and a different rate card, and an impossible one from a money column, because a mix cannot be recovered from a total. The same applies to every question of that family: what a caching change would have saved, what a longer TTL would have cost, what a 20% output reduction is worth in dollars.
The reason a total cannot be un-multiplied is worth stating as a property rather than as advice. Four counts and four rates produce one dollar figure, and the map from mixes to figures is many-to-one: a great many different combinations of the four counters land on the same cost, and once both have been reduced to a number, a request that spent everything on output is indistinguishable from one that spent everything on cache writes. Inverting a many-to-one map is not a matter of finding a cleverer query. The information is not present to be found. The counts are what the money was computed from, and keeping them is the only way to keep it.
-- Same traffic, repriced on Sonnet 5 (input $3.00 / output $15.00 per MTok)
SELECT sum(cost_usd) AS actual_opus5,
sum(input_tokens / 1e6 * 3.00
+ output_tokens / 1e6 * 15.00
+ cache_read_tokens / 1e6 * 0.30
+ cache_write_tokens / 1e6 * 3.75) AS hypothetical_sonnet5
FROM llm_cost_facts
WHERE feature = 'draft-reply'
AND hour_bucket >= '2026-03-01' AND hour_bucket < '2026-04-01';The result is a bound rather than a forecast — a different model produces different output lengths for the same prompts, and a smaller model often produces more output rather than less — so read it as the arithmetic component of a migration decision that still needs an evaluation to complete it. Even as a bound it is the number that gets a migration proposal taken seriously, and it exists only because nobody threw the counts away after multiplying them.
Decomposition is not diagnosis
Splitting the categories says which leg moved. It does not say whether a prompt changed, a model changed, users started asking harder questions, or a retry loop started re-sending longer contexts. The split narrows a month-end surprise to a category and a day, and someone still has to open the deploy log.
It also does not cover every line on the bill. Tokens are the largest cost on most systems and they are not the only one. Server-side web search is metered per search — $10 per thousand — and Managed Agents sessions additionally accrue running time at $0.08 per hour, neither of which appears in any token counter. A schema that only knows how to multiply tokens by rates will reconcile against the provider's total with a residual it cannot explain, and the residual grows the day someone enables web search. Model non-token line items as their own row types with their own units rather than converting them into notional tokens.
Converting them is tempting because it makes every row the same shape and every dashboard a single sum. What it destroys is the ability to answer the question that will actually be asked. A search is metered per search and a session per hour, so the levers that move those lines are the number of searches and the wall-clock duration, neither of which is a length of text. A search rewritten as a notional token count still costs what it cost, and now nothing in the table can say whether the line moved because searches got more numerous or because a rate changed. Units that were never comparable do not become comparable by being renamed.
Four columns instead of one is therefore the whole recommendation, and the reason it is worth insisting on in review is that the alternative fails silently in both directions: a mix shift into output raises the bill while the total holds still, and a cache collapse raises it while the total holds exactly still. Neither has a symptom anywhere in a system that stores one number per call.
All of this assumes the row exists. Every figure in this chapter came from a usage object that was returned, read and written down, and there are several ordinary ways for a call to be billed and leave no usage object at all. They are the reason a per-request cost figure can sit flat for a month while the invoice for the same model climbs by a third.
Cost tracking per request, user, feature and tenant — read the current version at https://daviddominguez.dev/learn/en/cost-tracking-per-request-user-feature-and-tenant/token-categories-and-their-rates