Usage history
Point store.path at a file and the router keeps a row per served request: the
alias, the upstream that answered, the tokens, the latency and the cost.
store:
path: ~/.llm-router/usage.db
queueSize: 1024
maxRows: 130000 # about 10 MB, keeping the newest 130k requestsQuery it through the router:
curl -H "Authorization: Bearer $ROUTER_KEY" \
'http://127.0.0.1:8787/usage?group_by=alias&since=7d'or open the file directly, even while the router runs (WAL is enabled):
sqlite3 ~/.llm-router/usage.db \
'SELECT upstream, count(*), sum(cost_usd) FROM requests GROUP BY 1'What it is
Usage and cost are observed, not invented. The relay feeds the bytes it has
already copied to a line-oriented observer, so token counts and cache reads are
recorded without parsing, buffering or rewriting the stream: the client still
receives the upstream’s bytes exactly, terminator included. Both wires are
normalised, because they disagree — the OpenAI wire’s prompt_tokens includes
cached tokens, while Anthropic’s input_tokens excludes its separate cache
fields — so cache reads are never billed twice. Each served request logs
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens and
cost_usd from the alias’s declared rates. When a provider reports no usage, the
log says usage=unreported rather than claiming a spend that was never measured.
What it is not
The store is a local record of what the router served, in a file any SQLite client can read. It is deliberately not a control plane: no keys, no budgets, no tenancy. It answers “what did this router do, and what did it cost” without requiring a database server, and the file is the whole state.
- Retention is a ring. Records are pruned as they are written — within one
flush — so
maxRowsis the actual cap. - A request whose provider reported nothing stores NULL, not zero. The token
columns are nullable precisely so that
SUMcannot count spend that was never measured;/usagereports those requests separately underunreported. - Writing never applies back-pressure. Records pass through a bounded queue
to a single writer. If the disk cannot keep up they are dropped and counted in
dropped_records, because accounting must never be the reason an inference call is slow, or the reason one fails. - The file is readable while the router runs. WAL mode is on, which is also
why
-waland-shmfiles appear beside it. - Only served requests are recorded. When every target failed there is no usage, no cost and no upstream that answered, so that request is logged rather than stored.