Cost analytics
How every LLM dollar is attributed and rolled up — the ledger top views, the eight-dimension rollup tuple, and the decision-log row behind every number.
On this page
Cost analytics is the read side of the decision ledger. Every proxied request writes one costed JSONL row; everything on this page is an aggregation of those rows, and two surfaces answering the same question can never disagree because both read the same log.
Two ways spend is attributed
TokenTriage attributes spend along two distinct surfaces — don’t conflate them:
-
The ledger
topviews roll the raw log up by a single facet from the terminal:--by model | agent | tool | trace | step-class, optionally scoped with--since. Token types (input / output / cache / reasoning) are priced per row and shown as columns, not as a facet.$ tokentriage top --by model --since 24h $ tokentriage top --trace tr_9f2c… # per-step cost tree for one traceMCP-tool accounting is per turn: because agentic clients resend the whole conversation each turn, each tool invocation is counted once (the turn that first feeds its result back), so an N-turn loop reports N calls, not ~N²/2. The
IN-TOK-ATTRcolumn is achar/4estimate — those rows carry"estimated": true, distinct from a provider-reportedusage.source. -
The cost-analytics rollup tuple powers the dashboard’s finance views. It pre- aggregates hourly on an eight-dimension key:
dim.tenant·dim.team·dim.key·dim.feature·decision.model_served·decision.tier·http.provider·http.endpoint

Business dimensions
The four business dims come from request headers when ledger.dimensions.enabled is set:
x-tt-user, x-tt-tenant, x-tt-feature, and bounded x-tt-tag-<k> (default up to 8
tags). They’re stamped on the row and stripped before the upstream forward; spend with no
value renders as a first-class “unattributed” slice, never silently dropped. Promotion
to Prometheus labels is opt-in and cardinality-guarded via
ledger.dimensions.promote_to_metrics. See
Observability & tracing for the full header contract.
Rollups and grains
When analytics.rollups.enabled (over dashboard.index.enabled) is on, a month-scale
question is answered from pre-aggregated cells rather than a full raw-row scan — roughly 3×
faster in the house benchmark (see Benchmarks for the measured
figures and methodology; the numbers are hardware-specific, not a wall-clock promise). Time
buckets use a fixed grain vocabulary: 1m · 1h · 1d · 1w · 1mo · 1q.

UNPRICED, everywhere
A row TokenTriage could not price is UNPRICED, and it propagates as a count — never as
$0.00 — through every rollup, report, forecast, and export. A total reads like $41.20 over 1,904 priced rows — 116 unpriced rows excluded. This is the same rule the
honesty model sets out, applied
to every aggregate.
Next: turn attribution into action with Cost optimization & savings verdicts.