Version 0.1.0, beta. An independent open-source project. It is not affiliated with, endorsed by, or supported by Anthropic. "Claude" is a trademark of Anthropic, used here only to say which product's usage data the tool reads.
Everything runs on your machine. No data leaves it. There is no hosted service, no telemetry, and no uploads. All analysis is local: your usage data is read, priced and reported without ever being sent anywhere.
The tool makes exactly one kind of network request, and only if you ask for it:
fetch calls one of Anthropic's own reporting APIs -- the Usage & Cost Admin
API or the Claude Enterprise Analytics API -- to download your data. It sends
nothing but the request. Everything else, including every CSV path, touches no
network at all.
The dependency list is empty, so you can verify all of that yourself in about five minutes rather than taking it on trust.
A CLI that reads Anthropic API usage and Claude plan usage data and reports where the money is going, with dollar figures you can trace to a file you control.
See examples/sample-report.md for what the output
looks like. It comes from a synthetic month that ships with the repo, so you can
reproduce it before pointing anything at your own data:
claude-spend-audit audit examples/sample-usage.csv --cost examples/sample-cost.csvThe organisation in it is invented, and the numbers are chosen so that every check has something to say.
For Claude Enterprise, there is an invented month in both Enterprise shapes:
examples/sample-enterprise-report.md
comes from a saved Analytics API fetch, and
examples/sample-enterprise-spend-report.md
from the same month as a spend report download. To run them yourself:
claude-spend-audit audit examples/sample-enterprise-analytics.jsonclaude-spend-audit audit examples/sample-enterprise-spend.csvEvery input path was built from Anthropic's published documentation and tested only against synthetic data written to match it. No real export, and no real API response from a real account, has been run through this tool.
How much that matters depends on the source:
- The two API paths (
fetch,fetch --source analytics) use field names copied from Anthropic's published API references. They are the most likely to work as written. - The cookbook CSV profile copies its column names from the published source of Anthropic's own exporter.
- The Console CSV and Team/Enterprise spend-report CSV mappings are educated guesses. Anthropic does not publish those exports' column headers.
Check the billed total against your real bill before trusting any dollar figure. Cost units differ by source, and one of them is an assumption:
- The Cost API and the Enterprise Analytics cost reports document their amounts in cents (fractional cents for Analytics); they are divided by 100.
- The Console cost CSV is assumed to be in cents like the API it mirrors. This is a guess.
- The cookbook cost CSV and the spend report are read as dollars (their
columns are named
amount_usdand..._spend_usd).
If the "billed" line in the report is 100 times larger or smaller than the
figure in your Console or invoice, the unit assumption is wrong for your
export -- every cost in the report is off by the same factor. The report also
raises a critical caveat (and exits 1) when billed and estimated cost are
20x or more apart. Please report it.
Because the exact formats are unknown, some files are refused rather than guessed at. What you might see, and what to do:
| Refused or flagged | Why | What to do |
|---|---|---|
| A cost CSV that fits two profiles equally, one reading cents and one dollars | the file gives no evidence for either, and a wrong pick is 100x | pass --cost-profile schemas/console_cost_csv.json (cents) or schemas/cookbook_cost_csv.json (dollars) |
| Slash dates that could be either order (every day and month 12 or less) | 03/08/2026 could be March or August | pass --date-order mdy or --date-order dmy |
| Any currency other than USD | the tool reports dollars and converts nothing | export in USD |
Decimal commas (10,00, 1.200,50) |
stripping the comma would read 100x too large | re-export with . as the decimal point |
| A file that is not UTF-8 or UTF-16 | a guessed code page garbles names silently | save as "CSV UTF-8" |
| A missing token column (for example no cache-read column) | a missing column is not zero tokens | the checks that need it are skipped, with a caveat naming the column |
| Rows labelled "Total", per-user subtotals, rows with no date | counting them would double the figures | they are excluded and counted in a caveat |
If a file is refused or looks mis-read, run
claude-spend-audit describe YOUR_FILE. It prints the file's columns, how they
were mapped and the shape of their values -- never the values themselves --
and is what to paste into a
format mismatch issue.
Every path fails loudly rather than guessing. An export that does not match prints its real header row; an API response that does not make sense raises rather than returning partial data. If you hit a mismatch, please open an issue with the header row or the shape of the response -- never the data itself.
| Your account | Best source | Fallback | What you get |
|---|---|---|---|
| Developer Console (API) with an Admin API key | fetch (Usage & Cost Admin API) |
Console CSV export | every usage check |
| Developer Console, no Admin API key | Console CSV export | CSV from Anthropic's cookbook exporter | most usage checks, depending on the export's columns |
| Developer Console, Claude Code use by people or keys | fetch --source claude-code (Admin API key) |
none | per-person Claude Code use: K1-K3, E1, E3, E4, and C2, C5, C6a, C8 on API-billed use |
| Claude Enterprise | fetch --source analytics (Analytics API key) |
spend report CSV | most usage checks, C7r and E1-E4 |
| Claude Team | spend report CSV | none | E1-E4 only, descriptive |
| Claude Pro or Max (individual) | not supported | none | no usage export exists |
| Amazon Bedrock, Google Vertex AI, Claude Platform on AWS | not supported | none | this usage never reaches Anthropic's reporting APIs |
An organisation that uses both the Developer Console and Claude Enterprise can audit each separately: they are different accounts with different keys and different data.
- Usage & Cost Admin API and Claude Enterprise Analytics API. Documented field names, real time buckets (daily or hourly), all five token categories with the two cache-write durations kept apart, and cost data beside the usage. Everything the checks need, with the least guessing.
- Cookbook exporter CSV. Verified column names, but daily buckets only, no context-window column, and it adds the two cache-write durations together -- which blocks C1, the strongest check.
- Console CSV export. Potentially as detailed as the API, but its column names are guessed, and the cost file's units (cents or dollars) are assumed. Check the billed total against your invoice before trusting anything else.
- Team/Enterprise spend report CSV. One row per person per model, summed over the whole range: no time axis, no cache breakdown, mostly guessed column names. It supports a descriptive picture of where the money went and nothing stronger.
- Untested on real data -- see above.
- Check figures use list rates. Every dollar figure a check computes comes
from the newest rate card in
pricing/, currentlypricing/2026-10.json. Negotiated discounts and plan pricing are not reflected unless you edit that file. The Enterprise Analytics path shows your billed-to-list ratio in the header so you can scale mentally; billed totals read from cost data are always what was billed. - Some spend cannot be priced or seen. Priority and flex service tiers have no published rate and are counted but not priced. Priority Tier costs are missing from the cost data entirely. Bedrock, Vertex AI and Claude Platform on AWS traffic never appears. Each report says so where it applies.
- No request-level detail. Every source is aggregated. Nothing can tell ten long requests from a thousand short ones, and thinking tokens are billed as output without ever being broken out.
- Confidence levels mean what they say. Only a
measuredfinding is arithmetic on your data. Acandidateis a pattern the data cannot confirm and always carries a range with its assumptions. Anobservationmakes no savings claim at all -- and on the spend-report path, every finding is one.
With an Admin API key (best). Create one in the Console under Settings > Admin keys, then:
export ANTHROPIC_ADMIN_KEY=sk-ant-admin01-...claude-spend-audit fetch --from 2026-08-01 --to 2026-09-01 -o raw.jsonclaude-spend-audit audit raw.jsonfetch saves the raw responses and stops. Analysis then runs offline as many
times as you like, at any thresholds, without touching the API again -- and
the saved file is something you can read before deciding whether to share it.
Add --bucket-width 1h for the off-hours check (C6b), and fetch cost data
separately with --what cost to compare against billing.
fetch also looks up your workspace and API-key names, so findings read
Production [...a1b2] rather than a bare wrkspc_... ID. Only each name is
kept -- not the key hint, creator or anything else those lists return. Pass
--no-names to skip it, or --anonymize at audit time to show IDs instead
(key names often name people). If the key may not list workspaces, the fetch
still saves the usage and says so.
With a CSV export (no credentials). Export from the Console's Usage and Cost pages and run against the files:
claude-spend-audit audit usage.csv --cost cost.csvThe file is matched against every profile in schemas/ and the best match
wins; --explain-schema prints which column mapped to which field. CSVs from
Anthropic's cookbook exporter (observability/usage_cost_api.ipynb) are
recognised the same way.
Caveats for this account type:
- Console CSV column names are guessed (profiles marked
provisional). If an export does not parse, the error names the file to edit -- a JSON change, not a code change. - The Console cost CSV is assumed to be in cents, like the API. If its billed total looks 100 times too small or large, that assumption is wrong.
- The cookbook exporter adds the two cache-write durations together, so C1 cannot run on its output; the tool says so and prices those tokens at the cheaper rate, making the estimate a floor.
Claude Code usage (the same Admin API key). The Claude Code Analytics API reports each person's (or API key's) Claude Code use per day: sessions, accepted and rejected edits, tokens and Anthropic's estimated cost per model.
claude-spend-audit fetch --source claude-code --from 2026-08-01 --to 2026-09-01 -o claude-code.jsonclaude-spend-audit audit claude-code.jsonThe API answers one day per request, so a month is at least 31 requests.
Caveats for this source:
- Every dollar figure is Anthropic's estimate, not a bill. Claude Code use billed through the API is also in the Usage & Cost API data, so never add this audit's figures to that one's.
- Pro and Team subscribers appear with an API-equivalent estimate, although they pay for a seat. They are left out of the token checks and shown separately by K3.
- The API's input token count is assumed to exclude cached tokens, as the Messages API reports it. Each audit checks that by pricing the tokens itself and comparing with Anthropic's estimate; a gap over 10% raises a caveat.
- Cache writes come without their duration, so C1 cannot run.
- The file holds users' email addresses.
--anonymizereplaces them in the output withuser-001..., consistently across every section. - Claude Code use by people on a Claude Enterprise plan is reported by the
Enterprise Analytics API instead (
fetch --source analytics), under theclaude_codeproduct.
With an Analytics API key (best). Only the Primary Owner can create one, in
claude.ai under Organization settings > API; it carries the read:analytics
scope. An Admin API key does not work here.
export ANTHROPIC_ANALYTICS_KEY=...claude-spend-audit fetch --source analytics --from 2026-08-01 --to 2026-09-01 -o raw.jsonclaude-spend-audit audit raw.jsonOne fetch saves every series the audit needs into one file: usage by model,
product, context window, region and speed; an ungrouped total to cross-check
it; billed and list-price cost; spend by RBAC group; and per-user usage and
cost. Pass --no-users to leave the per-user reports -- and every email
address -- out of the file, and --anonymize at audit time to replace emails
with user-001, user-002... in the report.
Caveats for this source:
- Check figures are at list price. The header shows billed, list price and the ratio between them; actual spend on a finding is likely lower by roughly that ratio, though the discount need not be uniform.
- Recent data moves. Figures refresh about every four hours and can be revised for 30 days. The report flags a range that runs past the latest refresh (which also sets exit code 1) or into the revision window.
- Grouped results can be cut short. The API keeps at most 100 groups per time bucket, with no remainder row. The tool cross-checks against the ungrouped total and flags any shortfall, with exit code 1.
- RBAC groups overlap. A user in two groups counts in both, so group rows are never added up; C7r uses only the row for users in no group.
- Data starts on 2026-01-01, and each request covers at most 31 days (the tool splits longer ranges itself). Plan products manage caching themselves, so cache remedies may not be available to you even where C1 or C2 measures a problem. Per-user figures cover seat users only.
With the spend report (fallback). See Claude Team below; it works the same.
Team plans have no reporting API, so the source is the spend report: Settings > Analytics > "Export spend report". Run the same command as for any other file:
claude-spend-audit audit spend-report.csvclaude-spend-audit audit spend-report.csv --format markdown --anonymize -o overview.mdThe file is recognised by its columns and routed to its own checks, E1-E4.
Caveats for this source:
- Descriptive only. It is one row per person per model, summed over the
range you exported, so it shows where the money went but cannot support a
savings claim. Every finding is an
observation, and the tool refuses to produce anything else from this data. - The column mapping is a guess. Anthropic documents five of the report's
column names and describes the rest in prose, so
schemas/enterprise_spend_csv.jsoncarries many candidate spellings per field. A file with a totals row has that row excluded and reported. - No dates means no spike or off-hours checks; no cache breakdown means no cache checks; and whether its prompt-token figure includes cache reads is not documented.
| check | confidence | |
|---|---|---|
| C1 | Cache writes not read back often enough to pay for themselves | measured |
| C2 | High uncached input with almost no cache reads | candidate |
| C3 | Standard-tier spend that could take the 50% batch discount | candidate |
| C4 | Long-context composition, and what a token removed there is worth | observation |
| C5 | Segments where output is most of the bill | observation |
| C6a | Daily spend spikes above the recent baseline | measured |
| C6b | Spend falling outside business hours | measured |
| C7 | Spend that cannot be allocated to a named workspace or key | observation |
| C7r | Spend from users in no RBAC group (Claude Enterprise) | observation |
| C8 | Model mix across frontier, mid and small models | observation |
| E1 | How concentrated spend is across users | observation |
| E2 | Spend by Claude product (chat, Claude Code, Cowork...) | observation |
| E3 | Model mix, from per-user data | observation |
| E4 | Product/model groups where completion tokens are a large share | observation |
| K1 | How often Claude Code's proposed edits were accepted, and who rejects most | observation |
| K2 | Claude Code days with unusually costly sessions | observation |
| K3 | Claude Code use by billing type: API vs Pro/Team subscription | observation |
Which checks can run depends on the source. A check the data cannot support is skipped, and the report names it and says why.
| check | Admin API | Console CSV | Cookbook CSV | Enterprise Analytics | Spend report | Claude Code |
|---|---|---|---|---|---|---|
| C1 | yes | if the export splits cache writes | no: cache writes summed | yes | no | no: cache writes summed |
| C2 | yes | if it has a cache read column | yes | yes | no | yes, API-billed use |
| C3 | yes | if it has a service tier column | yes | no: no service tier | no | no |
| C4 | yes | if it has a context window column | no | yes | no | no |
| C5 | yes | if it has a cache read column | yes | yes | no (E4 instead) | yes, API-billed use |
| C6a | yes, 8+ days | yes, 8+ days | yes, 8+ days | yes, 8+ days | no | yes, 8+ days |
| C6b | with --bucket-width 1h |
if exported hourly | no: daily only | with --bucket-width 1h |
no | no: daily only |
| C7 | yes | if it has a workspace column | yes | no (C7r instead) | no | no |
| C7r | no | no | no | yes | no | no |
| C8 | yes | yes | yes | yes | no (E3 instead) | yes, API-billed use |
| E1-E4 | no | no | no | yes, unless --no-users |
yes, where columns allow | E1, E3, E4 |
| K1-K3 | no | no | no | no | no | yes |
A missing token column is not read as zero: on a CSV without a cache read column, C1, C2 and C5 are skipped with a caveat naming the column.
Python 3.11 or newer. No runtime dependencies.
git clone https://github.com/TiagoCK/TokenOptimization.gitcd TokenOptimizationInstall into a virtual environment. Activating it puts the
claude-spend-audit command on your PATH for that terminal, so there is
nothing to add to PATH by hand:
python -m venv .venvsource .venv/bin/activateOn Windows, activate with .venv\Scripts\activate instead (and use py in
place of python if python is not found). Then:
python -m pip install -e .claude-spend-audit --helpActivate the environment again in each new terminal before using the command.
If the command is not found -- for example after installing without a virtual environment, where pip may put it in a scripts folder that is not on your PATH and warn you so -- run the tool through Python instead. It works the same way everywhere:
python -m claude_spend_audit --helpThe command works from any folder, because an editable install keeps reading
pricing/ and schemas/ from this checkout. Don't move or delete the checkout
after installing.
Install editable, or run from a checkout. The rate card (pricing/) and the CSV
schema profiles (schemas/) deliberately live at the repo root rather than
inside the package, because both are meant to be read and edited — that is the
whole point of the rates being traceable. The trade-off is that a plain
pip install . leaves them behind; if you need that, pass --pricing and
--usage-profile explicitly. Either way the tool refuses to run rather than
falling back on rates it cannot show you.
audit runs the checks and prints ranked findings. inspect does the same
loading but runs nothing, reporting only what the data covers and which checks
it can support. For the shareable version, add --format markdown -o report.md.
Useful flags:
describe FILE— a command of its own: the file's format with none of its data, for reporting a mismatch--explain-schema— print exactly which CSV column mapped to which field--date-order mdy|dmy— how to read slash dates when the file cannot settle it; only needed if the tool asks--cost-profile/--usage-profile— force a schema profile, for example when a cost file's cents-or-dollars reading is ambiguous--json— machine-readable output--anonymize— replace user emails withuser-001... in per-user data, and show workspace and API-key IDs instead of their names--pricing pricing/2026-09.json— use a specific rate card (the default is the newest file inpricing/)--utc-offset -5— set your local business day for the off-hours check--business-hours 08:00-17:00— set the window it measures against--min-impact 50— drop findings below a dollar threshold--cache-prefix-share,--batch-eligible-share— narrow C2's and C3's savings ranges from your own knowledge of the workload. Both default to a floor of $0, because guessing them would put an invented number in the report.
For fetch: --source admin|analytics, --from / --to (UTC dates, end
exclusive), --bucket-width 1d|1h|1m, --group-by, --no-users,
--no-names, and
--api-key-env to read the key from a different variable. The key is only
ever read from an environment variable. There is no --api-key flag,
deliberately: an argument lands in shell history and is visible to every user
on the machine through ps.
Exit code is 1 when something in the data means the headline total understates
the real bill, so a script wrapping this cannot mistake a caveated number for a
clean one.
Findings are ranked by confidence first, dollars second, and two different
numbers are kept apart throughout: measured is what was spent and is
arithmetic; recoverable is what could actually be saved and is often
unknowable from aggregate data. C6 can measure that $40,000 was spent overnight
without being able to say whether any of it was avoidable, so it reports the
first and prints not claimed for the second. Within a confidence level, a
finding that claims a saving comes before one that only measures spend.
Every rate the tool uses is in a rate card under pricing/ -- by default the
newest, pricing/2026-10.json --
with the source URL and fetch date recorded, and each entry marked either as
published or with the derivation that produced it. If a rate is wrong for your
organisation, edit that file and every figure moves with it.
ARCHITECTURE.md explains the confidence model in full,
along with the invariants that keep the numbers honest and how to verify any
figure by hand.
python -m pytest -qAdding a check is described in ARCHITECTURE.md; it never
requires touching a renderer. After changing anything that affects output,
regenerate the committed sample:
python -m tests.generate_sampleEvery check is a pure function of the normalized data, and every dollar figure in the tests is hand-calculated in a comment beside it, so a changed formula fails a test rather than quietly changing someone's number.
MIT