Skip to content

Costs & billing

threadle shows two dollar figures and never conflates them.

FigureMeans
tracked costsThe list-price value of the tokens — what an API key would have been billed
actual spendWhat you really paid

On a subscription those numbers are wildly different, and pretending otherwise is the most common way agent dashboards lie. A Max-plan week can show hundreds of dollars tracked and $0 (subscription) actual — both true.


Providertracked costsactual spend
Claude CodeComputed from the transcript, per model, at models.dev prices$0 on a subscription, = tracked on an API key
opencodeThe cost opencode recorded itselfSame number — opencode reports what its own keys were billed
Cursor Agent— (no list price in public storage)$0 — Cursor Agent sessions are subscription-billed

Cursor token counts come from the agent CLI result when threadle captured one, otherwise a chars ÷ 4 estimate. Estimated figures are prefixed ~ and carry a token source row (cli, turn_ended, or estimate (chars/4)).


Settings → claude code billing:

SettingEffect on Claude sessions
subscription (Pro / Max) — defaultactual spend = $0, tracked stays as computed
API key (pay per token)actual spend = tracked

Stored as claudeBilling in ~/.config/threadle/settings.json. When Claude Code has cached plan data locally, the settings card names the detected plan and billing type so the switch is not a guess.

Spend tripwires trip on tracked session dollars where a provider reports them — a measurement, not a forecast.


Claude’s /cost breakdown, reproduced from the transcript. One pass tallies per model: input, output, cache read, cache write, and the 1h-TTL share of cache write. The session panel renders a row per model with its dollar figure and in · out · cache read · cache write.

Pricing a model means finding its exact models.dev id, or the longest key the id extends — dated snapshots like claude-sonnet-4-5-20250929 price off their base entry.

cost = ( input × in$
+ output × out$
+ cacheRead × cacheRead$
+ write5m × cacheWrite$
+ write1h × (in$ × 2) ) / 1_000_000

models.dev’s cache_write price is the 5-minute rate. Cache writes with a 1-hour TTL bill at roughly 2× the input rate, so threadle splits cacheWrite into its 5m and 1h parts and prices them separately. Without an input price it falls back to 1.6 × the 5m rate.

A model with no price shows for its row and contributes nothing to the total. If no model in the session can be priced, the session has no tracked cost at all rather than a silent $0.00.


Cache hit rate is always:

cache read ÷ (fresh input + cache read + cache write)

It appears per session, per model, per provider, and per project. Higher is cheaper: cache reads are billed at a fraction of fresh input, so a long session with a high hit rate can cost less than a short one that keeps rebuilding its prompt.


RowSource
duration (API)Sum of the transcript’s turn_duration system lines — agent working time, matching /cost’s “API duration”
duration (wall)updated − created — elapsed clock time, including your thinking time
code changes+N −M lines, summed from Edit patch hunks and new-file writes

opencode sessions additionally carry their own diff summary (+N −M across K files).


The claude code section of Statistics can show plan, plan · 5h window, and plan · 7d window tiles with utilization bars and reset times.

These are read passively from ~/.claude.json — the state file Claude Code maintains itself, including the plan tier from your OAuth account and the utilization it cached from its own usage calls. threadle never phones home for this; it mirrors what the CLI already stored locally, and the tile tooltip says how old that cache is. Email address and referral data present in the file are deliberately ignored.

No file, no account block, no tiles.


List prices are the models.dev database — the same data opencode uses to compute its session costs.

Lookup is strictly local-first: any on-disk copy wins regardless of age.

  1. ~/.cache/opencode/models.json — opencode’s own mirror
  2. ~/.config/threadle/models-pricing.json — threadle’s cache

The freshest of the two by mtime is used. The network is contacted once, only when no local copy exists anywhere, then cached to disk. A failed fetch backs off for an hour. Stale list prices beat surprise network calls.

⟳ fetch prices in Statistics forces a refresh now (POST /api/pricing/refresh) and replaces the cache. The source URL is configurable in Settings and must be http(s); it defaults to https://models.dev/api.json.

The pricing table is scoped to providers you can actually run: anthropic, opencode, and every provider prefix exposed by your opencode install. When nothing is available it says so — unavailable (offline and no local cache) — instead of showing zeros.