usage receipt; the arithmetic below is
the published definition of the number on it.One nuance if you are also checking payloads against the
per-request ceiling: the ceiling counts your
strings as sent, without NFC normalisation, while the meter normalises to NFC.
For text already in NFC, which is nearly all text, the two agree.Why this page exists
If you dispute a charge, we cannot show you your records. We never had them. So the audit story has to work without them, and it does — by making the meter a pure function of your request body:You hold the payload
We publish the function
The receipt names the version
meter_version pins which definition applied.The definition
keywords, tags and
attributes are read as indexing hints and are deliberately outside the meter,
because a number you have to be able to reproduce should be short enough to
reproduce by hand.)
Not counted: keywords, tags, attributes (structurally bounded; a size
cap on them applies), record id, source,
step_id, kind, anything in the response, and any JSON syntax — braces,
quotes and commas in the envelope are not your text.
tokens_before on the response is a different number and is not the meter.On this deployment it works out to Σ estimate_tokens(record.text) — the record
text only. The meter adds record.title, task.prompt and
task.recent_prompts, so your input_tokens will be larger than
tokens_before, and the gap is exactly those three field groups. That is
expected, not a discrepancy.Do not reconcile an invoice against tokens_before: it is an engine-internal
budgeting quantity and is free to move when engine internals do, while the meter
is pinned by meter_version.The counting rules
These are part ofmeter_version, not implementation detail. They are what
make “you can recompute it” a true statement rather than an approximate one.
Normalise to NFC before counting
Normalise to NFC before counting
á can be one
code point or two — a followed by a combining acute — and the counter sees
them differently:á clause. Only the second is what the meter counts.So the meter normalises first, and so should you. For text that is already in
NFC — which is nearly all text — this is a no-op.Count over Unicode scalar values, not UTF-16 code units
Count over Unicode scalar values, not UTF-16 code units
"𝔘".length is 2 because the string is measured in UTF-16 code
units, while the same character is one scalar value.A naïve port using .length therefore diverges on every astral-plane
character — emoji, many CJK extensions, mathematical alphanumerics — and the
same document would bill differently depending on which language you checked
with. The TypeScript reference below uses [...text].length for this reason.Round once, at invoice aggregation
Round once, at invoice aggregation
input_tokens across your receipts first, then multiply, then round.
Not the other way round.The reference implementation
This is the published function, verbatim. It is a deterministic heuristic, not a model tokenizer — deliberately, so the number is reproducible with no model dependency and no per-provider drift.How the count works
Two estimates, and the larger wins:- Lexical — the number of word runs and standalone punctuation marks.
- Character fallback — code points ÷ 4, at least 1. This catches long compact strings with no word boundaries, such as a base64 blob or minified JSON, which the lexical pass would badly under-count.
max(lexical, char_based) means a normal sentence is counted lexically and a
dense blob is counted by length.
Worked example
The quickstart payload, field by field:task.keywords (["refund", "invoice"]) contributes nothing — it is
capped, not counted.
Then, from the receipt:
charge_multiplier read off the receipt for that request, and
rate_per_million from the rate card named by price_version.
Checking a whole invoice
Export your receipts for the period
input_tokens, outcome, charge_multiplier,
meter_version, price_version.Group by (price_version, charge_multiplier)
Sum input_tokens within each group, then price the group
Round the total, once
input_tokens for a sample of requests from your own
stored payloads. If your recomputation matches the receipts and your arithmetic
matches the invoice, the bill is verified end to end without us being involved.
If your number disagrees
You are off by a lot on one request
You are off by a lot on one request
title as well as text, and recent_prompts as
well as prompt. Those four are the whole surface, and omitting title or
recent_prompts is the usual cause.Also check you are not counting keywords, tags or attributes, which are
not metered, and not counting the JSON envelope.You are off by a little, on text with unusual characters
You are off by a little, on text with unusual characters
- You did not normalise to NFC. Decomposed accents count differently.
- You ported the function to a language that measures strings in UTF-16 code units. Use code points.
You are off by a little and none of that applies
You are off by a little and none of that applies
It still disagrees
It still disagrees
request_id, the meter_version from the receipt, and your
computed number. A disagreement between the published function and the meter
is a defect on our side, and it is one we want to hear about — the whole point
of publishing the function is that you can find it.Checking a payload before you send it
The same function answers “will this413?”. Your per-request ceiling is
denominated in exactly this quantity: