Skip to content

Mapping Usage and Token-Count Objects Between APIs

9 min read · updated August 11, 2026

Every chat API returns how many tokens it charged you for. No two of them agree on what to call the numbers, which of them to include, or when to send them. A cost script written against one shape does not error on another — it reads undefined, coerces it to zero, and reports a month of free inference.

The four shapes

There are broadly four response envelopes in circulation, and the token counts live in a different place with different names in each. The distinction that matters most is prompt/completion versus input/output: those are the same two numbers under two vocabularies, and reading one name off the other object is the single most common way this goes wrong.

OpenAI Chat Completions        usage.prompt_tokens
                               usage.completion_tokens
                               usage.total_tokens

OpenAI Responses               usage.input_tokens
                               usage.output_tokens
                               usage.total_tokens

Anthropic Messages             usage.input_tokens
                               usage.output_tokens
                               (no total field at all)

Google Gemini generateContent  usageMetadata.promptTokenCount
                               usageMetadata.candidatesTokenCount
                               usageMetadata.totalTokenCount

Three things in that table are worth stopping on. The object is called usage on three of the four and usageMetadata on the fourth, so a generic accessor that reaches for usage finds nothing on a Gemini response. Gemini uses camel case throughout while the other three use snake case, so even the names that mean the same thing do not match as strings. And Anthropic’s object has no total: if your schema requires one, you compute it, and you have to decide what goes into it before you can.

Note also that OpenAI’s two APIs disagree with each other. Moving from Chat Completions to the Responses API within the same vendor renames both counters. That is the migration that catches teams out, because nothing else about the credentials or the base URL changed and there is no reason to expect the cost path to have moved.

Cached, reasoning and tool tokens

The headline numbers are the easy part. The counts that decide whether your figure matches the invoice are in nested detail objects, and those have the least overlap of anything here.

  • Cache reads. OpenAI reports them inside the prompt side, as prompt_tokens_details.cached_tokens on Chat Completions and input_tokens_details.cached_tokens on Responses — a subset of the prompt count, already included in it. Anthropic reports them as two sibling fields, cache_read_input_tokens and cache_creation_input_tokens, which are not included in input_tokens. Gemini reports cachedContentTokenCount, which its reference describes as the cached portion of the prompt count. Same concept, three different answers to “do I add this or is it already counted?”
  • Reasoning tokens. OpenAI puts them in completion_tokens_details.reasoning_tokens (Chat Completions) or output_tokens_details.reasoning_tokens (Responses), inside the output count. Google’s reference documents thoughtsTokenCount as a separate field and says the total includes it. Anthropic bills extended thinking as ordinary output tokens, so there is no separate counter to read — which means a dashboard column labelled “reasoning” is simply empty for that provider rather than zero, and those are different facts.
  • Server-side tool use. Google documents toolUsePromptTokenCount. Anthropic’s streaming reference shows a server_tool_use object inside usage carrying counts such as web_search_requests — a count of requests, not tokens, which will not sum with anything else on the page.
These detail objects are the fastest-moving part of every one of these APIs; fields have been added to all four within the life of the current versions. Treat any list of them, including this one, as the shape at the time of writing and re-read the vendor reference before you rely on a subfield for billing.

When usage arrives in a stream

On a non-streaming call the usage object is in the response body and there is nothing to think about. Streaming is where cost tracking silently stops working, because each API delivers the numbers at a different moment and one of them does not deliver them at all unless you ask.

OpenAI’s Chat Completions stream omits usage from every chunk by default. You opt in with stream_options: { include_usage: true }, after which a final chunk arrives with an empty choices array and the usage object populated. Code that iterates chunks and reads choices[0].delta without checking for an empty array will throw on exactly that chunk, which is why the option is so often added and then reverted.

Anthropic sends input tokens early and output tokens late. The message_start event carries a message object whose usage already has input_tokens and, on cached requests, the two cache fields. Output tokens arrive on message_delta, and Anthropic’s streaming reference is explicit that the counts in that event are cumulative — so you take the last one you saw, you do not add them up. Adding them up is a bug that scales with response length, which makes it look like a pricing change rather than an arithmetic error.

Gemini’s streaming endpoint sends a full response object per chunk, each of which may carry usageMetadata. Take the last. The general mechanics of these frames are covered in the streaming event mapping page; what matters here is only that “read usage from the response body” is not a thing you can do uniformly once streaming is on.

Total is not always a sum

It is tempting to normalise by discarding whatever total the provider sent and computing input plus output yourself. That produces a number that disagrees with the vendor’s own, because the vendor totals are not all the same quantity. Google’s reference describes totalTokenCount as prompt plus thoughts plus response, so it is larger than the two headline counters added together on a thinking model. OpenAI’s total_tokens is the sum of its two headline counters, with reasoning tokens already folded into the completion side. Anthropic has none, and if you build one you must decide explicitly whether cache-creation tokens belong in it — they are billed, and they are not in input_tokens.

The practical rule: never reconcile against a total. Reconcile against the priced quantities, one line per price. A billing model that has a row for cached input, uncached input, output and cache writes can be checked against an invoice. One that has a single token number cannot, and the discrepancy will be small enough to look like rounding right up until it is not. There is more on that failure in why your token count does not match your bill.

A normalised record that does not lie

The adapter that fixes this is small, and its only interesting design decision is how it represents a field the provider did not send. Use null, not 0. Zero is a measurement; null is the absence of one, and a dashboard that cannot tell them apart will average missing data into your cost per request.

type Usage = {
  inputTokens: number | null;       // uncached prompt tokens, as billed
  outputTokens: number | null;      // everything generated, reasoning included
  cachedInputTokens: number | null; // read from cache, priced lower
  cacheWriteTokens: number | null;  // written to cache, priced higher; null where the
                                    // provider has no such concept
  reasoningTokens: number | null;   // null means "not reported", not "none"
  providerTotal: number | null;     // whatever the provider called total, kept verbatim
};

Keep providerTotal even though you will not price from it. When your computed figure and the vendor’s disagree, having both recorded turns an argument into a diff. And write the adapter so an unrecognised provider raises rather than returning an all-null record: a silent zero-cost provider is precisely the outcome this page exists to prevent.

The reference for each shape is the vendor’s own: Google publishes the generateContent reference including the full UsageMetadata field list, and Anthropic documents where usage appears in a stream in its streaming reference. Read those before trusting any table, this one included.