How we compute spend
This page explains the spend figure on the dashboard (period total, daily series, by-model cut) and every aggregate spend number the platform serves with a link back to here.
What it is
Spend is what your providers charged for requests that passed through our gateway. We compute it per request from the token counts the provider reported in its own response, priced against a frozen, versioned rate list. It measures provider list cost, not your provider invoice, and it is computed, never inferred. We do not infer token counts or guess rates, and a request we cannot price is stored with a null cost.
How it is computed
All money is integer micro-USD, one millionth of a dollar. No floating-point number touches a price. For each request:
- Find the
(provider, model)row in the frozen price list pinned on the receipt asrefPriceVersion, todayrpl-2026-08-05. Rates are micro-USD per million tokens. A model absent from the list prices to null and raisesprice_list_mismatchinternally. It is never inferred. - Resolve full-rate input. OpenAI reports
cached_tokensinsideprompt_tokens, so full-rate input isprompt_tokens - cached_tokens. Anthropic'sinput_tokensalready excludes cache reads and writes, so it is used unchanged. - Sum four products in 128-bit integer arithmetic: full-rate input times
inputPerMtok, cached tokens timescachedInputPerMtok, cache-write tokens timescacheWritePerMtok, completion tokens timesoutputPerMtok. - Floor-divide by 1,000,000, once, at the end, so per-component truncation cannot accumulate.
Reasoning tokens are not a separate term. Both providers report them inside completion tokens, so they price at the output rate exactly once. Detail a provider did not report contributes zero, because it is absent from that provider's invoice too. The receipt keeps the field null.
Worked example
An OpenAI gpt-4.1 request under rpl-2026-08-05: 5,211 prompt tokens of which 3,647 cached, and 804 completion tokens.
| Term | Tokens | Rate (micro-USD/MTok) | Product |
|---|---|---|---|
| Full-rate input (5,211 - 3,647) | 1,564 | 2,000,000 | 3,128,000,000 |
| Cached input | 3,647 | 500,000 | 1,823,500,000 |
| Cache write | 0 | 0 | 0 |
| Completion | 804 | 8,000,000 | 6,432,000,000 |
The sum is 11,383,500,000, which floor-divides to 11,383 micro-USD. The receipt renders 0.011383. The dashboard renders $0.01, rounded for display only, never for storage.
That integer is upserted additively into rollup_daily for the UTC day of the request start timestamp. Ingestion is idempotent on requestId, so a redelivered event never counts twice. Priced receipts also append to the workspace hash chain under recovea-chain-v1, which is the only recipe production writes. A second recipe, recovea-receipt-v1, is published with golden vectors, but nothing in the API writes or serves it. Both are SHA-256 over canonically encoded fields joined by U+001F. See receipts and ledger.
What it does not include
- Anything on your invoice that is not list price: discounts, committed-use, credits, free tiers, taxes.
- Rate dimensions outside the list schema: OpenAI long-context tiers above 272K input, Anthropic one-hour cache writes, regional uplifts, fast-mode SKUs. These price at the base rate, which under-measures and never over-charges.
- Traffic that did not pass through the gateway.
- Requests we could not price. They count as requests, hold a null cost, and add zero spend.
- Coalesced dedup followers, which made no provider call.
- Your Recovea subscription.
Limits and current status
- Budgets. Only workspace-scoped caps reach the gateway. The evaluator supports key and project scope, but the cap message the control plane pushes carries workspace scope only, so those caps are not enforced in path. Project-scoped spend reads as unknown, not zero, because receipts carry no project column.
- Alerts. The sweep runs once a minute. A fast burst can cross a threshold and reach a cap between ticks. Alerts warn on the way up. They are not an ordering guarantee.
- Counterfactual. Measured, never applied. No lever is activated in any deployed environment, and the shipped store returns no savings figure, so the surface stays empty.
- CPSO. Served at list price. The
settledlabel flips when the calendar month ends, not because an invoice was reconciled. Period close runs only as a fixture-driven job with no schedule. Invoice reconciliation is planned, not wired. - Score. The engine is not shipped. The store returns no factors, so no workspace has a score today.
- Spend and attribution drift. Project and tags are aggregated into
rollup_cutsandrollup_tag_cutsbut are not on the receipt row, so they cannot be re-derived from an export, and CPSO omits tag-based success rules for the same reason. Separately, the dashboard total and daily series readrollup_dailywhile the by-model cut readsreceipts: same rows, two layers. The receipt commits in one transaction and the rollup in a second, so a rollup failure after the receipt lands leaves the redelivered event skipped as a duplicate and that spend missing fromrollup_daily. Receipts are the record of truth, and no reconciliation job repairs such a gap today.
Where to check us
- Export the period. Two routes serve the same chain. The session route,
GET /platform/v1/ledger/export?period=YYYY-MM&format=jsonl, spells each row in camelCase:tokensIn,tokensOut,tokensCached,costMicroUsd,priceListVersion,previousHashandrowHash. The key-authed route,GET /v1/ledger/export, spells the same fields in snake_case. The ledger page carries the full field map for both. - Re-derive any row's cost with the four-term sum above at the rates for its pinned price list version. That rate table ships verbatim in our MIT-licensed CLI, in
dist/prices.jsof therecoveanpm package — published asrecovea@0.1.4, which pinsrpl-2026-08-05, the same version this page describes. Runnpm pack recoveaand read the file. Two gaps remain: no endpoint serves the rate table over HTTP, and the package carries one table at a time, so a receipt pinned to a superseded version cannot be re-derived from the current release. - Sum
costMicroUsdover the period and compare with the dashboard total. Unpriced requests contribute zero on both sides, so the two should agree to the micro-USD. - Re-derive the chain offline from the same export to confirm no row changed after the fact. The recipe is on the ledger page.
If your arithmetic and ours disagree, send us the request id. We publish the correction as a new row, never as an edit.