Usage
Query token usage and spend across your organization and projects.
Read-only, and the most-used group in practice: what was consumed, when, and by what. Usage is a view of the same usage records the portal's Analytics and Logs pages and your invoice use. The API computes nothing of its own, so a late correction appears everywhere at the same time.
| Endpoint | Scope | Token owner's role |
|---|---|---|
GET /usage | usage:read | Admin |
GET /usage/export | usage:read | Admin |
Both routes need an owner who is currently an organization Admin — the
same rule as the portal's Analytics page. A Member's token gets
403 insufficient_scope.
Two rules make this data safe to build on:
- Breakdowns sum to totals — usually. For every field of
totalsexceptunits, the sum overbreakdown[]equals the total exactly whenreconciledistrue(the normal case).totalsare never raised to match the breakdown; usage that cannot be attributed to a group, plus the tail beyond the top 20 groups, is reported in anotherrow rather than dropped. In rare cases the breakdown can briefly exceed the totals; the response then hasreconciled: falseand abreakdown_note. Retry for a consistent report — see below. - No content, ever. Usage reports counts, money and identifiers only. Prompt and completion content, error text and client addresses are never returned by this API.
Money is reported twice: cost_nanousd is the exact integer amount in
nano-USD (10⁻⁹ USD), and cost_usd is the same amount as a decimal string
("12.345678901"). Sum cost_nanousd or parse cost_usd as a decimal —
never as a float.
Query usage
GET /api/v1/mgmt/usageAggregated usage and cost over [from, to), broken down by one
dimension.
| Query parameter | Type | Required | Notes |
|---|---|---|---|
from | string | no | RFC 3339, inclusive. Default: to minus 30 days. |
to | string | no | RFC 3339, exclusive. Default: now. The range may span at most 366 days. |
group_by | string | no | One of project, model, key, day. Default model. One dimension per call. |
project_id | string | no | Only usage of this project. |
key_id | string | no | Only usage of this API key. |
model | string | no | Only usage of this model (provider/model). |
GET /usage is a report, not a list: it is not paginated, and limit or
cursor return 422 unsupported_filter, as does any other unknown
parameter.
group_by | breakdown[].key | Notes |
|---|---|---|
model | The model (provider/model) | |
project | The project id | |
key | The API key id | |
day | The UTC date (2026-09-23) | One exact row per UTC calendar day; the first and last may be partial days. At most 31 days per call. |
For project, model and key, the top 20 groups are returned
individually; the rest, plus any usage that could not be attributed (for
example requests with no key), are folded into a single row with
key: "other".
The usage report
| Field | Type | Notes |
|---|---|---|
from, to | string | The window actually read. |
group_by | string | The dimension of breakdown. |
currency | string | Always USD. |
totals | object | The usage quantities for the whole window (below), plus units. |
breakdown | object[] | One row per group: key plus the usage quantities. Sums to totals exactly, field by field, when reconciled is true. |
reconciled | boolean | true when breakdown sums to totals exactly (always the case for group_by=day). false in the rare case the breakdown briefly exceeds totals; breakdown_note then explains. Retry for a consistent report. |
breakdown_note | string | Present only when reconciled is false. |
usage_stale | boolean | true when usage counts may be incomplete for the most recent minutes. Money is always exact. |
usage_as_of | string | null | Usage counts are complete up to this instant (null when unknown). |
Every total and every breakdown row carries the same usage quantities:
| Field | Notes |
|---|---|
requests | Metered requests. |
errors | Requests that ended in an error status. |
input_tokens, output_tokens, total_tokens | Token counts. |
cached_input_tokens | Prompt-cache read tokens (a subset of input_tokens). |
cache_write_tokens | Prompt-cache write tokens (a subset of input_tokens). |
reasoning_tokens | Reasoning tokens (a subset of output_tokens). |
cost_nanousd | Billed cost, integer nano-USD. |
cost_usd | The same cost as a decimal string. |
totals.units[] gives the native-unit quantity per billing unit (token,
image, second, character, document, …) — unit, requests,
quantity_in, quantity_out, cost_nanousd, cost_usd. Quantities of
different units are never summed together.
curl -G https://portal.<domain>/api/v1/mgmt/usage \
-H "Authorization: Bearer $TF_PAT" \
-d from=2026-09-01T00:00:00Z \
-d to=2026-09-08T00:00:00Z \
-d group_by=model{
"from": "2026-09-01T00:00:00Z",
"to": "2026-09-08T00:00:00Z",
"group_by": "model",
"currency": "USD",
"totals": {
"requests": 105,
"errors": 2,
"input_tokens": 1500,
"output_tokens": 700,
"total_tokens": 2200,
"cached_input_tokens": 40,
"cache_write_tokens": 0,
"reasoning_tokens": 0,
"cost_nanousd": 3123456789,
"cost_usd": "3.123456789",
"units": [
{ "unit": "token", "requests": 105, "quantity_in": 1500, "quantity_out": 700, "cost_nanousd": 3123456789, "cost_usd": "3.123456789" }
]
},
"breakdown": [
{
"key": "openai/gpt-4o",
"requests": 60,
"errors": 0,
"input_tokens": 1000,
"output_tokens": 500,
"total_tokens": 1500,
"cached_input_tokens": 0,
"cache_write_tokens": 0,
"reasoning_tokens": 0,
"cost_nanousd": 2000000000,
"cost_usd": "2.00"
},
{
"key": "other",
"requests": 45,
"errors": 2,
"input_tokens": 500,
"output_tokens": 200,
"total_tokens": 700,
"cached_input_tokens": 40,
"cache_write_tokens": 0,
"reasoning_tokens": 0,
"cost_nanousd": 1123456789,
"cost_usd": "1.123456789"
}
],
"reconciled": true,
"usage_stale": false,
"usage_as_of": "2026-09-24T09:59:15Z"
}Need two dimensions — per project per day, say? Make one group_by=day
call per project (project_id=…), or one group_by=project call per day.
Several values in one call (group_by=project,day) are refused with
422 unsupported_filter.
Export usage
GET /api/v1/mgmt/usage/exportThe backfill path: one record per metered request in [from, to),
newest first, streamed as JSON Lines or CSV.
| Query parameter | Type | Required | Notes |
|---|---|---|---|
format | string | no | jsonl (default; application/x-ndjson, one record per line) or csv (with a header row). |
from, to | string | no | As for GET /usage: RFC 3339, default the last 30 days, at most 366 days. |
project_id, key_id, model | string | no | As for GET /usage. |
There is no group_by, and the export is not paged: limit, cursor and
any other parameter return 422 unsupported_filter.
At most 10,000 records per call. A window that matches more is refused
with 422 export_too_large before anything streams — narrow from/to and
export in windows, or read the aggregates with GET /usage. Requests
running longer than 60 seconds are cut off.
Each record:
| Field | Notes |
|---|---|
id | The usage record id. |
created_at | When the request was metered. |
project_id, key_id | |
model, provider | |
status | The request's outcome, e.g. ok or error. |
unit | The billing unit (token, image, …). |
input_tokens, output_tokens, total_tokens, cached_input_tokens, cache_write_tokens, reasoning_tokens | Token counts. |
quantity_in, quantity_out | Native quantity for non-token units (images, seconds, …). |
cost_nanousd, cost_usd | The request's cost. |
cost_available | false when this record's cost could not be read — the cost fields are then 0, not a real zero. |
latency_ms | |
streaming | Whether the request streamed. |
Truncation
An export can stop early after the 200 status has been sent. A truncated
export is therefore not an error response; it is a stream that ends with a
marker line:
- JSONL: a last line
{"truncated":true,"rows":N,"reason":"row_cap"|"upstream_error"}. - CSV: a last line
# truncated after N rows (<reason>).
A response that was cut short also carries an X-Export-Truncated: true
HTTP trailer (declared in the Trailer header) — but trailers are sent
after the body and some proxies silently drop them, so treat the trailer as
a convenience, not the source of truth. The final line of the stream is
the reliable signal: always check for the truncation marker before
treating an export as complete.
reason: row_cap means the window matched more than 10,000 records after
all — narrow from/to and export the rest separately. reason: upstream_error means reading the usage data failed mid-stream — re-run the
same export.
The export reads records newest first, in pages of 200, with a cursor on
(created_at, id). Records that share the same created_at are all
exported, each exactly once, however many there are.
Every completed export is recorded in the audit log
as usage_export, with row_count, format, from and to — bulk data
movement deserves the same trail as any other.
curl -G https://portal.<domain>/api/v1/mgmt/usage/export \
-H "Authorization: Bearer $TF_PAT" \
-d format=csv \
-d from=2026-09-23T00:00:00Z \
-d to=2026-09-24T00:00:00Z \
-o usage-2026-09-23.csv{"id":"7c2e4f1a-9b3d-4e8a-a1c6-5d0f2b8e3a97","created_at":"2026-09-23T23:59:41.203Z","project_id":"5b3e9a41-0c7d-4e2f-8a16-9d4b7c2e1f08","key_id":"c41d8e2a-7b90-4f3c-a6e5-2d1f0b9c8a74","model":"openai/gpt-4o","provider":"openai","status":"ok","unit":"token","input_tokens":812,"output_tokens":164,"total_tokens":976,"cached_input_tokens":0,"cache_write_tokens":0,"reasoning_tokens":0,"quantity_in":0,"quantity_out":0,"cost_nanousd":3670000,"cost_usd":"0.00367","cost_available":true,"latency_ms":1840,"streaming":true}Errors specific to usage
| Status | Code | When |
|---|---|---|
| 400 | invalid_request (param set) | from/to not RFC 3339; from not before to; a range over 366 days; group_by not one of the four values; project_id/key_id not an id; format not jsonl/csv. |
| 403 | insufficient_scope | Token lacks usage:read, or the owner is not currently an Admin. |
| 422 | unsupported_filter | An unknown parameter, limit/cursor, or several group_by values. |
| 422 | validation_failed | group_by=day over a range longer than 31 days. |
| 422 | export_too_large | The export window matches more than 10,000 requests. |
| 502 | upstream_unavailable | Usage data is temporarily unavailable. Retry. |
To reconcile usage with balances and invoices, see Billing.