Cost Claude Foundations

Session Cost Report Script

Price your Claude Code transcripts at real API rates, and the six rules that decide whether the number is right

17 of 66

What it does

Claude Code writes a JSONL transcript for every session under ~/.claude/projects/, and every assistant turn in it carries a usage object: input, output, thinking, cache writes split by TTL, and cache reads. That is enough to price the work at published API rates without an account, an API key, or a network call.

Releve is that, as three Python scripts and a dashboard. The scripts are standard library only.

Download

# one number
curl -O https://releve.neorgon.com/scripts/releve-mini.py
python3 releve-mini.py --days 30

# the full scan, which writes the file the dashboard loads
curl -O https://releve.neorgon.com/scripts/releve-scan.py
curl -O https://releve.neorgon.com/scripts/releve_cost.py
python3 releve-scan.py --days 30 --out releve.json

Requires Python 3.8+. No external dependencies. Each script prints the date of the rate card it used.

Basic usage

python3 releve-mini.py --days 7        # last week
python3 releve-mini.py --days 1        # today only
Releve  ·  7 days  ·  22 sessions  ·  13,621 turns
API-equivalent   $2,659.18
  excluded       113 turns, never billed
rates as of 2026-08-27. A counterfactual at list rates, not an invoice.

The six rules

Reading a transcript naively is wrong in six specific ways. Each of these is a real defect the first attempt shipped.

Rule
1Resolve the rate per turn, from that turn's own message.model. A session that switched models is not priced by whichever model appears last in the file.
2Never substitute another model's rates. Exact id, then longest declared prefix, then *unpriced*. An unpriced model is named and counted in its own bucket, never folded into a total and never rendered as zero.
3Split the cache writes. A 5-minute write bills at 1.25x base input, a 1-hour write at 2x. A turn carrying only the flat counter is an assumption, and should be flagged as one.
4Honour usage.speed. Fast mode bills double, so a fast session must not be priced as standard.
5A non-empty usage.iterations replaces the top-level counters rather than adding to them. Adding double-counts; ignoring it under-reports every multi-iteration turn.
6Count each response once, keyed on message.id. A response is written one entry per content block and every entry repeats the same usage, so a turn that thought, spoke and called a tool appears three times and is billed once.

Rule 6 decides whether the answer is right or roughly triple. On one real machine, 116,402 transcript entries are 52,078 responses, and counting entries reports 30.0B cache-read tokens against a true 12.9B.

Rule 3 is the one people skip, and the cache is usually most of the bill: cache reads at 0.1x base input are cheap per token and enormous in aggregate.

The whole tree, not one directory deep

Transcripts nest three ways, and a single-level glob misses the third:

~/.claude/projects/<project>/*.jsonl
~/.claude/projects/<project>/<session>/*.jsonl
~/.claude/projects/<project>/<session>/subagents/agent-*.jsonl

Subagent spend lives in that last tier. releve-scan.py walks all three; --no-subagents excludes them deliberately, which is different from missing them.

Budget alerts

--budget exits 1 when the window is over the threshold, so it works as a cron guard:

python3 releve-scan.py --days 7 --budget 100 --quiet

--notify sends a desktop notification with the total (macOS osascript, Linux notify-send).

Automate with cron

crontab -e
0 9 * * * /usr/bin/python3 /path/to/releve-scan.py --days 7 --budget 100 --notify --quiet

On macOS, launchd survives reboots. Create ~/Library/LaunchAgents/com.releve.costs.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.releve.costs</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/bin/python3</string>
    <string>/path/to/releve-scan.py</string>
    <string>--days</string>
    <string>7</string>
    <string>--budget</string>
    <string>100</string>
    <string>--notify</string>
    <string>--quiet</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key>
    <integer>9</integer>
    <key>Minute</key>
    <integer>0</integer>
  </dict>
</dict>
</plist>

Load it:

launchctl load ~/Library/LaunchAgents/com.releve.costs.plist

Reading the breakdown

releve-scan.py --out releve.json writes aggregates only, no prompt or response text. Drop that file on releve.neorgon.com and the same total is cut by model, project, skill, effort, git branch, main thread against subagent, service tier and Claude Code version. Nothing is uploaded: the file is read in the browser.

Add --anonymize before sharing one. It hashes project labels and drops cwd, gitBranch, title and slug.

The rate card is editable on the page, so you can reprice the whole window against a rate you have verified yourself rather than trusting the one shipped.

Limitations

  • Not an invoice. Every figure is tokens multiplied by a published list rate. Nothing here reads a bill or the Admin API.
  • Subscription users see what the work would have cost through the API. The plan number is whatever you type in.
  • Bedrock and Vertex rates differ and are left null rather than guessed. A wrong rate presented confidently is worse than a stated gap.
  • Web search (\$10 per 1,000 searches) and code execution time are not counted.
  • Thinking tokens are already inside output_tokens. Report them as a label, never add them to cost.

Working out which model to run this on? See The Codex. Packaging it as a reusable skill? See The Armory.