Ana içeriğe geç

Usage Reports Management API

Overview

The AI Budgets API answers what the ceilings are. This page answers the other half of the same question: how much of them is gone, and what a subject actually consumed in a period. It is the surface a FinOps owner automates against — chargeback exports, month-end reconciliation, "who is burning the budget" alerts — and it writes nothing.

Five endpoints, one response shape, five subjects: a consumer organization, a single consumer, a portal application, an API client, and an API proxy. Every address is under /apiops/projects/{projectName} and follows the shared Common Contract: the stable error envelope, correlation IDs, and the rule that a project outside the caller's scope is always 404 resourceNotFound, never 403. All five subjects are project-owned, so a call against the admin (platform) project is refused with 400 malformedRequest (params.reason = platformScopeNotSupported).

References are portable: a subject is addressed by its organization code, consumerKey, applicationKey, apiClientKey or API proxy name. No Mongo identifier, no traffic-log username and no quota identity value ever crosses the boundary.

Endpoints

MethodPathSubjectBreakdown
GET/credential-organizations/{organizationCode}/usageOne consumer organizationPer consumer
GET/consumers/{consumerKey}/usageOne consumer
GET/applications/{applicationKey}/usageOne portal applicationPer API client
GET/api-clients/{apiClientKey}/usageOne API client
GET/api-proxies/{apiProxyName}/usageOne API proxy

All five accept the same query parameters:

ParameterDefaultMeaning
from30 days before toStart of the period. ISO-8601 UTC: 2026-08-01, 2026-08-01T00:00:00Z or an offset date-time such as 2026-08-01T03:00:00+03:00
tonowEnd of the period, same spellings
modelevery modelExact LLM model id, e.g. gpt-5. Narrows the whole report to that model's requests, and keeps the scope-total budget rows so a per-model figure still has something to be measured against
environmentNameevery environment of the projectRestricts the traffic-log blocks to one environment

An inverted period (from after to), a period wider than 366 days, or an unparsable bound is refused with 400 malformedRequest rather than quietly corrected.

Response

{
"scope": { "type": "ORGANIZATION", "key": "acme-partners", "name": "acme-partners" },
"period": { "from": "2026-08-12T00:00:00Z", "to": "2026-09-11T00:00:00Z",
"effectiveFrom": "2026-08-12T00:00:00Z", "effectiveTo": "2026-09-11T23:59:59.999Z",
"timeZone": "UTC" },
"filters": { "model": null, "environmentName": null, "environmentApplied": false },
"ai": {
"dataSource": "ROLLUP_DAILY",
"granularity": "DAY",
"freshness": "hourly",
"totals": { "tokenInput": 4820113, "tokenOutput": 918204, "tokenCached": 120400,
"tokenTotal": 5858717, "costUsd": 214.382100, "requests": 20418, "failedRequests": 37 },
"byModel": [
{ "provider": "openai", "model": "gpt-5", "tokenInput": 4120113, "tokenOutput": 818204,
"tokenCached": 120400, "tokenTotal": 5058717, "costUsd": 198.210400, "requests": 17820 }
]
},
"aiLicensed": true,
"traffic": { "dataSource": "TRAFFIC_LOG", "totalRequests": 20418, "successfulRequests": 20381,
"blockedRequests": 0, "erroredRequests": 37,
"successRatePercent": 99.8, "avgTimeMs": 812.4 },
"children": {
"type": "CONSUMER",
"items": [ { "key": "con_8f3a1c", "name": "Ada Lovelace", "tokenTotal": 3120400,
"costUsd": 118.204000, "requests": 9820, "usagePercent": 53.3 } ],
"total": 14,
"truncated": false
},
"budgets": [
{ "family": "AI_COST", "unit": "USD", "window": "ONE_MONTH", "dimension": null,
"limit": 500.00, "consumed": 214.38, "remaining": 285.62, "usagePercent": 42.9,
"windowStartsAt": "2026-09-01T00:00:00Z", "resetsAt": "2026-10-01T00:00:00Z",
"assignmentKey": "lim_3d91", "planKey": "partner-monthly", "source": "TYPED" }
],
"budgetsAsOf": "2026-09-11T08:31:02Z",
"partial": false,
"unavailableEnvironments": [],
"notes": []
}

The three blocks, and where each comes from

BlockSourceWhat it covers
aiDeclared in ai.dataSource — see belowLLM token counts and cost in US dollars
trafficAlways the Elasticsearch traffic logGateway request counts, success rate, average total time
budgetsThe quota usage snapshotLive quota windows: ceiling, consumed, remaining, percentage, reset time

ai.dataSource is ROLLUP_DAILY for a consumer, an organization and an AI proxy. Those subjects are read from the hourly token-usage rollup — the same table the AI Reports screens and the quota percentages use. It is independent of the traffic log's index-lifecycle retention, so a question about last quarter answers correctly instead of quietly short. Its grain is a UTC calendar day, so period.effectiveFrom/effectiveTo widen from/to to whole days and tell you which days were actually measured.

ai.dataSource is TRAFFIC_LOG for an application and an API client. The rollup carries no application or API-client dimension, so those two are read from the traffic log at exact timestamps (granularity: EXACT). Two consequences worth planning around: their AI figures are bounded by however long the traffic log is retained, and traffic written before 2026.09.2 carries no client attribution fields, so it can be matched only through the application's own credential usernames — an API client that existed before that release shows no usage for the period before it.

budgets ignores from/to entirely. A quota window is a live thing; the block describes the windows that are open right now, with budgetsAsOf naming how fresh the snapshot is (it is refreshed about once a minute). remaining is max(limit - consumed, 0), so a counter that overshot its ceiling reports 0 rather than a negative number.

Reading the other fields

  • filters.environmentApplied is false when environmentName was sent but could not be applied to the ai block, because the daily rollup has no environment dimension. The traffic block is still narrowed. The flag exists so a narrowed request never looks like it narrowed something it did not.
  • children carries at most 200 rows, ordered by token total, and sets truncated: true whenever rows were dropped — by that 200-row cap, or by the traffic log's own bucket window before the report ever saw them. total is then a lower bound rather than an exact count, and the report is marked partial with the child-list-truncated note; when truncated is false, total is exact. usagePercent is each row's share of the whole scope — including rows that were dropped and traffic that carried no client key — so the kept rows keep their true weight and the answered rows add up to less than 100% rather than being renormalised. A child is keyed by its own public key (consumerKey, apiClientKey) — never by a traffic-log username.
  • partial is true when some source could not be read; unavailableEnvironments names the environments whose traffic log failed. The call still answers 200 with what was readable: a FinOps report that returns a clean 0 for a source it never reached is worse than one that says it could not look.
  • Attribution prerequisites. The typed client attribution fields (ack, cot, coi, coo) are stamped on a traffic-log document only when the request was authenticated by an OAuth2 or JWT policy that resolved a consumer or an API client (credential/ACL-managed mode). In managed from this policy mode with the client_credentials grant the caller is the proxy's own application key, so no consumer attribution exists; Basic, API-key and third-party-IdP JWT traffic is attributed by username (uok) only. The Manager API Traffic list projection does not return these fields — open the document detail (or read Elasticsearch directly) to verify attribution. A source that rejects the query (for example an index mapping that cannot parse the range) is reported as partial: true with the environment in unavailableEnvironments, never as silent zeros.
  • notes explains a zero or a gap in machine-readable form: no-members (the organization currently has no consumer), not-ai-proxy (the proxy is not an AI proxy, so ai is null), rollup-unavailable, member-list-truncated, child-list-truncated (the breakdown carries fewer rows than the scope has), model-list-truncated (more models were used than ai.byModel can carry — ai.totals still covers all of them), ai-module-not-licensed, client-attribution-since-2026.09.2.
  • aiLicensed reports whether the AI Gateway module is licensed on the installation. It is informational only — no block is withheld because of it; it simply explains why the AI figures are zero where the module is absent.

Budget rows

FieldMeaning
familyAI_TOKEN, AI_COST or REQUEST_QUOTA
unitTOKENS, USD or REQUESTS — matching the family
windowONE_HOUR, ONE_DAY or ONE_MONTH
dimension{ "type": "MODEL" | "PROVIDER", "ref": "..." } for a per-model / per-provider sub-limit, null for the scope total
assignmentKeyPublic handle of the limit assignment the counter accrues under, when there is one
planKeyKey of the limit plan that assignment references, when there is one
sourceTYPED (a limit assignment), LEGACY_BUDGET (an AI budget owner definition), POLICY (a summary of per-identity policy/rate-limit counters)

A POLICY row is a summary, one per policy window, with limit and consumed left null and usagePercent carrying the worst percentage observed. Such a counter is keyed by a request identity — an IP address, a header value, a full Authorization value — which the platform stores encrypted precisely because it may embed a secret. Listing those identities on an automation surface would publish that set, and summing per-identity ceilings would be a number that means nothing.

Errors

Every error follows the Common Contract's error envelope — branch on errorKey, never on message.

Error keyWhen
malformedRequestfrom is after to, the period is wider than 366 days, a date could not be parsed, or the admin (platform) project was addressed (params.reason = platformScopeNotSupported)
resourceNotFoundAn unknown organizationCode / consumerKey / applicationKey / apiClientKey / API proxy name, an environmentName that does not belong to the project, or a project outside the caller's scope
permissionDeniedAuthenticated, but the caller holds none of the categories this endpoint accepts

An unreachable Elasticsearch cluster or a failed rollup read is not an error — it answers 200 with partial: true. See Common Contract → Error keys for the complete closed set.

Permissions & Licensing

  • The four identity-shaped endpoints (organization, consumer, application, API client) accept the Identity or the AI Budgets permission at View. Either is enough: a budget owner and an identity owner both legitimately need this number, and neither should have to hold the other's category.
  • The API proxy endpoint accepts API Management or Analytics at View — a proxy's traffic is API-management data, and the analytics role already reads exactly these figures on the reports screens.
  • Every denial is the same 404 resourceNotFound, so a caller cannot learn which category it is missing.
  • No license module is required. The aiLicensed flag is reported for context only; no part of the response is withheld on an installation without the AI Gateway module.

Example

# 1. Last 30 days of an organization, with its per-consumer breakdown
curl "https://apinizer.example.com/apiops/projects/finance/credential-organizations/acme-partners/usage" \
-H "Authorization: Bearer $TOKEN"

# 2. One consumer, one calendar month, one model
curl "https://apinizer.example.com/apiops/projects/finance/consumers/con_8f3a1c/usage?from=2026-08-01&to=2026-08-31&model=gpt-5" \
-H "Authorization: Bearer $TOKEN"

# 3. An application and the API clients under it, PROD only
curl "https://apinizer.example.com/apiops/projects/finance/applications/app_2d7e9b/usage?environmentName=PROD" \
-H "Authorization: Bearer $TOKEN"

# 4. One API client
curl "https://apinizer.example.com/apiops/projects/finance/api-clients/cli_74b2e0/usage?from=2026-09-01" \
-H "Authorization: Bearer $TOKEN"

# 5. One AI proxy — tokens, cost and traffic together
curl "https://apinizer.example.com/apiops/projects/finance/api-proxies/chat-gateway/usage" \
-H "Authorization: Bearer $TOKEN"

A month-end chargeback export is the organization call per organization, reading ai.totals.costUsd and the children rows; a budget-alarm job is any of the five, reading the budgets rows and branching on usagePercent and resetsAt.

Rankings and per-API breakdowns

The five endpoints above describe one subject at a time. A portal page or a FinOps board usually needs the other two shapes as well: who spent the most this month, and which APIs did this consumer actually use. Three more read-only endpoints answer exactly those, under the same /apiops/projects/{projectName} address and with the same period rules, filters and dataSource declaration.

MethodPathAnswers
GET/usage/topThe period's ranking of one dimension by one metric, with each row's share of the total
GET/usage/consumers/{consumerKey}/api-proxiesWhich API proxies one consumer called, and how much
GET/usage/applications/{applicationKey}/api-proxiesWhich API proxies one application called, and how much

Ranking — GET /usage/top

ParameterDefaultMeaning
dimensionrequiredconsumers, applications, api-clients, models or api-proxies. Any other value is 400 malformedRequest (params.parameter = dimension)
metrictokenstokens, cost or requests. Decides both the ordering and what value / sharePercent mean
organizationevery organizationA consumer organization code; narrows the ranking to that organization's traffic
limit50Rows the answer may carry, at most 500. A value above the ceiling is clamped, not refused
from, to, model, environmentNameas aboveThe same period and filter rules as the five subject endpoints
{
"dimension": "consumers",
"metric": "cost",
"unit": "USD",
"period": { "from": "2026-08-12T00:00:00Z", "to": "2026-09-11T00:00:00Z",
"effectiveFrom": "2026-08-12T00:00:00Z", "effectiveTo": "2026-09-11T23:59:59.999Z",
"timeZone": "UTC" },
"filters": { "organizationCode": "acme-partners", "environmentApplied": false },
"dataSource": "ROLLUP_DAILY",
"freshness": "hourly",
"total": 1284.502310,
"limit": 50,
"truncated": false,
"items": [
{ "rank": 1, "key": "con_8f3a1c", "name": "Ayse Yilmaz", "value": 612.400000,
"sharePercent": 47.6, "tokenTotal": 18450233, "costUsd": 612.400000, "requests": 91204 },
{ "rank": 2, "key": "con_2b71de", "name": "billing-batch", "value": 388.102310,
"sharePercent": 30.2, "tokenTotal": 11002914, "costUsd": 388.102310, "requests": 40551 }
],
"partial": false,
"unavailableEnvironments": [],
"notes": []
}

Three properties of this answer are worth reading carefully, because a chart drawn from it depends on them:

  • total is the whole dimension, not the rows. It is summed over every matching row before limit is applied, and it also contains consumption that could not be attributed to a nameable row. The shares of a truncated answer therefore add up to less than 100%. They are floored to one decimal, never rounded, so the sum can never exceed 100 either.
  • truncated is the only honest signal that rows were left out. A full-looking list of limit rows is not evidence that nothing was dropped.
  • dataSource says which source was read. consumers, models and api-proxies come from the daily AI usage rollup — written hourly, kept independently of the traffic log's retention, and the same numbers the AI Reports screen shows. applications and api-clients have no rollup scope at all and are read from the traffic log, so they are bounded by its retention and, for traffic written before 2026.09.2, attributable only through the owner's own credentials (notes carries client-attribution-since-2026.09.2). On a rollup dimension metric=requests counts AI-attributed requests only, which the note requests-are-ai-attributed states.

A row whose source reference cannot be translated into a public key — an unresolved rollup reference, an application that has since been deleted — is not itemised; it stays inside total and notes carries unresolved-rows-dropped. This is deliberate: the reference may be a raw traffic-log username, which never crosses the API boundary.

Per-API breakdown — GET /usage/{consumers|applications}/{key}/api-proxies

Accepts from, to, model, environmentName and limit (default 50, at most 500).

{
"scope": { "type": "CONSUMER", "key": "con_8f3a1c", "name": "Ayse Yilmaz" },
"period": { "from": "2026-08-12T00:00:00Z", "to": "2026-09-11T00:00:00Z", "timeZone": "UTC" },
"trafficDataSource": "TRAFFIC_LOG",
"aiDataSource": "ROLLUP_DAILY",
"limit": 50,
"truncated": false,
"items": [
{ "proxyName": "chat-gateway", "requests": 84120, "failedRequests": 412,
"successRatePercent": 99.5, "avgTimeMs": 812.4,
"ai": { "tokenTotal": 18450233, "costUsd": 612.400000 } },
{ "proxyName": "petstore", "requests": 7084, "failedRequests": 0,
"successRatePercent": 100.0, "avgTimeMs": 41.2 }
],
"partial": false,
"notes": []
}

ai is absent, not a row of zeros, on a proxy that carried no AI traffic for this subject — a block of zeros would read as "an AI proxy nobody used". For a consumer the AI half comes from the rollup, so a proxy whose traffic has already aged out of the log still appears with its token and cost picture and requests: 0. For an application both halves come from the traffic log, which is why that answer carries the attribution note.

Errors and permissions

The error envelope, the 404 for a project outside the caller's scope and the 400 malformedRequest for the admin project are exactly those of the five endpoints above. dimension and metric add one case each: an unknown value is 400 malformedRequest with params.parameter naming which one. Authorization is decided before the dimension is validated, so a caller outside the project always receives the same 404 and learns nothing from the difference.

Permissions follow the same pairing: dimension=api-proxies accepts API Management or Analytics at View; every other dimension and both breakdown endpoints accept Identity or AI Budgets at View.

Examples

# Top 10 consumers by cost this month, inside one organization
curl "https://apinizer.example.com/apiops/projects/finance/usage/top?dimension=consumers&metric=cost&organization=acme-partners&from=2026-09-01&limit=10" \
-H "Authorization: Bearer $TOKEN"

# Which models the project spent its tokens on
curl "https://apinizer.example.com/apiops/projects/finance/usage/top?dimension=models&metric=tokens" \
-H "Authorization: Bearer $TOKEN"

# Busiest API clients, PROD only
curl "https://apinizer.example.com/apiops/projects/finance/usage/top?dimension=api-clients&metric=requests&environmentName=PROD" \
-H "Authorization: Bearer $TOKEN"

# Which APIs one consumer used
curl "https://apinizer.example.com/apiops/projects/finance/usage/consumers/con_8f3a1c/api-proxies?from=2026-09-01" \
-H "Authorization: Bearer $TOKEN"

# Which APIs one application used
curl "https://apinizer.example.com/apiops/projects/finance/usage/applications/app_2d7e9b/api-proxies" \
-H "Authorization: Bearer $TOKEN"

The endpoints above answer one figure at a time. A page embedded in a customer portal or a FinOps board usually needs three more shapes: how did this move over the period, how does a group break down, and who is about to run out. Three read-only views answer exactly those, under the same address as the subject they describe — the summary card is .../usage, and each view appends its own segment.

MethodPathAnswers
GET/{collection}/{key}/usage/timeseriesThe period trend of one subject, bucketed by day, week or month
GET/credential-organizations/{organizationCode}/usage/hierarchyThe sub-organization tree with rolled-up totals, or one page of the organization's consumers
GET/{organizations|applications}/{key}/usage/budget-boardOne row per direct child, carrying its live quota window closest to the limit

{collection} is the same set as above: credential-organizations, consumers, applications, api-clients, api-proxies.

Trend — GET .../usage/timeseries

ParameterDefaultMeaning
from, tolast 30 daysAs above. In addition, the period may not span more than 366 UTC calendar days — every source here is grained by whole days
granularitydayday, week or month
includeai,trafficWhich blocks to fill. A block that was not requested is answered as null, never as 0
model, environmentNameAs above
{
"scope": { "type": "CONSUMER", "key": "con_8f3a1c", "name": "Ada Lovelace" },
"period": { "from": "2026-09-01T00:00:00Z", "to": "2026-09-04T00:00:00Z",
"effectiveFrom": "2026-09-01T00:00:00Z", "effectiveTo": "2026-09-04T23:59:59.999Z",
"timeZone": "UTC" },
"filters": { "model": null, "environmentName": null, "environmentApplied": false },
"granularity": "DAY",
"aiDataSource": "ROLLUP_DAILY",
"aiFreshness": "hourly",
"trafficDataSource": "TRAFFIC_LOG",
"series": [
{ "bucketStart": "2026-09-01T00:00:00Z", "tokenInput": 120400, "tokenOutput": 24100,
"tokenCached": 0, "tokenTotal": 144500, "costUsd": 5.412000,
"aiRequests": 812, "aiFailedRequests": 3,
"requests": 840, "failedRequests": 5, "avgTimeMs": 742.1 },
{ "bucketStart": "2026-09-02T00:00:00Z", "tokenInput": 0, "tokenOutput": 0,
"tokenCached": 0, "tokenTotal": 0, "costUsd": 0.000000,
"aiRequests": 0, "aiFailedRequests": 0,
"requests": 0, "failedRequests": 0, "avgTimeMs": null }
],
"total": 4,
"partial": false,
"unavailableEnvironments": [],
"notes": []
}

Every bucket of the period is present, including the quiet ones — the example above lists the first two of the period's four daily buckets, which is why total reads 4. A source can only report the days it actually holds documents for; the series is therefore generated from the requested window and filled with whatever came back. That is what makes 0 and unread two different answers: a zero bucket means the day existed and nothing happened, while a source that could not be read sets partial: true and names the environment in unavailableEnvironments. avgTimeMs is null rather than 0 for a bucket with no request — an average that was never measured is not zero.

A week starts on Monday and a month on the 1st, in UTC (ISO-8601, the same convention Elasticsearch uses). A period that begins mid-week therefore opens with a bucket whose bucketStart is the preceding Monday; that bucket carries only the days inside the period, so a chart must label it as the calendar week it belongs to rather than as a whole week of traffic.

The AI block and the traffic block count different things. tokenTotal / costUsd / aiRequests are LLM consumption; requests, failedRequests and avgTimeMs are gateway traffic. An AI proxy that also fronts non-AI endpoints has traffic without tokens, which is why the two are never merged into a single requests field. aiDataSource declares where the AI half came from, exactly as ai.dataSource does on the summary card — ROLLUP_DAILY for a consumer, an organization and an AI proxy; TRAFFIC_LOG for an application and an API client.

Hierarchy — GET /credential-organizations/{code}/usage/hierarchy

ParameterDefaultMeaning
from, tolast 30 daysAs above, capped at 366 calendar days
levelorganizationorganization for the subtree, consumer for the paged consumer list
includeDescendantsfalseWith level=consumer, also list the consumers of descendant organizations
page, size0, 20 (max 200)Paging of the consumer list
{
"scope": { "type": "ORGANIZATION", "key": "acme-partners", "name": "acme-partners" },
"level": "ORGANIZATION",
"dataSource": "ROLLUP_DAILY",
"freshness": "hourly",
"nodes": [
{ "code": "acme-partners", "name": "Acme Partners", "classification": "ORGANIZATION",
"depth": 0, "shared": false, "memberCount": 3, "descendantMemberCount": 11,
"direct": { "tokenTotal": 144500, "requests": 812, "failedRequests": 3, "costUsd": 5.412000 },
"rollup": { "tokenTotal": 982400, "requests": 5140, "failedRequests": 21, "costUsd": 38.104000 },
"budgetPercent": 49.1,
"children": [ { "code": "acme-emea", "classification": "ORGANIZATION", "depth": 1, "…": "…" } ] }
],
"partial": false,
"notes": []
}

At every node rollup equals direct plus the sum of the children's rollup. That is the property the whole view exists for: an owner reads a parent row, expands it, and the children must add up. direct is what the organization's own consumers spent; rollup is the whole subtree.

Nodes are addressed by organization code, never by an internal identifier. classification says what a node is: a real ORGANIZATION, or one of the two synthetic buckets the report produces for consumption that belongs to no organization (UNASSIGNED) and for traffic that carried no consumer identity at all (ANONYMOUS). Those buckets exist so that no spend is silently dropped from the totals.

With level=consumer the answer carries members instead of nodes:

{
"level": "CONSUMER",
"members": {
"content": [
{ "consumerKey": "con_8f3a1c", "name": "Ada Lovelace", "organizationCode": "acme-partners",
"organizationPath": "Acme / Partners",
"usage": { "tokenTotal": 144500, "requests": 812, "failedRequests": 3, "costUsd": 5.412000 },
"budgetPercent": 28.9 }
],
"totalElements": 11, "page": 0, "size": 20
}
}

A consumer that no longer resolves — a deleted account, or a usage row the rollup could only file under a username — keeps its figures and answers consumerKey: null with the note unresolved-members. The figures stay so the members still add up to the organization; the key is withheld because the only identifier left is a traffic-log username, which never crosses this boundary.

The figures come from the hourly token rollup, which has no environment dimension, so this endpoint accepts no environmentName and filters.environmentApplied is always false. budgetPercent is filled only when the requested period sits inside a single calendar month — a monthly ceiling cannot be compared against a multi-month window.

Budget board — GET .../usage/budget-board

Available for an organization (rows are its consumers) and for an application (rows are its API clients).

ParameterDefaultMeaning
page, size0, 20 (max 200)Paging
sortpercent,descpercent[,asc|desc]; name is always accepted as the tie-breaker
{
"scope": { "type": "ORGANIZATION", "key": "acme-partners", "name": "acme-partners" },
"childType": "CONSUMER",
"summary": { "total": 11, "ok": 7, "warning": 2, "exceeded": 1, "noBudget": 1 },
"items": [
{ "key": "con_8f3a1c", "name": "Ada Lovelace",
"family": "AI_COST", "unit": "USD", "window": "ONE_MONTH",
"limit": 500.00, "consumed": 512.40, "remaining": 0.00, "percent": 102.5,
"windowStartsAt": "2026-09-01T00:00:00Z", "resetsAt": "2026-10-01T00:00:00Z",
"assignmentKey": "lim_3d91", "planKey": "partner-monthly", "source": "TYPED",
"status": "EXCEEDED" }
],
"totalElements": 11, "page": 0, "size": 20,
"asOf": "2026-09-11T08:31:02Z",
"notes": []
}

The board is independent of from/to and accepts neither: these are the quota windows the gateway is counting into right now, and asOf says how far behind the snapshot is (about a minute).

One row per child, carrying its worst live window. A child may own several at once — a monthly token ceiling, a monthly cost ceiling, a daily request quota — and the row shows whichever is closest to its limit, with family and unit naming the number. Without them a bare 512.40 could be dollars, tokens or requests.

status is OK, WARNING at 80% or more, EXCEEDED at 100% or more, and NO_BUDGET when the child has no live ceiling at all. A child with no ceiling is still a row rather than a gap — a board that hid it would quietly suggest it was fine — and summary counts every child, not only the page that was answered, so an owner can reconcile the counters against his own expectation before paging through the rows.

Two kinds of counter are deliberately not eligible for a board row. A per-model sub-limit is a slice of the child's budget rather than the budget, and ranking children by a slice would put a child that is 90% through one model above a child that is 99% through everything. And a policy or rate-limit counter is keyed by a request identity — an IP address, a header value, a whole Authorization value — which the platform stores encrypted precisely because it may embed a secret; such a counter describes a caller, not the child, and never reaches this board.

Examples

# Weekly trend of one consumer, tokens only
curl "https://apinizer.example.com/apiops/projects/finance/consumers/con_8f3a1c/usage/timeseries?from=2026-07-01&to=2026-09-30&granularity=week&include=ai" \
-H "Authorization: Bearer $TOKEN"

# Daily traffic trend of an AI proxy in one environment
curl "https://apinizer.example.com/apiops/projects/finance/api-proxies/chat-gateway/usage/timeseries?granularity=day&include=traffic&environmentName=PROD" \
-H "Authorization: Bearer $TOKEN"

# The organization tree for last month
curl "https://apinizer.example.com/apiops/projects/finance/credential-organizations/acme-partners/usage/hierarchy?from=2026-08-01&to=2026-08-31" \
-H "Authorization: Bearer $TOKEN"

# Its consumers, including every sub-organization, second page
curl "https://apinizer.example.com/apiops/projects/finance/credential-organizations/acme-partners/usage/hierarchy?level=consumer&includeDescendants=true&page=1&size=50" \
-H "Authorization: Bearer $TOKEN"

# Who is about to run out, worst first
curl "https://apinizer.example.com/apiops/projects/finance/credential-organizations/acme-partners/usage/budget-board" \
-H "Authorization: Bearer $TOKEN"

# The API clients of one application, alphabetically
curl "https://apinizer.example.com/apiops/projects/finance/applications/app_2d7e9b/usage/budget-board?sort=name,asc" \
-H "Authorization: Bearer $TOKEN"

A portal chart is the trend call per subject; a FinOps drill-down is the hierarchy call, expanding a node into level=consumer; a "who is about to run out" alarm is the budget board, reading summary.exceeded and summary.warning before it ever pages through items.

Errors and permissions

These three views share the summary card's gate exactly — the same permission pairs, the same 404 for every denial, the same 400 malformedRequest for the admin project. They add three more malformedRequest cases: an unknown granularity, an unknown token in include, and an unknown level. A misspelled parameter is refused rather than silently ignored, because a caller who believes his filter worked reads the wrong number. An unknown sort column on the budget board is unsupportedSortField.

  • AI Budgets API - The ceilings themselves: scope rules, hierarchy, effective limit, and the budget update
  • Limit Assignments Management API - The assignment whose public handle appears as assignmentKey on a budget row
  • Consumers & Applications Management API - The owners addressed by consumerKey and applicationKey on this page
  • API Clients API - The client addressed by apiClientKey, and its migration from a legacy credential
  • Reports API - Inventory reports (what exists) as opposed to the consumption reports on this page
  • Common Contract - The shared error envelope, correlation IDs, and status-code rules every endpoint on this page follows