Usage Reporting
Read-only endpoints that surface credit consumption for the admin Usage
dashboard. 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 (empty) credit pool back 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 timestamp.- 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-15T16:20:00.000Z",
"resourceType": "agent_run",
"totalCredits": "20.0000"
},
{
"date": "2026-01-15T16:20:00.000Z",
"resourceType": "app_compute",
"totalCredits": "50.0000"
},
{
"date": "2026-01-15T16:20:00.000Z",
"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-06",
"resourceType": "agent_run",
"totalCredits": "20.0000",
"totalEvents": 1
},
{
"month": "2026-06",
"resourceType": "ai",
"totalCredits": "130.0000",
"totalEvents": 2
},
{
"month": "2026-06",
"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.- 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,
"totalEvents": 2
},
{
"entityId": "00000000-0000-4000-8000-000000000002",
"entityType": "app",
"name": "Sales Dashboard",
"slug": "sales-dashboard",
"totalCredits": 0.25,
"totalEvents": 2
}
],
"topUsers": [
{
"actor": "marcus.webb",
"actorType": "user",
"displayName": "Marcus Webb",
"totalCredits": 0.25,
"totalEvents": 2
},
{
"actor": "annie.larson",
"actorType": "user",
"displayName": "Annie Larson",
"totalCredits": 0.25,
"totalEvents": 2
}
]
}
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 |