Usage Reporting
Read-only endpoints that surface credit consumption for the admin Usage
dashboard and the in-app usage banners. All consumption is recorded in the append-only usage_event ledger
and aggregated on read.
Every endpoint on this page is a plain GET that returns a JSON object or
array, so each one answers 200. None of them are AI-gated: AI does not need to
be enabled on the tenant. They read the local entitlement cache (a
findOne/create, with no License Manager round-trip), so a tenant that has
never configured AI still gets a valid answer (an empty credit pool, or no
alert) rather than an error.
Every metered activity appends one row to usage_event with the actor, resource
type, credits charged, and resource-specific metadata. AI events are mirrored
into the ledger from the AI audit log so all resource types share a single read
path. The reporting endpoints below aggregate this ledger.
GET /api/usage
Aggregated credit usage for a date range, plus current budgets, credit pool, and License Manager sync health. Defaults to the current month when no range is supplied.
Authentication: Required
Pre-blocks: permission.billing.manage
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
startDate | ISO date | start of current month | Range start (inclusive) |
endDate | ISO date | now | Range end (inclusive) |
Response:
A JSON object { usageSummary, dailyTotals, budgets, creditPool, sync } (with a
HAL _links.self).
usageSummaryrows group by(entityId, entityType, resourceType)and carrytotalCreditsandtotalEvents, ordered byentityIdthenresourceType.totalCreditsis serialized as a fixed-precision string (for example"50.0000").dailyTotalsrows are{ date, resourceType, totalCredits }, wheredateis the bucket day as a bareYYYY-MM-DDstring.- Each
budgetsentry carries the stored budget columns (monthlyBudget,creditsUsed) plus the computed gettersisOverBudget,budgetPercent, andbudgetRemaining, and anentitysub-object ({ name, slug, ownerId }) enriched from the App row. Theentityisnullfor non-app budgets or for a budget whoseentityIdno longer matches an App. creditPoolis the normalized pool with the internalslushfield stripped.syncsurfaces License Manager connectivity.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/usage"
}
},
"usageSummary": [
{
"entityId": "00000000-0000-4000-8000-000000000001",
"entityType": "app",
"resourceType": "agent_run",
"totalCredits": "20.0000",
"totalEvents": 1
},
{
"entityId": "00000000-0000-4000-8000-000000000001",
"entityType": "app",
"resourceType": "ai",
"totalCredits": "50.0000",
"totalEvents": 1
},
{
"entityId": "00000000-0000-4000-8000-000000000002",
"entityType": "app",
"resourceType": "ai",
"totalCredits": "80.0000",
"totalEvents": 1
},
"… 1 more items (4 total) — omitted from docs"
],
"dailyTotals": [
{
"date": "2026-01-15",
"resourceType": "agent_run",
"totalCredits": "20.0000"
},
{
"date": "2026-01-15",
"resourceType": "app_compute",
"totalCredits": "50.0000"
},
{
"date": "2026-01-15",
"resourceType": "ai",
"totalCredits": "130.0000"
}
],
"budgets": [
{
"entityId": "00000000-0000-4000-8000-000000000001",
"entityType": "app",
"monthlyBudget": 2000,
"creditsUsed": 1250.5,
"isOverBudget": false,
"budgetPercent": 62.525,
"budgetRemaining": 749.5,
"entity": {
"name": "Sales Dashboard",
"slug": "sales-dashboard",
"ownerId": "admin"
}
}
],
"creditPool": {
"included": 0,
"used": 0,
"remaining": 0
},
"sync": {
"syncedAt": null,
"lastSyncFailedAt": null,
"lastSyncFailureCode": null,
"stalenessLevel": "expired"
}
}
The sync block lets the UI render a "license server unreachable" banner.
lastSyncFailedAt is null when the most recent sync succeeded;
lastSyncFailureCode is one of lm_unreachable, lm_unavailable, lm_error,
or license_not_recognized. stalenessLevel reflects how current the cached
entitlement is (for example expired when the tenant has never completed a
sync).
GET /api/usage/monthly
Credit usage grouped by month and resource type, for the historical trend chart.
Authentication: Required
Pre-blocks: permission.billing.manage
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
months | integer (1-24) | 12 | Number of trailing months to include |
Response:
A JSON object { history, creditPool } (with a HAL _links.self). Each
history row is { month, resourceType, totalCredits, totalEvents }, where
month is a YYYY-MM string and totalCredits is a fixed-precision string.
Rows are ordered by month descending, then resourceType. creditPool is the
same normalized pool returned by GET /api/usage.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/usage/monthly"
}
},
"history": [
{
"month": "2026-09",
"resourceType": "agent_run",
"totalCredits": "20.0000",
"totalEvents": 1
},
{
"month": "2026-09",
"resourceType": "ai",
"totalCredits": "130.0000",
"totalEvents": 2
},
{
"month": "2026-09",
"resourceType": "app_compute",
"totalCredits": "50.0000",
"totalEvents": 1
}
],
"creditPool": {
"included": 0,
"used": 0,
"remaining": 0
}
}
GET /api/usage/top-consumers
Top apps and top users (or agents) by credit consumption over a rolling window.
Authentication: Required
Pre-blocks: permission.billing.manage
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
days | integer (1-365) | 30 | Rolling window size |
limit | integer (1-50) | 10 | Max rows per list |
Response:
A JSON object { windowDays, startDate, endDate, topApps, topUsers } (with a HAL
_links.self). startDate and endDate are bare YYYY-MM-DD strings bounding
the window.
topAppsrows enrichnameandslugfrom the App row; both arenullwhen no App matches theentityId.topUsersrows resolvedisplayNamefrom the User row, falling back to the actor id when no User matches.- Rows in both lists carry
byCategory, the row'stotalCreditssplit intoai,compute, andstoragebuckets by resource-type category. Usage whose resource type is no longer in the registry counts intotalCreditsbut in no bucket, so the segments can sum below the total. - Both lists are ordered by
totalCreditsdescending.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/usage/top-consumers"
}
},
"windowDays": 30,
"startDate": "2026-01-15",
"endDate": "2026-01-15",
"topApps": [
{
"entityId": "00000000-0000-4000-8000-000000000001",
"entityType": "app",
"name": "Ops Monitor",
"slug": "ops-monitor",
"totalCredits": 0.25,
"byCategory": {
"ai": 80,
"compute": 50,
"storage": 0
},
"totalEvents": 2
},
{
"entityId": "00000000-0000-4000-8000-000000000002",
"entityType": "app",
"name": "Sales Dashboard",
"slug": "sales-dashboard",
"totalCredits": 0.25,
"byCategory": {
"ai": 70,
"compute": 0,
"storage": 0
},
"totalEvents": 2
}
],
"topUsers": [
{
"actor": "marcus.webb",
"actorType": "user",
"displayName": "Marcus Webb",
"totalCredits": 0.25,
"byCategory": {
"ai": 80,
"compute": 50,
"storage": 0
},
"totalEvents": 2
},
{
"actor": "annie.larson",
"actorType": "user",
"displayName": "Annie Larson",
"totalCredits": 0.25,
"byCategory": {
"ai": 70,
"compute": 0,
"storage": 0
},
"totalEvents": 2
}
]
}
GET /api/usage/alert-status
The credit pool alert the in-app banners show billing admins, in the classic
interface and in Informer GO. It applies the Usage Alert "Notify when" switches
(billingConfig.alerts.thresholds) to the cached pool balance, so it answers
with no License Manager call and is cheap to poll.
Authentication: Required
Pre-blocks: permission.billing.manage
Response:
A JSON object { state, percentUsed } (with a HAL _links.self).
| Field | Description |
|---|---|
state | near-limit, exhausted, safety-buffer, or null when there is nothing to show |
percentUsed | Share of the pool consumed, 0 to 100 to one decimal place; null when no pool has been reported or the pool's grant is zero (state can still be exhausted) |
near-limitstarts at the lowest of the80,90, and95thresholds that is switched on (a switch that was never saved counts as on). With all three off there is nonear-limitstate.exhausted(no credits remaining) andsafety-buffer(no paid credits remaining, safety buffer still covering usage) are reported whatever the switches say, because features stop or are about to. The buffer amount itself is never exposed.stateis alwaysnullwhen the installation never draws from the credit pool.
GET /api/usage/resource-types
The canonical resource type registry. Read-only: types are defined in code and cannot be created or edited.
Authentication: Required
Pre-blocks: permission.tenant.superuser
This endpoint is gated by permission.tenant.superuser, not
permission.billing.manage, because the registry is system metadata rather than
billing data. A billing manager who is not a superuser cannot read it.
Response:
A plain JSON array of { slug, name, category, mode, unit } sourced from the
in-code resource-type registry. There is no database table behind it, so there
is nothing to create or edit.
[
{
"slug": "ai",
"name": "AI",
"category": "ai",
"mode": "event",
"unit": "dollar"
},
{
"slug": "agent_run",
"name": "Agent Run",
"category": "ai",
"mode": "event",
"unit": "run"
},
{
"slug": "app_compute",
"name": "App Compute",
"category": "compute",
"mode": "event",
"unit": "second"
},
"… 5 more items (8 total) — omitted from docs"
]
| Field | Description |
|---|---|
slug | Stable identifier used on usage events |
name | Display name |
category | ai, compute, or storage |
mode | event (per-occurrence) or gauge (point-in-time) |
unit | Unit of the raw measurement before rate application |