CommonBill
Records what every model call costs, by tag, and reports it. See what each client, area or profile costs you.
CommonBill writes one row for every successful model call: who made it, which model, how many tokens, what it cost, and the turn’s tags. Then it answers questions like “what did Acme cost this month?”
Id: bill · Table: model_calls · Route: GET /api/costs
What it records
CommonBill hooks post_api_request, which fires after each successful model call. It does not use post_llm_call, because that fires once per finished turn and would miss the extra calls inside a turn and calls in interrupted turns.
| Column | Holds |
|---|---|
turn_id, profile_id |
Which turn and profile made the call |
provider, model |
Who answered |
input_tokens, output_tokens |
Token counts |
cache_read_tokens, cache_write_tokens |
Prompt-cache token counts |
cost_usd |
The cost, from pricing.toml |
cost_status |
priced, or unpriced when the model has no price |
tags |
The turn’s tags |
created_at |
When |
Child turns carry their parent’s tags, so work done by delegated helpers is billed to the same client.
Prices
Prices change, so none ship with CommonOS. Put them in ~/.commonos/pricing.toml, in US dollars per million tokens:
[models."<model id>"]
input_per_mtok = 0.0
output_per_mtok = 0.0
cache_read_per_mtok = 0.0
cache_write_per_mtok = 0.0cache_read_per_mtok and cache_write_per_mtok default to 0. The file is read at start-up.
A model with no price is still recorded, with its tokens, cost_usd = 0 and cost_status = 'unpriced'. Add the price later and you can compute the cost from the stored tokens.
The mock model mock-1 has a built-in price (1.00 input, 2.00 output, 0.10 cache read and 1.25 cache write per million tokens), so reports work without a key.
Reports
GET /api/costs?group_by=tag&tag=client:acme&from=2026-10-01T00:00:00Z| Parameter | Does |
|---|---|
group_by |
total (default), tag, profile, model or day |
tag |
Only calls with this tag |
profile |
Only this profile’s calls |
model |
Only this model’s calls |
from, to |
Time range, RFC 3339 |
{
"group_by": "model",
"rows": [
{
"group": "mock-1",
"calls": 6,
"input_tokens": 600,
"output_tokens": 120,
"cache_read_tokens": 0,
"cache_write_tokens": 0,
"cost_usd": 0.00084,
"unpriced_calls": 0
}
]
}Examples
What did Acme cost in October?
curl -s 'http://127.0.0.1:9120/api/costs?tag=client:acme&from=2026-10-01T00:00:00Z&to=2026-11-01T00:00:00Z'Cost per day for one profile:
curl -s 'http://127.0.0.1:9120/api/costs?group_by=day&profile=Household'Which models had no price?
curl -s 'http://127.0.0.1:9120/api/costs?group_by=model' | jq '.rows[] | select(.unpriced_calls > 0)'The dashboard’s Costs panel shows the same report, grouped by tag, profile, model or day.