Skip to main content

Entity Budgets

An entity budget caps the monthly credit spend of a single App or Dataset, independent of any per-actor usage profile cap. Budgets reset at the start of each calendar month.

Both budget routes resolve the row through a find-or-create, so a budget exists for an App or Dataset the moment you first read or write it. There is no separate "create budget" endpoint. The entityType is a tight enum: only app and dataset are accepted, and the entityId must be a UUID.

Budget fields

The response body is the serialized budget: the stored columns plus four computed getters serialized alongside them.

FieldTypeDescription
idstring (UUID)Budget row id
entityIdstring (UUID)The App or Dataset id
entityTypestringapp or dataset
monthlyBudgetinteger | nullMonthly cap in credits. null = no cap
creditsUsedstringCredits consumed in the current period. A DECIMAL serialized as a string (for example "0.00")
periodStartdateStart of the current monthly period
hasBudgetbooleanComputed. true when monthlyBudget is set
budgetRemainingnumber | nullComputed. monthlyBudget - creditsUsed, or null when uncapped
budgetPercentnumberComputed. Percent of budget consumed (0-100)
isOverBudgetbooleanComputed. true once creditsUsed >= monthlyBudget
createdAt / updatedAtdateTimestamps

The response is a HAL object with a _links.self and a tenant field.


GET /api/usage-budgets/{entityType}/{entityId}

Get the budget for an App or Dataset. The row is created on first access via find-or-create, with monthlyBudget set to null and periodStart at the start of the current month. A second call returns the same row.

Authentication: Required

Pre-blocks: permission.tenant.superuser

Path Parameters:

ParameterTypeDescription
entityTypestringapp or dataset
entityIdstring (UUID)The entity's id

Response:

Responds 200 with the budget. On first access the getters report the uncapped state: hasBudget is false, budgetRemaining is null, budgetPercent is 0, and isOverBudget is false.

Get an entity budgetGET /api/usage-budgets/app/00000000-0000-4000-8000-000000000001
GET /api/usage-budgets/{entityType}/{entityId} returns the budget for an app or dataset, creating the row on first access via findOrCreate (monthlyBudget=null, periodStart = start of the current month). Requires superuser. The body is the stored columns plus four computed getters: hasBudget, budgetRemaining, budgetPercent, isOverBudget. creditsUsed is a DECIMAL serialized as a string. entityType is limited to app|dataset.
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/usage-budgets/app/00000000-0000-4000-8000-000000000001"
}
},
"hasBudget": false,
"budgetRemaining": null,
"budgetPercent": 0,
"isOverBudget": false,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"creditsUsed": "0.00",
"entityId": "00000000-0000-4000-8000-000000000001",
"entityType": "app",
"periodStart": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"monthlyBudget": null
}
Captured from the API examples test suite; ids and timestamps are normalized.
Entity type is limited to app or dataset

entityType is a tight enum. Any other value fails Joi param validation with a 400 before the handler runs, so budgets cannot be created for arbitrary entity types.

Reject an unsupported entity typeGET /api/usage-budgets/report/00000000-0000-4000-8000-000000000001
entityType is a tight enum (app|dataset). Any other value fails joi param validation with a 400 before the handler runs — budgets cannot be created for arbitrary entity types.
Response · 400
{
"statusCode": 400,
"error": "Bad Request",
"message": "\"entityType\" must be one of [app, dataset]",
"validation": {
"source": "params",
"keys": [
"entityType"
]
},
"data": {
"defaultError": {
"data": null,
"isBoom": true,
"isServer": false,
"output": {
"statusCode": 400,
"payload": {
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid request params input"
},
"headers": {}
}
}
}
}
Captured from the API examples test suite; ids and timestamps are normalized.

PUT /api/usage-budgets/{entityType}/{entityId}

Set or clear the monthly budget for an App or Dataset. The find-or-create resolves (or creates) the row, then updates monthlyBudget. Runs in a database transaction.

Authentication: Required

Pre-blocks: permission.usage.write (delegates to the billingManage global permission; superusers pass via the fallback)

Path Parameters:

ParameterTypeDescription
entityTypestringapp or dataset
entityIdstring (UUID)The entity's id

Request Body:

FieldTypeRequiredDescription
monthlyBudgetinteger | nullNoMonthly cap in credits (must be >= 0), or null to remove the cap. Omitting the field is accepted as a no-op.

Example Request:

{ "monthlyBudget": 2000 }

Response:

Responds 200 with the updated budget (the handler returns the row directly; it does not use .created(), so the status is 200, not 201). With a cap set, the getters update: hasBudget becomes true, and budgetRemaining and budgetPercent reflect creditsUsed.

Set a monthly budgetPUT /api/usage-budgets/app/00000000-0000-4000-8000-000000000001
PUT /api/usage-budgets/{entityType}/{entityId} sets the monthly cap. ensureBudget resolves (or creates) the row, then updates monthlyBudget. Payload is { monthlyBudget: integer >= 0 | null }. Runs in a DB transaction. Returns the updated budget with 200 (NOT .created()/201). With a cap set, the getters update: hasBudget=true, budgetRemaining and budgetPercent reflect creditsUsed.
Request body
{
"monthlyBudget": 2000
}
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/usage-budgets/app/00000000-0000-4000-8000-000000000001"
}
},
"hasBudget": true,
"budgetRemaining": 2000,
"budgetPercent": 0,
"isOverBudget": false,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"entityId": "00000000-0000-4000-8000-000000000001",
"entityType": "app",
"monthlyBudget": 2000,
"creditsUsed": "0.00",
"periodStart": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
Captured from the API examples test suite; ids and timestamps are normalized.

Send monthlyBudget: null to remove the cap. The getters then go uncapped: hasBudget is false, budgetRemaining is null, budgetPercent is 0, and isOverBudget is false.

{ "monthlyBudget": null }
Clear a monthly budgetPUT /api/usage-budgets/dataset/00000000-0000-4000-8000-000000000001
Send monthlyBudget=null to remove the cap. The getters then go uncapped: hasBudget=false, budgetRemaining=null, budgetPercent=0, isOverBudget=false.
Request body
{
"monthlyBudget": null
}
Response · 200
{
"_links": {
"self": {
"href": "https://informer.example.com/api/usage-budgets/dataset/00000000-0000-4000-8000-000000000001"
}
},
"hasBudget": false,
"budgetRemaining": null,
"budgetPercent": 0,
"isOverBudget": false,
"tenant": "acme",
"id": "00000000-0000-4000-8000-000000000002",
"creditsUsed": "0.00",
"entityId": "00000000-0000-4000-8000-000000000001",
"entityType": "dataset",
"periodStart": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"monthlyBudget": null
}
Captured from the API examples test suite; ids and timestamps are normalized.