Export schema (--json / --csv)
aireceipts can emit a session receipt as machine-readable JSON (--json) or CSV (--csv) instead of the text receipt, for FinOps tooling and spreadsheet ingestion. This document is the authoritative, field-by-field description of those shapes.
The single source of truth is the zod schema in src/receipt/exportSchema.ts (receiptJsonSchema / compareJsonSchema). This document mirrors it, and an automated parity test (test/receipt/json-schema-parity.test.ts) fails the build if the two ever disagree — every documented field name must equal the schema's field names, and the version below must equal the schema's SCHEMA_VERSION. If you find a discrepancy, it's a bug; please open an issue.
The current schema version is 1. It appears as schemaVersion on the root of every JSON export.
Invariants this schema upholds
- I2 — never a fabricated dollar. A
usd/totalUsd/actualUsdfield isnull(JSON) or an empty cell (CSV) whenever nothing priced; it is never0standing in for "unknown". Token fields are always populated. - I5 — byte-stable contract. Key order is fixed; the exporters build objects by hand rather than routing through
zod, so output is deterministic. - I6 — facts, not rankings.
comparecarries a factualdeltaline only — never a better/worse field.
JSON
aireceipts <selector> --json prints one receiptJsonSchema object. aireceipts compare <a> <b> --json prints one compareJsonSchema object.
Root object (receiptJsonSchema)
| Field | Type | Notes |
|---|---|---|
schemaVersion | number (literal 1) | This schema's major version. Bumped only on a breaking change. |
agentLabel | string | Human label for the agent, e.g. "Claude Code". |
source | enum | One of claude-code, codex, cursor, gemini, opencode. |
sessionId | string | Adapter-local id (an absolute file path for file-based adapters). |
title | string | null | Session title, or null when the adapter reports none. |
startedAtMs | number | null | Session start, epoch milliseconds, or null. |
durationMs | number | null | Wall-clock duration in milliseconds, or null. |
unpriceable | boolean | True for degraded sources (Cursor) with no per-turn usage/model. |
modelMix | array | Per-model token breakdown; see ModelMix entry. Empty when no turn carries a model. |
toolRows | array | Per-tool cost/token rows; see ToolRow. |
totalUsd | number | null | Total attributed cost, or null when nothing priced (I2). |
totalTokens | TokenUsage | Attributed token totals. |
sessionTotalTokens | TokenUsage | Adapter-reported session totals (the only real number for Cursor). |
wasteLines | array | Fired waste findings; see WasteLine. |
caveats | array | Confidence facts, never a ranking (SPEC-0028 time-integrity; SPEC-0044 A3 cost lower bound): kind (time-mtime | time-span | cost-lower-bound-cache-tier | dropped-transcript-records | partial-priced-coverage | subagents-unreadable | subagents-unpriced | subagents-priced-tokens-only | subagents-dropped-records) + text. Never affects $ itself. Empty when nothing to flag. |
budget | array (optional) | Advisory budget lines (SPEC-0009); present only when ~/.aireceipts/budget.json is configured. |
priceDelta | PriceDelta | null | Cheapest-current-model arithmetic, or null in tokens-only mode. |
methodology | string | The attribution methodology string (I3). |
priceRowsUsed | array | Every dated price row consulted; see PriceRowUsed. |
costShape | CostShape | SPEC-0067 — cost-shape facts (standalone, never in savings math): preEdit (pre-edit cost/token share), topTurns (expensive-turn concentration, or null), lateTurn (neutral late-half/early-half cost ratio, low confidence, or null). |
sameFileReReads | SameFileReReads | null | SPEC-0068 — same-file re-reads diagnostic (standalone, low confidence, NEVER a waste row or savings claim); null when none. |
subagents | Subagents (optional) | SPEC-0061 — the session's subagent (child-transcript) rollup; present only when children were discovered. Aggregate only — never child ids, titles, or paths. |
Subagents object
| Field | Type | Notes |
|---|---|---|
count | number | Every discovered child, readable or not. |
pricedUsd | number | null | Sum over priced children; null when no child priced (I2 — surfaces render tokens). |
tokensTotal | number | Total tokens across readable children; unreadable children contribute nothing (counted, never guessed). |
unpricedCount | number | Readable children with no matching price row. |
unreadableCount | number | Children whose transcripts failed to parse — the rendered TOTAL is a floor. |
CostShape object
SPEC-0067 cost-shape facts — standalone, never in savings math. preEdit always present; topTurns and lateTurn are null unless every usage-bearing turn is priced.
| Field | Type | Notes |
|---|---|---|
preEdit | object | The pre-edit cost/token split (below). |
preEditUsd | number | null | Priced cost before the first named edit turn; null unless all pre-edit turns priced (I2). |
postEditUsd | number | null | Priced cost from the first named edit turn onward; null unless all priced. |
preEditPct | number | null | preEditUsd / totalUsd percent; null unless every usage-bearing turn is priced (never a ratio over a partial denominator). |
preEditTokenPct | number | The same split in tokens (always present). |
firstEditTurn | number | null | 1-based turn of the first named edit tool, or null when none observed. |
confidence | string | high for preEdit/topTurns, low for lateTurn. |
topTurns | object | null | Expensive-turn concentration: sharePct and 1-based indices of the top-3 priced turns. |
sharePct | number | Share of priced cost in the top-3 turns. |
indices | array | 1-based turn numbers of the top-3 turns, ascending. |
lateTurn | object | null | lateRatio: a neutral late-half/early-half average-cost ratio (low confidence; never a "context growth" cause). |
lateRatio | number | Second-half average turn cost / first-half average. |
SameFileReReads object
SPEC-0068 — same-FILE re-reads (same normalized path, any range) with no recorded edit, compaction, or matching shell command between them. A neutral low-confidence diagnostic; it is not a waste row and never contributes to a "could have saved" figure.
| Field | Type | Notes |
|---|---|---|
count | number | No-recorded-cause re-reads (2nd..Nth reads of a path). |
turnIndices | array | 1-/0-based transcript turn indices of the counted re-reads, ascending. |
tokens | TokenUsage | Per-call token share of the counted re-reads. |
usd | number | null | Priced share; null if any counted re-read is unpriced (I2). |
confidence | string | Always low — the transcript cannot prove a re-read was unnecessary. |
TokenUsage object
| Field | Type | Notes |
|---|---|---|
input | number | Input tokens. |
output | number | Output tokens. |
cacheRead | number | Tokens served from the prompt cache. |
cacheCreation | number | Tokens written to the prompt cache (all TTL tiers). |
cacheCreation5m | number | null | Subset billed at the 5-minute tier, or null when the transcript doesn't split it. |
cacheCreation1h | number | null | Subset billed at the 1-hour tier, or null when unsplit. |
total | number | Sum of all token classes. |
ModelMix entry
| Field | Type | Notes |
|---|---|---|
model | string | Model id that served these tokens. |
tokens | TokenUsage | Tokens attributed to this model. |
tokenShare | number | 0..1 share of the session's total tokens. |
ToolRow
| Field | Type | Notes |
|---|---|---|
tool | string | Tool name, or "(thinking/reply)" for tool-free turns. |
usd | number | null | Cost attributed to this tool, or null when its turns never priced (I2). |
callCount | number | Number of calls to this tool. |
Caveat (SPEC-0028 time-integrity; SPEC-0044 A3 cost lower bound)
| Field | Type | Meaning |
|---|---|---|
kind | enum | time-mtime (a turn timestamp postdates the transcript file), time-span (non-positive span carrying usage), or cost-lower-bound-cache-tier (a priced turn's cache-write fell back to the base input rate because the vendor's price row cites no cache-write rate — row-aware, not just "unsplit": an unsplit write against a row that cites the 5m rate, e.g. Anthropic, is priced exactly and never sets this). |
text | string | The rendered caveat line, verbatim. |
WasteLine (discriminated on kind)
| Field | Type | Notes |
|---|---|---|
kind | enum | stuck-loop, trivial-spans, or context-thrash. |
runLength | number | (stuck-loop) Consecutive identical calls. |
wallClockMs | number | null | (stuck-loop) Wall-clock spent in the loop, or null. |
eligibleTurnCount | number | (trivial-spans) Turns that could have used a cheaper model. |
cheaperModel | string | (trivial-spans) The cheaper model the arithmetic used. |
compactionCount | number | (context-thrash) Refill-positive compactions clustered in the window. |
turnSpan | number | (context-thrash) Assistant-turn span from the window's first to last compaction. |
turnIndices | array | (context-thrash) The contributing post-compaction turn indices (the cost basis). |
Each variant also carries a tool/usd/tokens field as documented above. The context-thrash variant omits tool, carries a nullable usd (tokens-only when any contributing turn is unpriced, I2), and reports prompt-only tokens.
PriceDelta
| Field | Type | Notes |
|---|---|---|
actualUsd | number | The session's real attributed cost. |
Also carries cheaperModel (the cheapest current model) and usd (the re-priced total).
PriceRowUsed
| Field | Type | Notes |
|---|---|---|
vendor | string | Price-table vendor. |
input_cached | number | null | Cache-hit rate (USD per MTok), or null when the row cites none. |
input_cache_write_5m | number | null | 5-minute cache-write rate, or null. |
input_cache_write_1h | number | null | 1-hour cache-write rate, or null. |
from_date | string | ISO date the row takes effect. |
to_date | string | null | ISO date the row expires, or null when still current. |
sources | array | Cited price sources; see PriceSource. |
Also carries model, input, and output (rates in USD per MTok) as documented above.
PriceSource
| Field | Type | Notes |
|---|---|---|
url | string | Source URL. |
observed_at | string | null | ISO date the price was observed, or null. |
excerpt | string | null | Cited excerpt, or null. |
Compare envelope (compareJsonSchema)
| Field | Type | Notes |
|---|---|---|
a | Root body | First session's receipt body (Root object minus schemaVersion). |
b | Root body | Second session's receipt body. |
delta | string | Factual delta line — a cost/token ratio, never a ranking (I6). |
compare also carries schemaVersion on its root.
Handoff envelope (handoffJsonSchema) — SPEC-0042
aireceipts --handoff <selector> --json: the machine-readable resume packet. Always emits the full structure (empty arrays included). The attribution-only privacy fields (cwd, gitBranch, sidechain linkage) are structurally absent, same as every export.
| Field | Type | Notes |
|---|---|---|
schemaVersion | number | Same envelope as receipt/compare. |
source | string | Agent source enum. |
sessionId | string | Adapter-local session id. |
title | string | null | Session title when known. |
startedAtMs | number | null | Session start, epoch ms. |
durationMs | number | null | Wall-clock span. |
totals | object | tokens (TokenUsage object) + turnCount + toolCallCount. |
turnCount | number | (totals) Assistant turns in the session. |
toolCallCount | number | (totals) Tool calls in the session. |
wasteLines | array | Same WasteLine union as the receipt, plus a per-line rule (SPEC-0059). |
rule | string | null | (wasteLines) The class's fixed one-line next-time rule; null for a class without one. |
couldHaveSaved | object | SPEC-0059: the extracted savings ceiling — usd (sum of priced waste lines, null when none priced), tokens (sum over all fired lines), pctOfTotal. Arithmetic, never a prediction. |
pctOfTotal | number | null | (couldHaveSaved) round(100 · usd / totalUsd); null without both dollar sides. |
suggestions | array | Standing-rule suggestion strings (SPEC-0013), possibly empty. |
threshold | number | The distinct-session recurrence threshold in effect. |
coverage | object | What the packet covers, checkably: turns, toolCalls, compactions, wasteLines (all numbers). |
turns | number | (coverage) Turn count the packet covers. |
toolCalls | number | (coverage) Tool-call count the packet covers. |
compactions | number | (coverage) Compaction events in the session. |
aggregates | array | {class, distinctSessionCount} — exactly the waste classes that fired in the trailing recurrence window, below-threshold classes included (inspectable, not silent). |
class | string | (aggregates) Waste class name. |
distinctSessionCount | number | (aggregates) Distinct recent sessions the class fired in. |
Backfill envelope (backfillJsonSchema) — SPEC-0056
aireceipts backfill --json: the bulk-sweep summary. Counts are honest per SPEC-0045 — degraded/unloadable sessions are counted in loadFailureCount, never silently dropped. sessions is one row per matched session (after --since/--limit), newest-first; rows also carry source, sessionId, title, startedAtMs with the same meanings as the handoff envelope above.
| Field | Type | Notes |
|---|---|---|
schemaVersion | number | Same envelope as receipt/compare/handoff. |
discoveredCount | number | Every discovered session summary, degraded ones included. |
matchedCount | number | After the --since/--limit filters. |
loadFailureCount | number | Honest per SPEC-0045, mode-dependent: on an --out run (loads attempted), degraded summaries plus failed loads; on a dry run only known-unreadable summaries — a lower bound (labelled Known unreadable in the text summary). |
writtenCount | number | Receipt files written this run; 0 without --out. |
wroteFiles | boolean | true only when --out wrote files. |
sessions | array | One row per matched session, newest-first. |
fileName | string | null | (sessions) File written under --out, or null (dry run / load failed). |
loadFailed | boolean | (sessions) true when the session is known unreadable or (on an --out run) its load failed. |
CSV
CSV is a flat projection of the same model for spreadsheets. $ cells are an empty string when unpriced (never 0); token cells are always populated. RFC 4180 quoting is applied (a cell containing ", ,, CR, or LF is quoted, embedded quotes doubled). Records are LF-terminated. Columns are additive-only within a schema major version — never reordered or removed. Every row's first column is schemaVersion.
--csv=session (default) and compare --csv
One summary row per session. Columns:
schemaVersion, sessionId, agent, title, startedAt, durationMs, primaryModel, totalUsd, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, totalTokens
compare --csv emits exactly two such rows plus a trailing delta column carrying the factual delta line on the first row (empty on the second) — compare accepts --csv=session only.
--csv=tool
One row per tool line. Columns:
schemaVersion, sessionId, agent, tool, usd, inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens, totalTokens, callCount
Versioning (semver discipline, R4)
- Any breaking change to a JSON shape (renamed/removed field, changed type or meaning) bumps
SCHEMA_VERSION, and this document must be updated in the same change or the parity test fails the build. - Additive changes (a new field, a new CSV column appended) do not bump the version.
- CSV columns are additive-only within a major version.
Weekly digest (SPEC-0008 integration point)
week --json (SPEC-0008's weekly digest) is implemented (weekToJson in src/receipt/week.ts) and emits a {window, priorWindow, sinceOverride, byProject, current, prior, delta, topWaste} digest (waste lines are under topWaste). It does not yet carry a schemaVersion wrapper or a weekJsonSchema in exportSchema.ts, so it is not covered by the field-parity test — a known gap tracked for a follow-up: it MUST gain the same schemaVersion constant (from src/receipt/exportSchema.ts) and a weekJsonSchema documented inside the json-fields markers above, rather than a second undocumented shape (R5, single-source-of-truth). Until then, treat week --json as an unversioned convenience surface, not part of the versioned contract.