Concepts, data sources, and the math behind every number on every page.
The atomic billing unit and the building block of every cost calculation.
A turn is one user-prompt → assistant-response exchange. In Claude Code's JSONL transcript, each turn is a single record carrying:
timestamp (UTC, when the assistant replied),model that answered,usage block — input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens,Cost is computed per turn (see API value). When you see "39,227 turns" on the Insights page, that's the total count of those JSONL records across every parsed session.
A conversation (or session) is a single
sessionId — i.e. one continuous chat thread Claude Code kept alive.
One conversation contains many turns: opening the session is turn 1, every reply
after a follow-up prompt is another turn. A long-running CLI session in a
complex project can rack up hundreds or thousands of turns; a cowork "ask once"
session might be just two or three.
The Conversations table on the Insights page summarises each session:
See also Turn log cap for how many turns the timeline activity feed retains.
What every dollar/euro figure on the dashboard represents.
Cost is computed at the pay-as-you-go API rate for each model, regardless of what subscription plan you're on. Even when you've paid a flat $100/mo for Max 5×, the dashboard shows the API cost equivalent of your usage so you can compare value-received against fee-paid.
The cost of a single turn is:
where in/out/cw/cr are token counts and the per-million rates come from each model's pricing card. Two pricing pipelines exist:
ccusage CLI (used by usage_history.json) — Anthropic's own tool, the source of truth for KPIs.build_insights.py (used by insights.json) — our own pricing tables, used for per-project / per-tool / per-MCP attribution. Per-token rates differ slightly from ccusage.See Reconciliation for how the two are aligned.
Two distinct ways Claude Code writes JSONL transcripts:
claude command. Stored under ~/.claude/projects/<cwd-slug>/.%APPDATA%/Claude/local-agent-mode-sessions/<tenant>/<workspace>/local_<sessionId>/.claude/projects/.Both produce identical JSONL turn records — the dashboard differentiates them only because the directory structure tells us which entrypoint launched the session.
Claude Code can attach a session to a git worktree —
a side-checkout of the same repo on a different branch under .claude/worktrees/<random-name>/.
Each worktree session writes its own JSONL stream, so build_insights.py sees them as
distinct project entries named <parent> (wt: <branch>).
The Projects roster and the dashboard's project filter roll worktrees back up under their parent so a project's true total includes every branch's spend.
How much of your prompt was reused vs paid full price for.
cache_read tokens cost ~10% of input rate. cache_create
tokens cost ~125% of input rate (the cost of putting them in the cache). A high
ratio means long sessions are reusing context efficiently.
Healthy is >85%. Below 50% suggests the model is rehydrating context from scratch each turn.
"What would this have cost if prompt caching didn't exist?"
Without caching, every cache_read token would be re-billed at the
full input rate (since they'd be regular input tokens) and
cache_create would never have been a separate line item.
The savings are:
The 0.9 factor is (input rate − cache_read rate) ÷ input rate ≈ (1 − 0.1). Reading from cache is 10% of input price; the saving per token is the other 90%.
Used by Chapter 4 of the Story page and the dashboard's "Cost by Model & Pricing Tier" table.
Average daily cost extrapolated to a 30.44-day month (the average days/month across a year).
Span is calendar-day distance between the first and last active day in the selected range, not just the count of active days. So 5 active days inside a 20-day calendar window divides by 20, not 5.
What share of your subscription's flat fee you're getting in API value.
Pay-as-you-go API and Enterprise plans return n/a (no flat fee to compare against).
Every tool invocation is bucketed into one of five categories at parse time:
mcp__<server>__<name>. Server name is extracted as the second segment.plugin_<name>.Task tool with a subagent_type argument.Cost is attributed to the turn that ran the tool, then divided equally among that turn's tool invocations for per-tool aggregations.
Two pricing engines, two slightly different totals. ccusage
($A) uses Anthropic's pricing data verbatim. build_insights.py
($B) re-prices from JSONL using static rate tables — typically ~2× higher
on cache-heavy workloads.
The dashboard shows ccusage totals (KPI authoritative). When the user filters by project, only insights has the per-project breakdown. To keep the filter consistent, we use insights for shares and apply them to the ccusage day-total:
So a project's filtered total is mathematically guaranteed to be a fraction of the day's true dashboard total.
Each machine writes its own snapshot to machines/<hostname>.json
and pushes via git. build_machine_snapshot.py aggregates every
peer's snapshot into:
machines/manifest.{json,js} — list of machines with summary totals.machines/daily_merged.{json,js} — per-date sum across hosts (powers the contribution heatmap).machines/hourly_merged.{json,js} — per-hour sum (powers the timeline line chart).machines/all_machines.{json,js} — per-host daily[] arrays bundled into one shim (powers the dashboard Machine filter).All times stay UTC across machines so hour-bucket keys merge cleanly. Display-time conversion (story page tz selector) shifts only what's rendered.
JSONL records carry a Z-suffixed UTC timestamp. build_insights.py
slices the first 13 chars (YYYY-MM-DDTHH) as the hour-bucket
key. Aggregations across machines work because every host writes the same
canonical UTC keys.
On the page side, tzmode.js uses
Intl.DateTimeFormat({ timeZone }) — DST is handled by the
browser's IANA tz database, so you don't need to track shift transitions
manually. UTC remains the default; pick a zone via Settings.
The Timeline activity feed reads insights.json's
turnLog[], which is capped at 1000 most-recent turns
by default. Heavy users can raise the cap by exporting an env var:
CCUSAGE_TURN_LOG_CAP=5000 bash update_usage.sh
The feed title shows "capped at X of N parsed" when truncation occurred so older activity isn't silently dropped.
The Projects roster tags any project as a "dead-end" if either:
insights.json's generatedAt).
Auto-generated cowork session names like
affectionate-gifted-ramanujan usually fall into this bucket.
Hide-by-default toggle keeps them out of the roster unless you
explicitly want to see them.
Across the selected date range, every turn is bucketed by its UTC weekday (0…6) × hour-of-day (0…23). The cell with the highest total cost is the "peak cell". A single integer hour-of-day from this heatmap gets converted to local time via:
Caveat: a single hour-of-day across many months can't fully represent a DST-spanning year. The "today" reference is used for DST orientation — accurate within a ±1-hour shift if your data crosses a DST transition.