← Research

How a receipt is built

This page explains the per-request receipt: the token, cost, and hash fields behind every row in the request feed, the receipt view, and the ledger export.

What it is

A receipt is one metered request: what the provider reported, the cost we computed from those numbers against a frozen price list, and a hash binding the fields so the row cannot be edited later. We never estimate. An unreported token count is stored as null, and a model absent from the price list gives a null cost.

How it is computed

1. Usage. We record only what the provider returned: prompt/input tokens, completion/output tokens, cached and cache-creation tokens, reasoning tokens, the finish reason, and the upstream status. Nothing is read from request or response bodies.

2. Cost. Price rows are micro-USD per million tokens (1,000,000 micro-USD = $1). Arithmetic is integer i128: products are summed first, then floor-divided by 1,000,000 exactly once, so per-component rounding cannot accumulate.

ClassRule
OpenAI inputcached_tokens is inside prompt_tokens: full rate on the difference, cached rate on the rest
Anthropic inputinput_tokens excludes cache reads and writes, so both are added on top
Outputcompletion_tokens at the output rate; reasoning_tokens sit inside it, never charged twice
Unreported detailContributes zero, as on the provider invoice

Worked example: gpt-4.1 under rpl-2026-08-05 (input $2.00/MTok, cached $0.50/MTok, output $8.00/MTok), 5,211 prompt tokens of which 3,647 cached, and 804 completion tokens.

  • (5211 - 3647) x 2,000,000 = 3,128,000,000
  • 3647 x 500,000 = 1,823,500,000
  • 804 x 8,000,000 = 6,432,000,000
  • sum 11,383,500,000, floored by 1,000,000 = 11,383 micro-USD, displayed 0.011383

3. Hash. Both published recipes share one encoding: fields joined with U+001F, SHA-256, lowercase hex. Integers are base-10 without leading zeros, null is the empty string, an array is comma-joined, an empty array is the empty string.

recovea-chain-v1 hashes, in order: recipe id, previous row hash, requestId, timestampMs, provider, model, route, tokensIn, tokensOut, tokensCached, integer costMicroUsd, priceListVersion, basis, mandateId. Lever and rowHash are not inputs. recovea-receipt-v1 is a per-row variant without chain linkage; its status is below.

Worked example, the first committed recovea-chain-v1 vector. Join recovea-chain-v1, 64 zeros (genesis), req_9f2c41d87ab34e60, 1750000000000, openai, gpt-4.1, /v1/chat/completions, 1200, 340, 0, 4180, rpl-2026-06-01, measured, and the empty string. SHA-256 gives 2f542e00b0403c4e95403ef7fe6ac40d24c6598e0d100ee6cc6ec8a6d18152e2.

What it does not include

  • Recovea fees, taxes, provider minimums, commitment discounts, negotiated rates. The meter measures at list price; the provider invoice is the bill.
  • Prompts, completions, and secrets. No receipt field can hold request content by schema.
  • Dedup followers, which made no provider call and carry a null cost.
  • Unpriced rows in the export: a row chains only once it has both token counts and a cost.
  • Any deduction for retries or failures. Both stay in cost.

Limits and current status

  • Recipes. recovea-chain-v1 is what production computes and stores. recovea-receipt-v1 has committed golden vectors, but nothing in the API writes or serves it. The receipt endpoint uses receipt-v1 field names while labelling the row recovea-chain-v1 and returning the chain hash. Verify against the export, not that view.
  • Null rendering. The read path coerces unreported tokens and unpriced costs to 0 before serialization, so a feed row can show zeros where the stored receipt holds null. The stored row and the export are right; the rendered JSON is not.
  • Attribution drift. In the deployed read path project always renders default, env always live, and keyPrefix is the key id, not a masked key. Project and turn attribution rides separate tables, not the receipt row.
  • Price list. Long-context tiers, one-hour cache writes, regional uplifts, and fast-mode SKUs sit outside the price schema, so those requests price at the base rate. That under-measures, never overcharges.
  • Budgets. Only workspace-scoped caps reach the gateway. Key- and project-scoped caps can be stored, but are not enforced.
  • Alerts. The sweep targets one pass every 60 seconds, so a fast burst can cross a threshold and reach a cap between passes. Alerts warn on the way up; they are not an ordering guarantee.
  • Counterfactual. Measured, never applied. Levers default off and are activated in no deployed environment, so no receipt carries a fired lever.
  • CPSO. Figures are at list price. Invoice reconciliation at period close runs only from a hand-supplied fixture into in-memory stores. It is not scheduled or wired to the database.
  • Score. The engine is not shipped. The endpoint returns a null score. No workspace has a score today.

Where to check us

  1. Export a period: GET /platform/v1/ledger/export?period=YYYY-MM&format=jsonl on a session, or GET /v1/ledger/export with an rcv_live_ key. The header line carries the recipe id, genesis, chain head, row count, and the price list versions pinned. The two routes serve the same rows under different field spellings; the map is on the ledger page.
  2. Re-derive each row hash from the fourteen recovea-chain-v1 fields. Hash the integer micro-USD value, never a formatted string: that is costMicroUsd on the session route, and cost (JSON, JSONL) or the cost_micro_usd column (CSV) on the key-authed route. Check each previousHash against the prior rowHash, and the last hash against chainHead.
  3. Re-derive each cost from tokensIn, tokensOut, tokensCached and the row's priceListVersion, applying the rules above and flooring once.

You need only a SHA-256 implementation; nothing here calls back to us. Any row where your number differs from ours is a bug we want reported.