# Pricing math (/docs/pricing)



`@pleach/core/pricing` is the cost math. It turns a turn's token
counts into a dollar figure against a `(family × callClass)` rate
matrix. It's what puts the USD number next to a row in the
[audit ledger](/docs/audit-ledger) — pure functions plus an optional
live-rate resolver, no side effects in the hot path.

## `computeTurnCost` [#computeturncost]

The pure primitive. Give it the token counts and a matrix; get back
the cost, or `null` when the matrix has no cell for that
`(family, callClass)`.

```ts
import { computeTurnCost, DEFAULT_PRICING_MATRIX } from "@pleach/core/pricing"

const cost = computeTurnCost(
  {
    family: "anthropic",
    callClass: "converse",
    inputTokens: 1_200,
    outputTokens: 800,
    reasoningTokens: 400, // folded into output at the output rate
  },
  DEFAULT_PRICING_MATRIX, // optional; this is the default
)
// cost: ComputeTurnCostResult | null
```

`ComputeTurnCostInput` carries `family`, `callClass`, `inputTokens`,
`outputTokens`, and optional `reasoningTokens`. Input tokens are
*fresh* tokens — cache reads are not billed at the input rate.
Reasoning tokens bill at the output rate. A missing matrix cell
returns `null` rather than guessing, so a caller never silently
under-reports.

## `DEFAULT_PRICING_MATRIX` [#default_pricing_matrix]

The bundled rate table. Every `(family × callClass)` cell is present —
no missing-cell fallback needed. Each cell is a `PricingEntry`:

```ts
interface PricingEntry {
  readonly inputCostPer1M: number   // USD per 1M input tokens
  readonly outputCostPer1M: number  // USD per 1M output tokens
}
```

## Live rates — `createPricingResolver` [#live-rates--createpricingresolver]

Bundled rates drift. `createPricingResolver` caches a fetched matrix
and re-fetches when it goes stale.

```ts
import { createPricingResolver, DEFAULT_PRICING_MATRIX } from "@pleach/core/pricing"

const resolver = createPricingResolver({
  ttlMs: 60 * 60 * 1000,          // consider a fetched matrix fresh for 1h
  fallback: DEFAULT_PRICING_MATRIX, // used until the first fetch lands
})

const matrix = await resolver.resolve()
const cost = computeTurnCost(input, matrix)
```

* `ttlMs` — freshness window. Within it, `.resolve()` returns the
  cached matrix synchronously (wrapped in a resolved promise); past it,
  the next `.resolve()` triggers a fetch that blocks until the new data
  lands. Set `ttlMs: Infinity` to fetch exactly once — the right choice
  in a short-lived function invocation.
* `fallback` — returned when no matrix is cached and the first fetch
  fails. After that, a failed fetch returns the last-good matrix. Pass
  `DEFAULT_PRICING_MATRIX`.
* Concurrent `.resolve()` calls during a fetch share one in-flight
  request — no fetch stampede.

`schedulePricingRefresh(resolver, { intervalMs })` drives the refresh
on a timer instead of lazily on read.

## OpenRouter rates [#openrouter-rates]

The live source behind the resolver is OpenRouter's model list.

* `fetchOpenRouterPricing(opts?)` → `FetchOpenRouterPricingResult` —
  pulls current per-model rates (override `endpoint` to point at a
  mirror).
* `applyToMatrix({ base, byModelId, modelIdMap })` — folds fetched
  per-model rates into a `(family × callClass)` matrix, mapping model
  IDs to matrix cells.

## Helpers [#helpers]

* `parseModelSlug(slug)` → `ParseModelSlugResult | null` — splits a
  `"vendor/model"` slug into its parts, or `null` when the slug isn't a
  single `vendor/model` pair.
* `resolveCostEventUsd(event, matrix?)` — returns the event's own
  `costUsd` when the seam already priced it (a real rate is never
  overridden), and otherwise computes the cost from the event's token
  counts. This is the reconciliation seam: providers that report a
  price keep it; providers that don't get one derived.

## Where it sits [#where-it-sits]

The cost figure this module computes rolls up per turn into the audit
ledger's `TokenCostRecord` (see [Typed records](/docs/typed-records))
and, in a multi-tenant deployment, into
[`@pleach/gateway`'s cost events](/docs/gateway/cost-events). Pricing
is the shared arithmetic underneath both.
