Skip to main content

Usage API

One read-only call answers "how much, by whom, on what": requests, outcomes, tokens and cost of every app in your organization, over all time or any range. Rows are per period and optionally per one or two dimensions, and totals always covers the whole selection.

CallUse it forScope
GET /usageSpend, requests and tokens by period, app, model, provider, user, key, source or outcome.read
GET /apps/{app_name}/keysEach gateway credential's usage: when it was last used and its requests in the last 30 days.read
GET /usersEach person's requests and cost for the last 30 days.read

No extra scope is needed: a key with read can call all three. Try them with your own key in the Management API playground.

Build a report​

1. Range
from / to
Omit both for all time
2. Periods
granularity
total · month · week · day
3. Breakdown
group_by (up to 2)
app, model, provider, user, key, source, outcome
4. Narrow
Filters on any dimension
Up to 50 values each
5. Read
rows + totals
sort, limit, cursor
QuilrAI
ParameterValuesDefault
fromInclusive start: a date (YYYY-MM-DD, UTC midnight) or an RFC 3339 timestamp with a timezone.The first recorded request
toExclusive end: an RFC 3339 timestamp with a timezone, or a date to include that whole UTC day (to=2026-10-31 ends at 2026-11-01T00:00:00Z).Now
granularitytotal (one row per group), month, week (starts Monday) or day. All periods are UTC, and period_start is the period's first day (YYYY-MM-DD).total
group_byUp to two of app, model, provider, user, key, source, outcome, repeated (group_by=app&group_by=model) or comma-separated (group_by=app,model).No breakdown
app, model, provider, user, key, source, outcomeFilters, comma-separated, at most 50 values each. Apps and emails match case-insensitively. source is gateway, sdk or copilot_studio; outcome is served, blocked, error or rate_limited.None
sortperiod (oldest period first, then highest cost), or cost, requests, tokens (highest first).period
limit, cursorRows per page (1 to 5,000) and the previous page's next_cursor. Keep the rest of the query unchanged while paging; follow next_cursor until it is null for a complete export.500

Filters narrow every number, including totals, and totals always equals the sum of every row across all pages. Periods without traffic are omitted, so a chart should fill gaps with zero.

What the numbers mean​

FieldMeaning
requestsClient requests with every outcome. The background cache-refresh requests the gateway sends on its own are not counted.
blockedRefused by a policy check before the model call, or blocked by a guardrail.
rate_limitedRefused by a gateway rate limit, token limit or quota.
errorsFailed upstream or in the gateway (status 400 or above), excluding blocked and rate-limited requests. A provider's own 429 counts here.
input_tokens, output_tokensInput includes cached input; cache_read_tokens and cache_write_tokens break it down. reasoning_tokens is part of output where the provider reports it.
cost_usdThe cost recorded with each request when it ran. It is never re-estimated, and it includes background cache-refresh requests because the provider bills them.
unpriced_requestsRequests with token usage but no recorded cost, for example a model that had no price when it was called. They add nothing to cost_usd.
totals.priced_coverage_pctThe share of billable requests (those that reached pricing with token usage, background cache-refresh requests included) that have a recorded cost: 100 x (billable - unpriced_requests) / billable. Below 100, cost_usd understates spend. null when nothing was billable.
  • Dimensions. app uses the app's current name while the app exists, otherwise its recorded name; app_deleted: true marks an app that no longer exists. model is the served model (after routing and aliases), not the model name the client sent. key is a gateway credential id (with key_name while the credential exists) and provider is the provider label. A value is null when it was not recorded, and user is null when the request carried no identity or data retention removed it.
  • Freshness. Results can be up to 60 seconds old. generated_at is when the data was read, and data_through is the effective end of the range (your to, or the time of the read). Requests are written a few seconds to minutes after they finish, so the latest ones can be missing.
  • Paging. A next_cursor is tied to the result it came from. If the result is recomputed and has changed (new or late requests), the next page answers 409 collection_changed: restart without cursor. For a stable export, pass a to in the past.
  • Cost of a query. Grouping or filtering by key reads more data. Use a bounded range with it. Queries are limited to 20 seconds and 100,000 result rows (422 result_too_large).
  • Errors. An invalid parameter returns 400 invalid_request with a message such as to must be after from or group_by accepts at most 2 dimensions. 503 query_timeout means the query exceeded its time or read limits: narrow it and retry. 503 data_unavailable means usage data cannot be read right now.
Need individual requests?

This API returns aggregates. To pull every request log line into your warehouse or SIEM, use the Log Export API. To understand one request, use Explain a request.

Admin recipes​

Each recipe needs a management key with read.

export API="$MANAGEMENT_URL/llmgateway/management/v1"
auth=(-H "Authorization: Bearer $MANAGEMENT_KEY")

# Every row of a usage report, one JSON object per line: follows next_cursor to the
# last page and stops on an error (for example 409 collection_changed: rerun it).
usage_rows() {
local cursor="" page
while :; do
page=$(curl -sG "$API/usage?$1" "${auth[@]}" ${cursor:+--data-urlencode "cursor=$cursor"}) || return 1
if jq -e '.error' >/dev/null <<<"$page"; then jq -c '.error' <<<"$page" >&2; return 1; fi
jq -c '.rows[]' <<<"$page"
cursor=$(jq -r '.next_cursor // empty' <<<"$page")
[ -n "$cursor" ] || return 0
done
}

The recipes that export a report use usage_rows, so they include every page. A recipe that reads one request is labeled "first page".

Daily spend for the last 30 days​

FROM=$(date -u -d '30 days ago' +%F 2>/dev/null || date -u -v-30d +%F)
usage_rows "from=$FROM&granularity=day" | jq -r '[.period_start, .requests, .cost_usd] | @tsv'

All-time spend by app​

usage_rows "granularity=total&group_by=app&sort=cost" \
| jq -r '[.app, .requests, .cost_usd, .unpriced_requests] | @tsv'

Top 10 users this month​

First page only: limit=10 with sort=cost returns the ten highest-cost users.

curl -s "$API/usage?from=$(date -u +%Y-%m-01)&granularity=total&group_by=user&sort=cost&limit=10" "${auth[@]}" \
| jq -r '.rows[] | [.user // "(no identity)", .requests, .cost_usd] | @tsv'

Spend by model per week, for one app​

usage_rows "from=2026-09-01&granularity=week&group_by=model&app=Support%20Bot" \
| jq -r '[.period_start, .model, .cost_usd] | @tsv'

Find stale keys​

Every key in a credentials list or read carries usage: last_used_at (the most recent request in the last 90 days, or null when there was none) and requests_30d (requests in the last 30 days, 0 when there were none). List the active keys of an app that nobody used in the last 90 days:

First page only (up to 50 keys); follow next_cursor for apps with more.

curl -s "$API/apps/Support%20Bot/keys" "${auth[@]}" \
| jq -r '.items[] | select(.status == "active" and .usage != null and .usage.last_used_at == null)
| [.key_id, .name, .created_at] | @tsv'

usage itself is null only when usage data is unavailable, so the filter skips those keys instead of reporting them as stale. To compare keys across every app over a longer range, group usage by key: GET /usage?from=2026-01-01&granularity=total&group_by=app,key&sort=requests. Revoke a stale key with Revoke a gateway credential.

Error and block rate by app​

usage_rows "from=$FROM&granularity=total&group_by=app,outcome" | jq -r '[.app, .outcome, .requests] | @tsv'

For why requests failed at a provider, read provider health.

Endpoints​

GET/usage

Read usage

Requests, outcomes, tokens and cost of every app in the tenant, over all time by default (`from` inclusive, `to` exclusive; a date `to` includes that whole UTC day). Rows are per period (`granularity`: total by default, or UTC month, week starting Monday, or day) and optionally per one or two dimensions: app, model, provider, user, key (gateway credential), source or outcome. Filters narrow every number, including totals, and `totals` always equals the sum of every row across all pages. Cost is the cost recorded with each request at request time; requests without one are counted in unpriced_requests instead of being estimated. Request counts exclude the background cache-refresh requests the gateway sends on its own, while tokens and cost include them because the provider bills them. Requests whose user identity was removed by data retention are kept with user null. Periods without traffic are omitted. Credential ids are recorded from 2026-10-16; earlier requests report key null. Grouping or filtering by key reads more data, so prefer a bounded range with it. Results can be up to 60 seconds old. Page with `limit` and `cursor` while the query stays the same; a cursor answers 409 `collection_changed` when the result was recomputed and differs (restart without cursor; a fixed `to` in the past keeps results stable). Rows group by the app's current name while it exists, otherwise the name recorded with its requests, and by the served model. `generated_at` says when the data was read and `data_through` the effective end of the range; `totals.priced_coverage_pct` says how much of the billable traffic has a recorded cost. Queries are limited to {QUERY_TIMEOUT_SECONDS} seconds and 100,000 result rows (422 `result_too_large`). 503 `query_timeout` when the query exceeds its time or read limits (narrow it and retry); 503 `data_unavailable` when the data cannot be read right now.

REQUIRESread
Path, query & header parameters 14
from (query)stringoptional

Inclusive start: a date (YYYY-MM-DD, UTC midnight) or an RFC 3339 timestamp with a timezone. Omitted: from the first recorded request.

to (query)stringoptional

Exclusive end: an RFC 3339 timestamp with a timezone, or a date (YYYY-MM-DD) to include that whole UTC day. Omitted: now.

granularity (query)stringoptional

Period of each row: total (one row per group), or UTC month, week (starting Monday) or day.

default: "total"
"total""month""week""day"
group_by (query)array<string>optional

Up to 2 of: app, model, provider, user, key, source, outcome. Repeat the parameter (group_by=a&group_by=b) or separate values with commas (group_by=a,b).

max items: 2unique items
Array item specification
string

Values: "app""model""provider""user""key""source""outcome"

app (query)stringoptional

Only these apps (names, case-insensitive). Comma-separated, at most 50 values.

model (query)stringoptional

Only these models. Comma-separated, at most 50 values.

provider (query)stringoptional

Only these provider labels. Comma-separated, at most 50 values.

user (query)stringoptional

Only these user emails (case-insensitive). Comma-separated, at most 50 values.

key (query)stringoptional

Only these gateway credential ids (key_id). Comma-separated, at most 50 values.

source (query)stringoptional

Only these sources: gateway, sdk, copilot_studio. Comma-separated, at most 50 values.

outcome (query)stringoptional

Only these outcomes: served, blocked, error, rate_limited. Comma-separated, at most 50 values.

sort (query)stringoptional

period: oldest period first, then highest cost. cost, requests, tokens: highest first.

default: "period"
"period""cost""requests""tokens"
limit (query)integeroptional

Page size: 1 to 5000, default 500.

min: 1max: 5000default: 500
cursor (query)stringoptional

next_cursor from the previous page; send the same other parameters.

No request body.

curl --request GET \
'https://guardrails-usa-1.quilr.ai/llmgateway/management/v1/usage?limit=500' \
--header 'Authorization: Bearer <management-key>'

Authentication, errors & retry rules Try it in the playground

  • Monitoring API - guardrail activity, provider health and single-request explanations.
  • Users API - per-person access, keys and usage.
  • Budgets API - enforce spend, request and token limits.
  • Log Export API - every request log line, for your own data platform.
  • Management API - authentication, errors and retry rules.