Accounting for reasoning tokens: the line item with no artifact.
Reasoning tokens are the only thing on your invoice with no matching object in your logs, and the three major providers report them in three incompatible shapes — so the single piece of arithmetic every cross-vendor cost dashboard copies is wrong in opposite directions depending on which vendor it is pointed at. Add OpenAI's reasoning count to its output count and you overstate that call; fail to add Gemini's and you understate it. The fix is not a better formula. It is one normalisation step at the edge, per model and per surface, reconciled against the invoice rather than against the transcript.
Three vendors, three totals, one spreadsheet.
Every provider bills reasoning at the output rate. That part is boringly consistent and it is the part everyone knows. What differs — and what quietly breaks the dashboard — is whether the reasoning count is inside the output count or beside it.
- OpenAI. Reasoning tokens are reported under
output_tokens_details.reasoning_tokensand are already included inoutput_tokens. The detail field is a breakdown, not an addend. Summing the two double-counts the reasoning. - Anthropic. Thinking tokens are counted in
usage.output_tokens, which is the billing figure. There is no separate addend to forget, which makes this the shape least likely to be got wrong and the shape most likely to make you think the problem does not exist. - Gemini.
usageMetadata.thoughtsTokenCountsits alongsidecandidatesTokenCount, not inside it. Google's own usage documentation defines the total as prompt plus candidates plus thoughts when present, which is as clear a statement as you will get that the thoughts are an addend here.
The error is not a rounding problem. Reasoning routinely dominates the output of a reasoning call, so a mis-signed addend moves the number by multiples rather than percents.
# One call, 240 visible output tokens, 1,760 reasoning tokens. # Same underlying work. Three correct totals, three wrong ones. OpenAI output_tokens = 2000 reasoning_tokens = 1760 # nested correct: 2000 naive sum: 3760 → +88% overstated Anthropic usage.output_tokens = 2000 # nested, no detail field correct: 2000 reading the visible text: 240 → −88% understated Gemini candidatesTokenCount = 240 thoughtsTokenCount = 1760 # sibling correct: 2000 candidates only: 240 → −88% understated
The dangerous version of this bug is the one that cancels out. A fleet split across two providers, overstating one by 88% and understating the other by 88%, can produce a plausible aggregate while every per-model unit economic is wrong — and per-model is exactly the granularity at which routing decisions get made. The aggregate reconciling against the invoice is not evidence the breakdown is right; see cost attribution.
The field can be missing, and missing is not zero.
A normalisation layer written once against one model on one surface will be wrong for a reason nobody budgets for: the reasoning field is not reliably present. Developers have reported Gemini batch-prediction results that carry a thoughtSignature on the content — proof that thinking happened — while thoughtsTokenCount is absent from usageMetadata entirely. There are also reported cases of a model's returned output count diverging sharply from what billing charged, and reports that the same field is nested on one of Google's two surfaces and a sibling on the other. Treat all of those as what they are: a strong prior that the shape is a property of the model-and-surface pair, not of the vendor.
So the defensive rule is unglamorous and it is the whole of this page's operational content. Default a missing reasoning field to null, never to 0, and keep the two distinguishable all the way into your warehouse. A null says "this call's reasoning spend is unmeasured"; a zero says "this call did no thinking". One of those is a data-quality alert and the other is a finding about the model, and collapsing them is how a silent 8× undercount survives a quarter.
# Normalise once, at the edge, keyed on (vendor, model, surface). def normalise_usage(raw, vendor, model, surface): shape = REASONING_SHAPE[(vendor, model, surface)] # "nested" | "sibling" reasoning = extract_reasoning(raw) # None if the field is absent visible = extract_output(raw) if reasoning is None: # Unmeasured, NOT zero. Alert; do not silently bill it as free. return Usage(billed_output=visible, reasoning=None, measured=False) billed = visible if shape == "nested" else visible + reasoning return Usage(billed_output=billed, reasoning=reasoning, measured=True)
The table that maps the pair to the shape is the asset here, and it is a pinning problem rather than a coding one: the entry must be re-verified when the model version changes, when you move between a direct API and a cloud reseller, and when you switch from synchronous to batch. That is the same discipline as unpinned vendor defaults, applied to a field you read rather than a parameter you send.
What you are billed for and what you are shown are different objects.
The deeper reason reasoning spend resists ordinary accounting is that the billed quantity has no artifact. You are charged for the reasoning tokens the model generated; what comes back is, depending on the vendor and the setting, a summary of them, an empty block, or nothing. Anthropic states the position plainly: you pay for the full thinking tokens generated by the request, not the summary tokens, and the billed output count will not match the tokens you can see. The display setting changes the artifact and not the bill.
Three consequences follow, and all three are the kind that surface late.
- You cannot derive spend from the transcript. Any cost tooling that tokenises the stored response — a reasonable-looking design, and one people build when the usage object was not persisted — reads only the visible tail. On a heavy reasoning call that is the 12% of the output you can see.
- You cannot derive the transcript from spend. The usage number tells you a lot of thinking happened and nothing about what it was, which is why reasoning share is a cost signal and never a debugging one. When you need the content, the only honest answers are a vendor feature that returns it or a model that does — not an inference from the counter.
- Continuity has a price. Where reasoning is handed back across turns as an opaque or encrypted blob so the model can resume its own train of thought, that blob occupies the context you pay for on every turn it rides along in — the mechanics are in carrying reasoning across tool calls. A multi-step agent therefore pays for one span of reasoning once at the output rate and then repeatedly at the input rate, which is the single most under-modelled term in agent unit economics.
This is also the honest answer to the audit question. An agent whose reasoning is summarised or withheld cannot produce a reviewable record of how it reached a decision, however complete your token accounting is — the trade-off argued in latent reasoning and the trace you stop getting and in chain-of-thought faithfulness. Accounting tells you what the thinking cost. It will never tell you whether it was any good.
Instrument three numbers per call, not one.
Most agent telemetry logs a single cost figure per call, which is the one shape that cannot answer any of the questions reasoning actually raises. Log the decomposition instead, on every span, and store the provider's usage object verbatim beside it so a later correction to your shape table can be replayed rather than re-run.
- Input, visible output, reasoning as three separate counters, plus cached-input tokens where the provider reports them. Attribute cache behaviour separately — reasoning and caching interact, and the economics are in context-caching economics.
- The reasoning share — reasoning over total output — as a first-class metric per model, per task class, and per effort setting. This is the number that moves when a vendor retunes a model under a stable version string, and it moves before your quality metrics do.
- The raw usage object, unparsed. It is tiny, it is the only evidence that survives your own parsing bug, and it is the difference between re-deriving a quarter of history and losing it.
Put these on the span rather than in a billing sidecar, so reasoning spend lands on the same trajectory as the tool calls that provoked it — the join that tracing and observability exists to give you, and that the OTel GenAI semantic conventions give you names for. Without it the only available question is "why did last month cost more", which has no answer; with it the question becomes "which task class raised its reasoning share", which has exactly one.
Then reconcile. Pick one model and one surface, run a known workload, and compare your computed total against the invoice line for that model — not against the provider-level total, which is where opposite-signed errors hide. Repeat per model-and-surface pair. This is a half-day exercise that pays for itself the first time a routing change moves traffic onto a pair whose shape you assumed.
What the number is actually for.
Accurate reasoning accounting is not an end in itself; it exists to make three decisions that are otherwise guesses.
- Setting effort per task class. The effort knob is the highest-leverage cost control in a reasoning deployment and it is nearly always set once, globally, by whoever wrote the client. Reasoning share per task class tells you where the budget is going, and adaptive thinking and effort budgets covers the mechanics of moving it. Pair it with the warning in inverse scaling in test-time compute: more reasoning is not monotonically better, so the cheapest setting and the best setting are sometimes the same setting.
- Routing. A per-task-class cost that includes reasoning is the input to model routing. Computed without reasoning, the cheap model looks cheaper than it is whenever it compensates for weaker weights by thinking longer — a real and common pattern that an output-only cost model cannot see at all.
- Forecasting. Reasoning share is the term that makes agent spend non-linear in request volume, because an agent's reasoning grows with the number of steps and not with the number of user turns. Forecasting agent spend and cost control in the loop both take this as an input, and both degrade quietly when it is wrong.
One honest limit on all of it: reasoning spend is measured, not controlled. A requested budget is a hint the model may exceed or ignore, and the ceiling that actually holds is the one you enforce yourself — a hard cap on tokens per run, checked in your loop, which is the only control on this page that does not depend on the vendor telling you the truth.
Do this today, in an hour: find the one place your code computes cost from a usage object, and check its sign per provider. If it adds a reasoning field to an output field for OpenAI or Anthropic, you are overstating; if it reads candidatesTokenCount alone for Gemini, you are understating. Then make a missing reasoning field raise an alert instead of defaulting to zero, and start storing the raw usage object. Everything else here — the shape table, the per-pair reconciliation, the reasoning-share metric — is worth building, but those two edits are the ones that stop you making a routing decision on a number that is wrong by a multiple.