Skip to content

Metering & pricing

Every call — success, error, cache hit, even a router-classifier call — writes one row to a SQLite ledger, priced at write time and attributed to all five principal dimensions the caller declared: tenant, project, user, agent, session.

Cache hits are recorded with zero usage and the tokens the provider would have charged recorded as saved (tokens_saved, usd_saved) — so the cache’s value shows up in the same ledger as the spend it avoided.

Read it back:

  • GET /v1/calls — recent rows
  • GET /v1/cost/by_principal — spend grouped by any dimension
  • GET /v1/cost/by_agent — the per-agent view
  • the dashboard at / renders both

Prices are keyed by model, not by provider, with resolution in order:

  1. exact model name (gpt-5.6-terra)
  2. vendor-prefix suffix match (openai/gpt-4.1-minigpt-4.1-mini)
  3. glob patterns (gemini-3.1-*)
  4. a per-provider fallback rate
  5. unpriced_default — $0/$0, and the row says so via price_source

Each entry can carry cache_read_multiplier / cache_write_multiplier / batch_multiplier (vendors disagree on cache billing mechanics — that difference is data here, not code) and a quality weight used by cost/quality routing.

models:
gemini-2.5-flash:
provider: gemini
input: 0.30 # $/Mtok
output: 2.50
cache_read_multiplier: 0.25
quality: 0.55

price_source on every cost breakdown says which rule priced the call — a paid model silently landing on unpriced_default would make every dollar report a lie. CI runs scripts/check_pricing.py, which registers every provider with fake keys and fails if any configured default model resolves to the unpriced row.

The full shipped table (synced from the release) is in the configuration reference.