
# Usage accounting

Every request through the gateway produces a record. This page is the
schema and the access patterns.

## Per-request record

The fields stored for each request:

| Field | Description |
|-------|-------------|
| `generation_id` | Opaque, globally unique. Returned in the response and used to look up the record later. |
| `created_at` | UTC timestamp when the gateway accepted the request. |
| `api_key_id` | The API key used. Names are joined in for display. |
| `user_identifier` | Identifier of the account the key belongs to. |
| `model_id` | The model that served the request. When the caller sent an auto-routed or aliased ID (e.g. `auto/mistralai/mistral-large-2512`), this is the resolved route. |
| `provider_id` | Upstream provider that served the request. |
| `region` | Region the upstream served from. |
| `prompt_tokens` | Token count of the input. |
| `completion_tokens` | Token count of the output. |
| `total_tokens` | Sum. |
| `latency_ms` | First-byte latency for streaming, end-to-end for non-streaming. |
| `request_duration_ms` | Total request duration measured at the gateway. |
| `cost` | Amount debited for this request, in EUR. |
| `energy_wh` | Estimated energy for the inference, in watt-hours. |
| `carbon_gco2e` | Estimated CO₂e for the request, in grams. |
| `routing_mode` | How the model was selected (e.g. explicit vs. auto). |
| `routing_reason` | Why the router picked this model/provider. |
| `fallback_occurred` | Whether a fallback provider served the request after a primary failed. |
| `status_code` | HTTP status returned to the caller. |

Prompt and response *content* are not stored.

## Where to read it

- **API lookup** — a completion response carries an `id` (the same
  `chatcmpl-…`/`cmpl-…` value OpenAI clients already read). Pass it to
  `GET /v1/generation/{id}` for the full record, or to
  `GET /v1/metrics/{id}` for just the token/energy/carbon figures.
  Both require the same API key and only return records billed to
  that key's account. See **Looking up a generation** below.
- **Dashboard → Recent transactions** — the last ~50 requests, with
  filtering by date range, model, provider, region, key.
- **Transaction detail page** — the full record for a single request,
  accessed by clicking a row in the transactions table (the detail
  path is keyed on the transaction's `referenceId`).
- **Export** — **Dashboard → Recent transactions → Export** produces a
  CSV of the filtered range, capped at 50,000 rows per export. Use this
  for programmatic queries, reconciliation, or piping into your own
  data warehouse.

## Looking up a generation

Every completion response includes a top-level `id`. That value is the
generation's lookup key — there is no separate `generation_id` field in
the response body.

```bash
# The id from a prior chat/completions response
GENERATION_ID="chatcmpl-abc123"

curl https://api.lowrouter.ai/v1/generation/$GENERATION_ID \
  -H "Authorization: Bearer $LOWROUTER_API_KEY"
```

The record is scoped to the calling key's account: a key can only read
generations billed to its own customer. An unknown id — or one that
belongs to another account — returns `404`, so the id space can't be
probed across accounts.

For just the metrics (tokens, energy, carbon, duration) without the
routing detail, use `GET /v1/metrics/{id}` with the same id.

## Aggregates

The dashboard pre-computes a small set of aggregates and updates them
on each request:

- Tokens per day, per model.
- Cost per day, per model.
- Energy and carbon per day, per model.

These aggregates power the charts. They are derived from the
per-request records and are reproducible from a CSV export.

## Retention

| Data | Retention |
|------|-----------|
| Per-request records | 13 months from `created_at`. |
| Daily aggregates | 36 months. |
| Audit log entries | 36 months. |
| Account profile | For the lifetime of the account. |

After retention expires the per-request rows are deleted and replaced
by anonymised aggregates. Aggregates are kept for sustainability
reporting and platform analytics; they cannot be used to reconstruct
individual requests.

You can request earlier deletion of all your usage records via the
support email; the deletion is irreversible and may affect your
ability to dispute past invoices.

## Reconciliation tips

- The sum of `cost` over a day should equal the daily cost on
  the dashboard within a rounding tolerance.
- The sum of `total_tokens` over a day grouped by model is what the
  upstream provider's usage report (if you have one) should show.
- The carbon estimate is reproducible: given the same `model_id`,
  `region`, and `total_tokens`, recomputing with the formula in
  [methodology](../sustainable-ai/methodology) should yield the same
  gram count.

If the numbers diverge more than rounding allows, that is a bug —
[open an issue](https://github.com/carbonifer/lowrouter/issues).
