Usage
List billed usage for an API key or its account, summarize it, and look up one request.
Authentication
These endpoints are server-to-server. Authenticate with Authorization: Bearer <API_KEY> or x-api-key. Keep the key on your backend.
A key can read account usage, not only its own calls. Keys for the same buyer share one wallet, so reconciliation is normally scope=account. scope=key is the subset for the current key. Looking up one request_id is always account-scoped, even when the call was made with another key of the same buyer.
What a row means
Rows come from billed usage only. A call that was rejected, rate-limited, or interrupted before billing has no row. Settlement is asynchronous: LLM usage usually appears within seconds, and a media task appears after the task finishes. A missing row means the call was not billed, or billing has not finished yet. Do not use this API as a failure-rate counter.
charge is the amount posted to the account for that call, in credits. official_price is the list-price total for the call; historical rows without a stored list price return null.
kind is derived from the request id. Media task ids use a tsk prefix (kind is media); LLM calls use an aitk- prefix (kind is llm). kind is an open value: a filter you do not recognize returns no rows, and a response value you do not recognize should be skipped. A media row's request_id is the task id and can be passed to GET /v1/media/tasks/{id}. Token-priced media fills tokens; per-call media reports zeros.
The reconciliation key is request_id (the x-aitoken-request-id returned by the original call, or the media task id).
Amounts are decimal strings with six fractional digits. Token counts are integers. Timestamps are UTC ISO 8601. to is exclusive.
List usage
/v1/account/usageReturns billed rows for the current key or the whole account, newest first.
curl "https://hopsapi.com/v1/account/usage?scope=account&from=2026-09-22T00:00:00Z&to=2026-09-23T00:00:00Z&limit=2" \
-H "Authorization: Bearer sk-YOUR_KEY"| Name | In | Required | Description |
|---|---|---|---|
scope | query | no | key (default) or account. Any other value is 400. |
from | query | no | Window start, ISO 8601 or unix seconds. Default is 24 hours before to. |
to | query | no | Window end, exclusive. ISO 8601 or unix seconds. Default is now. |
model | query | no | Exact model id. |
channel | query | no | Exact channel id, standard or flex. |
kind | query | no | Exact kind. llm and media match those rows. Any other value matches nothing. |
limit | query | no | Page size, integer 1–500. Default 100. |
cursor | query | no | Opaque next_cursor from the previous page. |
from must be earlier than to, and the window cannot be longer than 31 days.
{
"scope": "account",
"from": "2026-09-22T00:00:00Z",
"to": "2026-09-23T00:00:00Z",
"currency": "credits",
"credits_per_usd": "100",
"data": [
{
"request_id": "aitk-3f2c",
"kind": "llm",
"model": "claude-sonnet-5",
"channel": "flex",
"channel_name": "Subscription Direct",
"tokens": {
"input": 1834,
"output": 412,
"cached": 1200,
"cache_write": 0,
"cache_write_1h": 0
},
"charge": "0.263400",
"official_price": "0.752571",
"status": 200,
"key": { "id": 1042, "name": "prod-us", "key_masked": "sk-abcd…9f3c" },
"created_at": "2026-09-22T13:05:41Z"
}
],
"has_more": true,
"next_cursor": "eyJ0IjoiMjAyNi0wOS0yMlQxMjo1ODowM1oiLCJpIjo3MTIzNDV9"
}scope=account includes historical rows from deleted or disabled keys. Each row includes key.id, key.name, and key.key_masked.
Cursor pagination
Results are ordered by created_at descending, then id descending. Pass next_cursor as cursor to read the next page. next_cursor is null when there are no further rows. Do not use an offset: the cursor is anchored to a row, so rows inserted after you start paging do not shift later pages.
curl "https://hopsapi.com/v1/account/usage?scope=account&cursor=eyJ0IjoiMjAyNi0wOS0yMlQxMjo1ODowM1oiLCJpIjo3MTIzNDV9" \
-H "Authorization: Bearer sk-YOUR_KEY"A modified cursor returns 400.
Summary
/v1/account/usage/summaryAggregates the same billed rows as the list endpoint over the same window and filters.
curl "https://hopsapi.com/v1/account/usage/summary?scope=account&from=2026-09-22T00:00:00Z&to=2026-09-23T00:00:00Z&group_by=model" \
-H "Authorization: Bearer sk-YOUR_KEY"| Name | In | Required | Description |
|---|---|---|---|
scope | query | no | Same as the list endpoint. Default key. |
from | query | no | Same window rules as the list endpoint. |
to | query | no | Same window rules as the list endpoint. |
model | query | no | Exact model id. |
channel | query | no | Exact channel id. |
kind | query | no | Exact kind. An unrecognized value matches nothing. |
group_by | query | no | day (default), model, or key. Any other value is 400. |
day buckets are UTC dates (YYYY-MM-DD) and omit empty days. model buckets are (model, channel). key buckets include key. With scope=key that is one bucket for the current key. total is the whole window, not just one bucket. Summing every page of the list endpoint matches total for calls, each token counter, and charge.
{
"scope": "account",
"from": "2026-09-22T00:00:00Z",
"to": "2026-09-23T00:00:00Z",
"group_by": "model",
"currency": "credits",
"credits_per_usd": "100",
"data": [
{
"model": "claude-sonnet-5",
"channel": "flex",
"calls": 1830,
"tokens": {
"input": 3120044,
"output": 611203,
"cached": 1988000,
"cache_write": 0,
"cache_write_1h": 0
},
"charge": "482.113200"
}
],
"total": {
"calls": 2011,
"tokens": {
"input": 3200000,
"output": 640000,
"cached": 1988000,
"cache_write": 0,
"cache_write_1h": 0
},
"charge": "540.220100"
}
}One request
/v1/account/usage/{request_id}Returns one billed row for the current buyer's account. The body matches one object from the list endpoint.
| Name | In | Required | Description |
|---|---|---|---|
request_id | path | yes | The original x-aitoken-request-id, or a media task id. |
curl https://hopsapi.com/v1/account/usage/aitk-3f2c \
-H "Authorization: Bearer sk-YOUR_KEY"404 means there is no billed row for that id on this account. That includes an unknown id, another buyer's id, and a call whose settlement has not finished. Retry a recent id before treating it as absent. The response does not say which of those cases it is.
Errors
Every response includes x-aitoken-request-id. These endpoints do not use LLM account limits. The three usage routes share one per-key limit of 120 requests per minute. A rejection is 429 with draft-6 RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset. See Rate limits.
| HTTP status | error.type | error.code | Meaning |
|---|---|---|---|
400 | invalid_request_error | VALIDATION | Bad scope, window, filter, limit, group_by, or cursor. A window over 31 days says so. |
401 | authentication_error | The key is missing, invalid, or inactive. The body matches GET /v1/models. | |
404 | not_found | usage_not_found | No billed row for this account. It may not be settled yet. |
429 | rate_limit_error | usage_rate_limit | The key exceeded 120 usage requests in the current minute. Use the RateLimit-* headers. |
503 | internal_error | usage_unavailable | Usage could not be read. The body includes error.request_id. An empty data array is not a read failure. |