Spend · Billing Guard CLI

Billing Guard CLI

A deterministic command-line audit of your Claude spend. It reads the Claude Code session telemetry already on your machine, pulls Anthropic's own Usage & Cost reports when you give it an Admin key, recomputes what each day should have cost from raw token counts, and flags the difference. Zero model tokens: detection is plain code, so running it costs nothing. See the Billing Guard overview for why this exists.

What it is

repoops-billing-audit is the portable shell around the Billing Guard reconciliation engine. One run:

  • Reads local Claude Code JSONL transcripts (default ~/.claude/projects, override with --claude-dir) and folds them into a day-by-model token ledger. Your ground truth; it never leaves the machine.
  • Pulls the Anthropic Admin Usage & Cost API for the same window when ANTHROPIC_ADMIN_KEY is set. The provider's claim.
  • Recomputes expected cost from token counts times a versioned rate card and compares inside a tolerance band (default 2%).
  • Prints a findings table (day, model, expected, billed, delta, signature) plus a coverage summary, and writes an evidence bundle JSON shaped for a finance dispute.

Exit codes make it automation-friendly: 0 everything reconciles, 1 findings, 2 configuration error.

Prerequisites

  • Node 20 or newer. Check with node --version.
  • Claude Code telemetry present. If you use Claude Code on this machine, transcripts already live under ~/.claude/projects. No setup needed.
  • Optional: a provider Admin or Cost key. If you pay per token (an Anthropic Console org, OpenAI, Bedrock, or Azure), set that provider's key to reconcile the audit against the real invoice. For Anthropic, create one at console.anthropic.com: Settings, then API keys, then Create Admin Key, and set it as ANTHROPIC_ADMIN_KEY in the environment. Without any provider key the CLI runs in telemetry-only mode: it prints the expected-cost ledger it computes locally, valued at current API rates, and reconciles nothing. If you are on a flat subscription such as Claude Pro, Max, or Team, there is no per-token bill to reconcile: read the total as the API-rate value of your usage, not an overcharge check. It never fakes a finding.

Install and run

One command, free. On the first run you sign in to RepoOps with a verified email (one browser approval); the CLI then downloads the audit engine on demand, verifies its checksum, and caches it locally, so later runs start instantly and keep working offline. The download itself is free: sign-in is the only gate, and there is no plan or payment requirement.

  1. Run it

    npx repoops-billing-audit --window 30
    First run: a browser window opens to approve the sign-in, then the engine downloads and the audit runs. Every run after that uses the cached engine.
  2. With verification enabled

    # PowerShell
    $env:ANTHROPIC_ADMIN_KEY = "sk-ant-admin01-..."
    npx repoops-billing-audit --window 30 --out billing-guard-report.json
    
    # bash / zsh
    ANTHROPIC_ADMIN_KEY=sk-ant-admin01-... npx repoops-billing-audit --window 30
  3. In CI or on a headless machine

    REPOOPS_AUTH_TOKEN=<token from an authenticated machine> npx repoops-billing-audit --json
    Setting REPOOPS_AUTH_TOKEN skips the browser entirely. Once cached, the CLI also runs offline: if repoops.ai is unreachable it uses the newest cached engine and says so.
  4. From a checkout (contributors)

    git clone https://github.com/sellingascode/repo-dashboard.git
    cd repo-dashboard
    npm install
    node bin/billing-guard.mjs --window 30

Flags:

  • --window <days> lookback window (default 30).
  • --tolerance <pct> tolerance band percent (default: the REPOOPS_BILLING_GUARD_TOLERANCE_PCT environment variable when set, else 2).
  • --measure-baseline print your measured baseline drift (per-day drift over fully-verified days, median/p90/max) plus a recommended tolerance band instead of the findings table. The recommendation is never auto-applied; adopt it via --tolerance or the environment variable.
  • --claude-dir <path> Claude home directory (default ~/.claude).
  • --out <path> evidence bundle path (default ./billing-guard-report.json).
  • --json print the evidence bundle JSON instead of the table (for scripts and CI).

Scheduled background runs

The audit is cheap and read-only, so a daily background run on a managed laptop is the natural setup. Neither example below needs admin rights.

Windows: per-user Scheduled Task. Runs daily at 09:00 under your own account:

schtasks /Create /SC DAILY /ST 09:00 /TN "RepoOpsBillingAudit" ^
  /TR "npx repoops-billing-audit --window 30 --out C:\Users\you\billing-guard-report.json"

Set ANTHROPIC_ADMIN_KEY as a user environment variable first (setx ANTHROPIC_ADMIN_KEY sk-ant-admin01-..., then a fresh session picks it up). Remove the task with schtasks /Delete /TN "RepoOpsBillingAudit".

macOS: LaunchAgent. Save as ~/Library/LaunchAgents/ai.repoops.billing-audit.plist, then launchctl load it:

<?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>ai.repoops.billing-audit</string>
  <key>ProgramArguments</key><array>
    <string>/usr/local/bin/npx</string>
    <string>repoops-billing-audit</string>
    <string>--window</string><string>30</string>
    <string>--out</string><string>/Users/you/billing-guard-report.json</string>
  </array>
  <key>EnvironmentVariables</key><dict>
    <key>ANTHROPIC_ADMIN_KEY</key><string>sk-ant-admin01-...</string>
  </dict>
  <key>StartCalendarInterval</key><dict>
    <key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer>
  </dict>
</dict></plist>
launchctl load ~/Library/LaunchAgents/ai.repoops.billing-audit.plist
Wire the exit code to an alert
Exit code 1 means findings. In any wrapper script, treat a nonzero exit as "open the evidence bundle": the JSON at --out carries both sides' numbers per flagged day.

Reading the ledger

The primary view is a grouped per-day ledger. Each day is a group: the date shows once at the top of the group, one row per model carries that model's token count and expected cost, and a per-day subtotal row (· day) sums the group. A TOTAL footer carries the grand token count and the grand expected cost across the window. In a real terminal the rows are colored by model; piping, NO_COLOR, and --json turn color off.

With an Admin key set (verified mode), each day's subtotal also carries the provider's billed figure and a verdict: OVER when the bill exceeds expected beyond the tolerance band, under when it falls below it, even inside it, with the signed percentage. Without a key (telemetry-only mode), the billed and verdict columns read a dimmed needs key so you can see exactly what a key unlocks; the footer reads awaiting Admin key. Any model missing from the rate card is never silently costed at zero: its tokens are called out in a footnote so you know the real total is higher.

In verified mode a findings table follows the ledger, one row per flagged day: day, model, expected, billed, delta, signature. The signatures:

  • variance-band: the billed day total exceeds the telemetry-recomputed expected cost beyond the tolerance band.
  • ghost-usage: the provider reports usage for a day and model on which local telemetry (which was capturing that day) logged zero tokens.
  • rate-check: the billed amount implies a per-token price matching no known rate-card row.
  • duplicate-burst: provider-reported usage is at least 2x the locally logged usage for the same day.
  • cache-share-divergence: the cache-read share of input-side tokens differs between local telemetry and the bill for a day and model, even when the day's total cost sits inside the band.
  • delta-alarm: day-over-day billed total grew 3x or more.
  • contradiction: the provider's own surfaces disagree; the usage report shows zero tokens while the cost report bills money.

Below the table, the coverage summary is as important as the findings. Days the local telemetry did not capture are listed as cannot-verify, never flagged: missing coverage is not evidence of overbilling. Structural blind spots (Priority Tier costs, code-execution charges) appear as cannot-cross-verify annotations.

The tolerance band

Anthropic explicitly documents client-side cost figures as approximations, so Billing Guard never compares dollars for cent-equality. Token counts are the stable primitive: expected cost is recomputed from your token counts times the published per-model rates, then compared inside the band (default 2%, --tolerance to adjust). A day inside the band reconciles; a day outside it becomes a finding with the raw numbers attached. If your baseline drift runs hotter than 2%, widen the band before trusting the alerts; a band tighter than the real-world approximation noise produces noise, not findings. Run --measure-baseline to see your real drift distribution and a recommended band before choosing.

Honest limits

  • Telemetry is opt-in and local. The ground-truth ledger only covers machines and sessions where Claude Code transcripts exist. A day with no local capture is reported as cannot-verify, not as a finding.
  • Subscription plans without Admin API access cannot verify. The Usage & Cost reports require an organization Admin key. Claude subscription plans (Pro/Max) do not expose invoice-side data this way, so the CLI can only run telemetry-only mode there.
  • Known Admin API blind spots. Priority Tier costs are excluded from the cost report and code execution is excluded from the usage report; both are surfaced as annotations rather than silently mismatching.
  • The rate card is versioned, not omniscient. A model missing from the card makes its day cannot-verify rather than producing a made-up expected cost.

Read more

The same findings render in the RepoOps dashboard: the Billing Guard section on the Claude Code usage tab adds a per-day reconciliation ledger, the coverage summary, a tolerance-band control, and an evidence-bundle export button. See the Billing Guard docs, the overview, or the research post on why AI invoices deserve an audit trail.

Last updated