Usage Query API
GET /v1/usage returns your organisation's cost and usage aggregates as JSON rows: live figures for dashboards and meters, and historical breakdowns for pulling into spreadsheets, BI tools, or your own systems.
GET https://api.aispendops.com/v1/usage
Authorization: Bearer aso_agt_...
Authentication uses an access token (aso_agt_…, created under Settings → Access Tokens) with the read:usage scope. Responses are aggregates only, computed from pre-aggregated rollups: fast, cheap, and safe to poll.
Quick examples
Spend this month, as one number:
curl "https://api.aispendops.com/v1/usage?metric=cost_usd&window=month_to_date" \
-H "Authorization: Bearer $ASO_ACCESS_TOKEN"
Spend broken down by your feature dimension for March:
curl "https://api.aispendops.com/v1/usage?metric=cost_usd&dimension=dim&dim_key=feature&window=month&month=2026-03" \
-H "Authorization: Bearer $ASO_ACCESS_TOKEN"
A year of monthly spend per provider, one call:
curl "https://api.aispendops.com/v1/usage?metric=cost_usd&dimension=provider&window=monthly_series&months=12" \
-H "Authorization: Bearer $ASO_ACCESS_TOKEN"
Which models each value of your feature dimension used this month, with cost, request count, and tokens per row (made for BI extracts):
curl "https://api.aispendops.com/v1/usage?metric=cost_usd&dimension=dim_model&dim_key=feature&window=month_to_date&limit=1000" \
-H "Authorization: Bearer $ASO_ACCESS_TOKEN"
Response shape
{
"asOf": "2026-08-08T14:23:11Z",
"rows": [
{ "label": "openai", "value": 2103.40 },
{ "label": "anthropic", "value": 1240.15 }
]
}
label: the group (provider name, model, API key id, dimension value), or"total"for single-number queries. API key breakdowns return the key id (aso_k_...), not its display name; resolve names via your own records if you need them.value: USD forcost_usd, a count forrequestsandtokens.bucket: always present, but only non-null onmonthly_seriesrows, where it carries the month ("2026-03-01").model,requests,tokens: always present, but only non-null ondimension=dim_modelrows. There each row is one (dimension value, model) pair:labelis the dimension value,modelis the model id,valueis that pair's cost in USD, andrequestsandtokensare its request and token counts.
A dim_model row looks like this:
{ "label": "checkout", "model": "claude-sonnet-5", "value": 2.55, "bucket": null, "requests": 103, "tokens": 1030207 }
Parameters
| Parameter | Required | Description |
|---|---|---|
metric | Yes | cost_usd, requests, or tokens |
window | Yes | Time window, see below |
dimension | No | Group results: provider, model, api_key, dim, dim_model, or day |
dim_key | With dimension=dim or dim_model | Which custom dimension to break down by (for example feature, team) |
month | With window=month | Calendar month as YYYY-MM |
months | With window=monthly_series | How many months back, 1 to 24 (default 12) |
limit | No | Rows for breakdowns, 1 to 1000 (default 5), largest first |
api_key | No | Filter any cost query to one API key id |
Windows
All windows are UTC, matching the dashboards: today is the current UTC day, month_to_date is the current UTC calendar month so far.
window | Meaning |
|---|---|
month_to_date | Current month so far |
last_month | The previous calendar month |
today | Current UTC day |
yesterday | Previous UTC day |
last_7_days / last_30_days | Rolling daily range, used with dimension=day for trends |
month | Any specific past month (&month=2026-03) |
monthly_series | Month-by-month trend, up to 24 months |
Supported combinations
Queries are validated against a fixed catalogue; anything outside it returns 400 with code unknown_catalog_tuple. This is what keeps the API fast and safe to poll.
| Metric | Dimension | Windows |
|---|---|---|
cost_usd | none (total) | month_to_date, last_month, today, yesterday, month |
cost_usd | provider | month_to_date, today, month, monthly_series |
cost_usd | model | month_to_date, month |
cost_usd | api_key | month_to_date, today, month |
cost_usd | dim | month_to_date, month |
cost_usd | dim_model | month_to_date, month |
cost_usd | day | last_7_days, last_30_days |
requests | none (total) | today, month_to_date |
tokens | none (total) | month_to_date |
dimension=api_key additionally requires the read:keys scope, because per-key spend reveals how your organisation's keys are used.
Freshness and caching
Results are served from a short server-side cache, so polling does not multiply query cost:
| Window | Typical freshness |
|---|---|
today totals and breakdowns | within 60 seconds |
| Month-to-date and monthly | within 5 minutes |
| Closed months (immutable) | cached up to 1 hour |
Poll as often as you like within your rate limit; identical queries inside the cache window return the same snapshot.
Limits and errors
- Rate limit: 60 requests per minute per token. Exceeding it returns
429with aRetry-Afterheader. - Responses use the standard error shape:
{ "error": "...", "code": "..." }.
| Status | Code | Meaning |
|---|---|---|
400 | unknown_catalog_tuple | The metric, dimension, window combination is not supported |
400 | Invalid parameter (bad month, months out of range, missing dim_key) | |
401 | Missing, invalid, expired, or revoked token | |
403 | scope_denied | Token lacks a required scope (for example read:keys) |
429 | Rate limited, honour Retry-After |
Notes
- Figures match the AI SpendOps dashboards: same data, same UTC day and month boundaries.
- Breakdowns return the top rows by value; raise
limit(up to 1000) to fetch a complete breakdown of a low-cardinality dimension. - Dimension breakdowns (
dimanddim_model) cover only requests tagged with that dimension; untagged traffic is not included. Totals and provider, model, and API key breakdowns cover everything. - Costs are USD.