# D36. What paid AI services charge is a ledger of measured requests, read only within its price period (13 Oct 2026)

On 13 October the estimate of ElevenLabs voices was 3.6 times too low: it had learnt the share a Starter plan was charged during Eleven v4's launch discount (72 %, announced « until October 12 », told by no field of the API), which had ended overnight (D29, D30). Hence a log of the costs as we experiment, and an average the app uses, for the next AI services too, organised so that an estimate is never used once the models or the prices change; discounts can skew credits.
- **The ledger** is in `ai-costs/` (README there): `measurements.jsonl`, one measured request a line (provider, the exact model id, the request's kind, its settings, the plan or tier, the UTC date, the units sent and billed as `{ unit: count }` maps, so that characters, tokens, images or seconds and credits or dollars need no new format, the price period, the source such as a history id, and what the service said of its prices then); `periods.json`, the providers (their billed unit, the settings that change the price, the settings the list price is multiplied by) and, for each provider, model and plan, its price periods: start and end, the list price per unit (entries by settings, per a quantity such as a million tokens), a discount's rate, announced end and source. A new model id, a new list price or a discount starting or ending opens a period; every measurement belongs to exactly one, which `ledgerProblems` checks (`@elabora/core` `ai/costs.ts`).
- **The averages are generated**, never written: `npm run gen` writes `@elabora/core` `ai/cost-ledger.generated.ts` (the periods and, by provider, model, plan, price settings and period, the requests, their list price, what was billed, the share billed and the share without the period's discount), and refuses a ledger with problems; a unit test fails when the module is stale or a measurement has no period.
- **The estimate** (`expectedShare`) reads only the current period, the one holding today for the model and plan, and only while its list price is the one the service says now (`GET /v1/models` for ElevenLabs): the period's average from 3 measurements, its discount taken off, whether its end is announced or not (a discount whose rate is not known makes the period's figures unusable rather than counted on); the list price where the period has fewer, or no period is known. An older period's figures are never used: a new model, a new list price or the end of a discount start from the list price until they are measured. The app and the command line ship these averages; the author's own requests (the `character-cost` header, kept in `<cache>/charges.json` with their date, plan and list price) take over under the same keys and period rules: from the first request where they say more than the ledger (a rise followed at once), from three requests and a quarter more where the ledger has nothing, never under the ledger's figure (a discount nobody announced is not counted on), never past the list price; where the plan has no period, only the requests since the model's last change of price on any plan count. D30's threshold and its shares of the quota are unchanged.
- **Recording** is a tool of ours, not the authors': `npm run costs:record [-- --dry-run]` reads ElevenLabs' history (`GET /v1/history`, free; the text is in each item's `dialogue`), appends the requests it lacks once (`history_item_id`), with the plan read then, the voices' rates and `GET /v1/models`' `character_cost_multiplier` and `cost_discount_multiplier` at that moment, says a list price or a discount the current period does not have and a share far from the period's average, and generates the averages again. The key is read from `~/.config/elabora-dev/elevenlabs-api-key` and never written.
- **First content**: the 49 requests of 12 and 13 October (the launch discount, 29 requests billed 0.110 of the list price, 0.401 without the discount; then 18 at 0.4005), and 5 bought for the ledger on 13 October (40 credits: Eleven v3 and v4 Turbo, 0.4 of the list price each). Eleven v4 on a Starter plan is told 0.4005 of the list price, v4 Turbo and v3 0.4; Multilingual v2 (one request) and any other plan, the list price.
*Revisit if:* a service's bill cannot be measured per request (a monthly invoice only), its price depends on something no setting says (the time of day, the load), or authors' own requests should be pooled to measure plans we do not have.
