Docs ledger

Export schema (--json / --csv)

Rendered from docs/json-schema.md.

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/actualUsd field is null (JSON) or an empty cell (CSV) whenever nothing priced; it is never 0 standing 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. compare carries a factual delta line 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)

FieldTypeNotes
schemaVersionnumber (literal 1)This schema's major version. Bumped only on a breaking change.
agentLabelstringHuman label for the agent, e.g. "Claude Code".
sourceenumOne of claude-code, codex, cursor, gemini, opencode.
sessionIdstringAdapter-local id (an absolute file path for file-based adapters).
titlestring | nullSession title, or null when the adapter reports none.
startedAtMsnumber | nullSession start, epoch milliseconds, or null.
durationMsnumber | nullWall-clock duration in milliseconds, or null.
unpriceablebooleanTrue for degraded sources (Cursor) with no per-turn usage/model.
modelMixarrayPer-model token breakdown; see ModelMix entry. Empty when no turn carries a model.
toolRowsarrayPer-tool cost/token rows; see ToolRow.
totalUsdnumber | nullTotal attributed cost, or null when nothing priced (I2).
totalTokensTokenUsageAttributed token totals.
sessionTotalTokensTokenUsageAdapter-reported session totals (the only real number for Cursor).
wasteLinesarrayFired waste findings; see WasteLine.
caveatsarrayConfidence 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.
budgetarray (optional)Advisory budget lines (SPEC-0009); present only when ~/.aireceipts/budget.json is configured.
priceDeltaPriceDelta | nullCheapest-current-model arithmetic, or null in tokens-only mode.
methodologystringThe attribution methodology string (I3).
priceRowsUsedarrayEvery dated price row consulted; see PriceRowUsed.
costShapeCostShapeSPEC-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).
sameFileReReadsSameFileReReads | nullSPEC-0068 — same-file re-reads diagnostic (standalone, low confidence, NEVER a waste row or savings claim); null when none.
subagentsSubagents (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

FieldTypeNotes
countnumberEvery discovered child, readable or not.
pricedUsdnumber | nullSum over priced children; null when no child priced (I2 — surfaces render tokens).
tokensTotalnumberTotal tokens across readable children; unreadable children contribute nothing (counted, never guessed).
unpricedCountnumberReadable children with no matching price row.
unreadableCountnumberChildren 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.

FieldTypeNotes
preEditobjectThe pre-edit cost/token split (below).
preEditUsdnumber | nullPriced cost before the first named edit turn; null unless all pre-edit turns priced (I2).
postEditUsdnumber | nullPriced cost from the first named edit turn onward; null unless all priced.
preEditPctnumber | nullpreEditUsd / totalUsd percent; null unless every usage-bearing turn is priced (never a ratio over a partial denominator).
preEditTokenPctnumberThe same split in tokens (always present).
firstEditTurnnumber | null1-based turn of the first named edit tool, or null when none observed.
confidencestringhigh for preEdit/topTurns, low for lateTurn.
topTurnsobject | nullExpensive-turn concentration: sharePct and 1-based indices of the top-3 priced turns.
sharePctnumberShare of priced cost in the top-3 turns.
indicesarray1-based turn numbers of the top-3 turns, ascending.
lateTurnobject | nulllateRatio: a neutral late-half/early-half average-cost ratio (low confidence; never a "context growth" cause).
lateRationumberSecond-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.

FieldTypeNotes
countnumberNo-recorded-cause re-reads (2nd..Nth reads of a path).
turnIndicesarray1-/0-based transcript turn indices of the counted re-reads, ascending.
tokensTokenUsagePer-call token share of the counted re-reads.
usdnumber | nullPriced share; null if any counted re-read is unpriced (I2).
confidencestringAlways low — the transcript cannot prove a re-read was unnecessary.

TokenUsage object

FieldTypeNotes
inputnumberInput tokens.
outputnumberOutput tokens.
cacheReadnumberTokens served from the prompt cache.
cacheCreationnumberTokens written to the prompt cache (all TTL tiers).
cacheCreation5mnumber | nullSubset billed at the 5-minute tier, or null when the transcript doesn't split it.
cacheCreation1hnumber | nullSubset billed at the 1-hour tier, or null when unsplit.
totalnumberSum of all token classes.

ModelMix entry

FieldTypeNotes
modelstringModel id that served these tokens.
tokensTokenUsageTokens attributed to this model.
tokenSharenumber0..1 share of the session's total tokens.

ToolRow

FieldTypeNotes
toolstringTool name, or "(thinking/reply)" for tool-free turns.
usdnumber | nullCost attributed to this tool, or null when its turns never priced (I2).
callCountnumberNumber of calls to this tool.

Caveat (SPEC-0028 time-integrity; SPEC-0044 A3 cost lower bound)

FieldTypeMeaning
kindenumtime-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).
textstringThe rendered caveat line, verbatim.

WasteLine (discriminated on kind)

FieldTypeNotes
kindenumstuck-loop, trivial-spans, or context-thrash.
runLengthnumber(stuck-loop) Consecutive identical calls.
wallClockMsnumber | null(stuck-loop) Wall-clock spent in the loop, or null.
eligibleTurnCountnumber(trivial-spans) Turns that could have used a cheaper model.
cheaperModelstring(trivial-spans) The cheaper model the arithmetic used.
compactionCountnumber(context-thrash) Refill-positive compactions clustered in the window.
turnSpannumber(context-thrash) Assistant-turn span from the window's first to last compaction.
turnIndicesarray(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

FieldTypeNotes
actualUsdnumberThe session's real attributed cost.

Also carries cheaperModel (the cheapest current model) and usd (the re-priced total).

PriceRowUsed

FieldTypeNotes
vendorstringPrice-table vendor.
input_cachednumber | nullCache-hit rate (USD per MTok), or null when the row cites none.
input_cache_write_5mnumber | null5-minute cache-write rate, or null.
input_cache_write_1hnumber | null1-hour cache-write rate, or null.
from_datestringISO date the row takes effect.
to_datestring | nullISO date the row expires, or null when still current.
sourcesarrayCited price sources; see PriceSource.

Also carries model, input, and output (rates in USD per MTok) as documented above.

PriceSource

FieldTypeNotes
urlstringSource URL.
observed_atstring | nullISO date the price was observed, or null.
excerptstring | nullCited excerpt, or null.

Compare envelope (compareJsonSchema)

FieldTypeNotes
aRoot bodyFirst session's receipt body (Root object minus schemaVersion).
bRoot bodySecond session's receipt body.
deltastringFactual 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.

FieldTypeNotes
schemaVersionnumberSame envelope as receipt/compare.
sourcestringAgent source enum.
sessionIdstringAdapter-local session id.
titlestring | nullSession title when known.
startedAtMsnumber | nullSession start, epoch ms.
durationMsnumber | nullWall-clock span.
totalsobjecttokens (TokenUsage object) + turnCount + toolCallCount.
turnCountnumber(totals) Assistant turns in the session.
toolCallCountnumber(totals) Tool calls in the session.
wasteLinesarraySame WasteLine union as the receipt, plus a per-line rule (SPEC-0059).
rulestring | null(wasteLines) The class's fixed one-line next-time rule; null for a class without one.
couldHaveSavedobjectSPEC-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.
pctOfTotalnumber | null(couldHaveSaved) round(100 · usd / totalUsd); null without both dollar sides.
suggestionsarrayStanding-rule suggestion strings (SPEC-0013), possibly empty.
thresholdnumberThe distinct-session recurrence threshold in effect.
coverageobjectWhat the packet covers, checkably: turns, toolCalls, compactions, wasteLines (all numbers).
turnsnumber(coverage) Turn count the packet covers.
toolCallsnumber(coverage) Tool-call count the packet covers.
compactionsnumber(coverage) Compaction events in the session.
aggregatesarray{class, distinctSessionCount} — exactly the waste classes that fired in the trailing recurrence window, below-threshold classes included (inspectable, not silent).
classstring(aggregates) Waste class name.
distinctSessionCountnumber(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.

FieldTypeNotes
schemaVersionnumberSame envelope as receipt/compare/handoff.
discoveredCountnumberEvery discovered session summary, degraded ones included.
matchedCountnumberAfter the --since/--limit filters.
loadFailureCountnumberHonest 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).
writtenCountnumberReceipt files written this run; 0 without --out.
wroteFilesbooleantrue only when --out wrote files.
sessionsarrayOne row per matched session, newest-first.
fileNamestring | null(sessions) File written under --out, or null (dry run / load failed).
loadFailedboolean(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.