Skip to content

Usage and cost

What the fleet spent, and what it is honest to say about it.

Artel records a usage rollup per session and model: turns, input, output, and the two cache counters. Agents post it themselves — the capture hook already parses the transcript for prose, so the numbers come from the same read.

POST /usage   {"model": "claude-opus-5", "session_id": "…", "billing_mode": "subscription",
               "turns": 412, "input_tokens": 8231, "output_tokens": 380122,
               "cache_read": 190337221, "cache_write": 2210945}

GET  /usage?days=30            # grouped by project and model, with cost where it exists

Cost is computed, never estimated

Cost is always computed where a rate exists — the tokens are identical whether you are on a seat or metered, and on a seat the list-price figure is the more interesting one: it is what the work would have cost per-token, i.e. what the seat is worth.

billing_mode labels the number rather than withholding it:

Mode billed basis
metered true actual spend
anything else false list-price equivalent

GET /usage returns billed_usd and list_equivalent_usd separately. They are never added: one is an invoice, the other is a valuation.

There is still no number when there is no rate. $0 reads as "this was free", the most expensive way for a spend report to be wrong.

Rates come from MODEL_RATES or live from OpenRouter, cached six hours, with none compiled in — prices move and a stale constant is a wrong invoice. Model ids are normalised on lookup: a session records claude-opus-4-8 while rate tables name it anthropic/claude-opus-4.8, and without that every Anthropic model reads as unpriced.

Declaring how you are billed

ARTEL_BILLING_MODE (metered or subscription) tells the drainer which it is. It only changes the label — an unset host still gets figures, reported as list-price equivalents rather than as an invoice.

Why cache counters matter more than they look

On a real fleet the cache read counter dominates everything else — commonly by three orders of magnitude over fresh input. Any instinct to cut spend by switching to a cheaper model is aimed at a rounding error; the lever is cache behaviour. Reporting the four counters separately is what makes that visible.

Double counting

usage_events carries a unique index on (session_id, model, window_end) and the route inserts with OR IGNORE. A drainer that re-reads a transcript from an earlier cursor will re-post windows it has already sent; billing them twice is the one error this data must never make.