Skip to content
Search documentation titles, headings, and guides
API v1Updated 2026-09-28

Usage & Balance API

Workspace REST endpoints for reading your own balances, usage history, transactions, analytics, and exporting data — via a browser session or a zr_ API key.

These endpoints expose read-only access to your own workspace data: balances, usage history, transactions, analytics, and CSV/JSON exports. In a browser you call them with your dashboard session; from a server or script you can now use the same zr_ API key you already use for the inference API.

Authentication#

Every endpoint below accepts either credential, whichever arrives first:

  • Session cookie — the httpOnly zrouter_session cookie issued by Google sign-in. This is how the dashboard calls the API.
  • API key — a zr_ key created in the dashboard under API Keys (shown once at creation). Send it in the Authorization header as a Bearer token or in the X-Api-Key header.

API key access is limited to these five read-only paths: GET /api/customer/balances, GET /api/customer/usage, GET /api/customer/transactions, GET /api/customer/analytics, and GET /api/customer/exports/{kind}. Everything else under /api/customer/* — checkout, receipt downloads, session management, and API key management — still requires the browser session. If both headers are sent and they carry different keys, the request is rejected.

Call with an API key (two equivalent header styles)
# Style 1: Authorization Bearer
curl -H "Authorization: Bearer zr_<public-id>_<secret>" \
  https://api.zrouter.dev/api/customer/balances

# Style 2: X-Api-Key
curl -H "X-Api-Key: zr_<public-id>_<secret>" \
  https://api.zrouter.dev/api/customer/balances

If a request carries a valid session cookie, the session wins and the API key is ignored. A blocked key returns the same 401 invalid_api_key as a wrong, revoked, or expired key — the responses are indistinguishable by design, so probing which failure mode occurred is not possible.

Rate limits#

Requests authenticated with an API key are limited to 60 requests per minute per key (the default; the operator can change it via CUSTOMER_KEY_RATE_LIMIT_PER_MINUTE). Exceeding the limit returns 429 rate_limited with a Retry-After: 60 header. Repeated attempts with an invalid key are additionally throttled per source IP. Session-cookie calls are not counted against the key limiter.

Errors#

HTTPcodeWhen
401invalid_sessionNo or invalid session cookie and no API key provided.
401invalid_api_keyThe API key is wrong, revoked, expired, or blocked — identical body in all cases.
429rate_limitedToo many requests. Please retry shortly. See Retry-After.
503blacklist_unavailableTemporary server-side condition. Retry shortly; do not treat the key as invalid.

List balances#

GET/api/customer/balances
Auth: session-cookie or api-key

Return your PAYG balance (as a decimal string) and the remaining-token summary for every Token Plan you hold.

Responses

200Balances for the authenticated user.
{
  "payg": { "currency": "USD", "balance": "12.345678" },
  "token_plans": [
    {
      "model": "<public-name>",
      "purchased_tokens": 1000,
      "used_tokens": 320,
      "remaining_tokens": 680,
      "status": "active"
    }
  ],
  "as_of": "2026-09-16T00:00:00Z"
}
401No valid credential (see Errors).
{ "error": { "code": "invalid_api_key", "message": "Invalid API key." } }
Read balances with an API key
curl -H "Authorization: Bearer zr_<public-id>_<secret>" \
  https://api.zrouter.dev/api/customer/balances

List usage#

GET/api/customer/usage
Auth: session-cookie or api-key

Return your inference usage events, newest first, with cursor pagination and a 90-day retention window.

Parameters

limitintegerqueryoptional

Page size, 1-100. Default 50.

cursorstringqueryoptional

Opaque cursor from next_cursor / prev_cursor.

prevbooleanqueryoptional

Set to true to page backwards. Requires cursor.

fromstringqueryoptional

RFC3339 lower bound, inclusive.

tostringqueryoptional

RFC3339 upper bound, inclusive.

modelstringqueryoptional

Filter by model public name.

api_key_iduuidqueryoptional

Filter by API key id (UUID). You can only see your own keys.

Responses

200Usage page for the authenticated user.
{
  "data": [
    {
      "id": "b8c1...",
      "request_id": "req_9f2e...",
      "model": "<public-name>",
      "input_tokens": 1204,
      "output_tokens": 356,
      "total_tokens": 1560,
      "display_cost_usd_micro": "0.000120",
      "funding_mode": "token_plan",
      "status": "success",
      "timestamp": "2026-09-16T04:12:33Z"
    }
  ],
  "next_cursor": "eyJvZmZzZXQiOjUwfQ",
  "prev_cursor": null,
  "total": 1234,
  "retention_days": 90
}
400Malformed or out-of-range parameters.
{ "error": { "code": "invalid_request", "message": "limit must be between 1 and 100" } }
401No valid credential (see Errors).
{ "error": { "code": "invalid_api_key", "message": "Invalid API key." } }

Each item also carries api_key_id, api_key_name, api_key_prefix, endpoint_kind, streamed, cache_read_input_tokens, plan_debit_tokens, payg_debit_usd_micro, funding, settlement_status, completed_at, and settled_at where applicable.

Page through usage
curl -H "X-Api-Key: zr_<public-id>_<secret>" \
  "https://api.zrouter.dev/api/customer/usage?limit=100&cursor=eyJvZmZzZXQiOjB9"

List transactions#

GET/api/customer/transactions
Auth: session-cookie or api-key

Return your top-up and purchase transactions (checkout orders), newest first, with cursor pagination.

Parameters

limitintegerqueryoptional

Page size, 1-100. Default 50.

cursorstringqueryoptional

Opaque cursor from next_cursor / prev_cursor.

prevbooleanqueryoptional

Set to true (or 1) to page backwards. Requires cursor.

Responses

200Transaction page for the authenticated user.
{
  "data": [
    {
      "id": "3f1c...",
      "type": "token_plan",
      "status": "credited",
      "amount_idr": 16000000,
      "usd_value_micro": 1000000,
      "fee_idr": 800000,
      "total_idr": 16800000,
      "granted_tokens": 1000,
      "created_at": "2026-09-12T08:30:00Z",
      "credited_at": "2026-09-12T08:31:02Z"
    }
  ],
  "next_cursor": null,
  "prev_cursor": null,
  "total": 7
}
400Malformed parameters or an invalid cursor.
{ "error": { "code": "invalid_request", "message": "invalid cursor" } }
401No valid credential (see Errors).
{ "error": { "code": "invalid_api_key", "message": "Invalid API key." } }

amount_idr, fee_idr, and total_idr are IDR integers (IDR has no minor unit). usd_value_micro is micro-USD. credited_at is set once the payment is credited; receipt download (GET /api/customer/transactions/{order_id}/receipt) remains dashboard-only and is not part of API key access.

List transactions with an API key
curl -H "Authorization: Bearer zr_<public-id>_<secret>" \
  "https://api.zrouter.dev/api/customer/transactions?limit=50"

Usage analytics#

GET/api/customer/analytics
Auth: session-cookie or api-key

Return aggregated usage analytics for a time range (default: the last 30 days), broken down by day, model, and API key, plus a comparison with the previous equal-length period.

Parameters

fromstringqueryoptional

RFC3339 lower bound. Defaults to 30 days ago.

tostringqueryoptional

RFC3339 upper bound. Defaults to now. Must be after from.

modelstringqueryoptional

Filter by model public name.

api_key_iduuidqueryoptional

Filter by API key id (UUID). You can only see your own keys.

Responses

200Analytics for the authenticated user.
{
  "summary": {
    "requests": 210,
    "tokens": 348000,
    "cost_usd_micro": 25400,
    "input_tokens": 300000,
    "output_tokens": 48000,
    "cache_read_tokens": 12000,
    "cached_input_share": 0.04,
    "blended_cost_usd_micro_per_million": 73000
  },
  "previous_summary": { ... },
  "daily": [
    { "date": "2026-09-16", "requests": 18, "tokens": 26000, "cost_usd_micro": 1900,
      "input_tokens": 22000, "output_tokens": 4000, "cache_read_tokens": 900 }
  ],
  "breakdown_by_model": [
    { "model": "<public-name>", "requests": 180, "input_tokens": 250000,
      "output_tokens": 45000, "cache_read_tokens": 11000, "cost_usd_micro": 22000 }
  ],
  "breakdown_by_key": [
    { "api_key_id": "d1e2...", "name": "prod-key", "requests": 200,
      "tokens": 340000, "cost_usd_micro": 24000 }
  ],
  "token_breakdown": { "input_tokens": 300000, "output_tokens": 48000, "cache_read_tokens": 12000 }
}
400Malformed parameters or from not before to.
{ "error": { "code": "invalid_range", "message": "from must be before to" } }
401No valid credential (see Errors).
{ "error": { "code": "invalid_api_key", "message": "Invalid API key." } }
Fetch analytics with an API key
curl -H "X-Api-Key: zr_<public-id>_<secret>" \
  "https://api.zrouter.dev/api/customer/analytics?from=2026-09-01T00:00:00Z&to=2026-09-28T00:00:00Z"

Export data#

GET/api/customer/exports/{kind}
Auth: session-cookie or api-key

Download your data as a file attachment. The response carries Content-Disposition: attachment.

Parameters

kindstringpathrequired

One of usage, transactions, balances.

formatstringqueryrequired

One of csv, json.

fromstringqueryoptional

RFC3339 lower bound (usage and transactions only).

tostringqueryoptional

RFC3339 upper bound (usage and transactions only).

Responses

200File attachment (CSV or JSON). Save it with curl -o.
Content-Disposition: attachment; filename="usage-2026-09-28.csv"
400Unknown kind or format.
{ "error": { "code": "invalid_request", "message": "unsupported export kind" } }
401No valid credential (see Errors).
{ "error": { "code": "invalid_api_key", "message": "Invalid API key." } }
Export usage as CSV with an API key
curl -o usage.csv -H "Authorization: Bearer zr_<public-id>_<secret>" \
  "https://api.zrouter.dev/api/customer/exports/usage?format=csv"