Recording consumption
The consumption API lets backend services record arbitrary numeric usage metrics (API calls, bytes stored, seats used, etc.) per user per application. Aggregates are stored in a dedicated table and can be read back — or reset — via the API. A key that prices a metered feature of the user's plan is handled differently, see Metered keys.
Authentication
The consumption endpoints (record and read) accept two forms of authentication:
client_credentialsaccess token — aBearertoken issued by the OAuthclient_credentialsgrant for the application. This is the recommended approach for server-to-server calls. The token is bound to its own application: using it for another application'sapplicationIdreturns403 AUTH_011. A token bound to a user is refused with403 CONS_004, and an invalid or expired token with401 AUTH_001.- Admin session cookie — an active session with the
adminorsuperadminrole. It may act on any application.
Any other request receives 403 CONS_004. Resetting a counter is admin-session only (see below).
Record consumption
POST /api/consumption
Authorization: Bearer <access_token>
Content-Type: application/json
{
"applicationId": "<app-uuid>",
"userId": "<user-id>",
"key": "api.calls",
"value": 10
}
| Field | Type | Description |
|---|---|---|
applicationId | string (UUID) | Target application |
userId | string | Target user |
key | string | Metric name — alphanumeric plus dots (1–64 chars), e.g. api.calls, storage.bytes. An invalid key returns 400 CONS_001 |
value | number | Amount to add — must be a finite number (positive or negative), otherwise 400 CONS_002 |
idempotencyKey | string | Optional retry key (1–200 chars). Honoured for metered keys only |
A raw entry (in consumptionEntries) is inserted and the running aggregate (in consumptionAggregates) is updated with an ON CONFLICT … DO UPDATE upsert, in two successive statements. Returns 404 CONS_003 if the user has no access record for the application. The response is:
{ "success": true, "aggregate": { "key": "api.calls", "total": "1540" } }
Read aggregates
GET /api/consumption/:userId/:appId
Returns the latest aggregate for every key the user has recorded for the application:
{
"aggregates": [
{ "key": "api.calls", "total": "1540", "updatedAt": "2026-04-12T10:00:00Z" },
{ "key": "storage.bytes", "total": "52428800", "updatedAt": "2026-04-11T15:30:00Z" }
]
}
totalis returned as a string because the underlying column usesnumeric(arbitrary precision). Parse withparseFloat()or a decimal library.
A single key can be read with GET /api/consumption/:userId/:appId/:key, which returns { "aggregate": { "key", "total", "updatedAt" } }, or 404 CONS_003 when nothing was recorded. A malformed identifier returns 400 CONS_005.
Administrators can also read all users of an application with GET /api/admin/applications/:id/consumption ({ "entries": [...] } with user name, email, plan id, key, total and updatedAt), and the entries of one calendar month, grouped by user and key, with GET /api/admin/applications/:appId/consumption/monthly?year=2026&month=10 (year and month default to the current ones; the month boundaries use the server's local time zone). In the admin interface these are shown on the Consumption tab of an application.
Metered keys
When the key is the featureKey of a metered price on the plan of the user's active subscription, the report is not stored in the counters above. Instead it is recorded as metered usage for the current UTC month, subject to the plan's cap, and, when Stripe is configured, pushed to Stripe as a meter event (see the Stripe integration guide). In that case:
valuemust be greater than zero (400 BIL_002otherwise);- a report that would take the month's total past
usageLimitis refused with403 BIL_003and not recorded (-1means no cap); - the same
idempotencyKeyreported twice counts once; without one, every call counts; - the response is
{ "success": true, "metered": true, "aggregate": { "key", "total" } }, wheretotalis the month's usage; GETandDELETEon/api/consumption/...do not see this usage. Read it with the application billing API.
Reset a counter
Admin only (requires an admin session — client_credentials tokens are not accepted for DELETE):
DELETE /api/consumption/:userId/:appId/:key
Deletes all raw entries and the aggregate row for the given userId + appId + key combination and returns 204.
Managing plans
Create per-application subscription plans with feature flags and optional Stripe billing.
Custom templates
Override the login, register, verify-email, email-verified, select-org, two-factor, device, error and delegated billing pages (subscribe, invoices, billing-usage) per application, and the transactional emails globally, using the TEMPLATES_DIR environment variable.