Create, read and change budgets
List the budgets with what is left of them, create a budget, change its limit, and archive or restore it.
A budget is a ceiling on what may be spent. Every hold a model call takes is checked against the budgets that cover it, and a call that does not fit is refused before the provider is called. The operations of this page list the budgets with what is left of each, create one, change its limit and archive it. The console shows the same budgets on Budgets.
The kinds of budget
type says what a budget counts.
type | What it caps |
|---|---|
daily, weekly, monthly | What is held and captured in the current day, week or month. The next one starts from zero, and resets_at says when |
total | What is held and captured, for good. It never starts again |
per_operation | One hold: a hold larger than the limit is refused with operation_limit_exceeded. Nothing is added up |
concurrency | The holds that are open at the same time. limit_amount is their number, and one more is refused with concurrency_limit_exceeded |
A budget of money counts what it has captured and what it holds together, and a hold that would take both over the limit is refused with budget_exceeded. How budget reservations work explains the hold.
What a budget covers
scopeset toprojectmakes a budget of the project of the key.organizationmakes one of the whole organization, which counts the spend of every project.operation_codenarrows a budget to the calls of one operation. Without it the budget covers every call.type,scopeandoperation_codecannot be changed later: the spend counted under them would not follow. Create another budget instead.
A key reads the budgets of its project and those of the organization. It creates, changes and archives the budgets of its project only: a write to a budget of the organization answers 403 with the code project_access_restricted. A budget of another project, or one that does not exist, answers 404 with the code budget_not_found.
Who may change a budget
Reading takes read. Creating, changing and archiving take management:write: a key an application spends with — gateway:write — reads the budgets and cannot change one. Such a request answers 403 with the code insufficient_scope. The audit log records every creation, change and archive.
Change, archive and restore
A new
limit_amountbinds the next hold, in the window that is already open. A limit below what the window has already used is accepted: no new hold fits until the window starts again or the limit is raised.nameis a label of your own.nullclears it, and a request that leaves it out keeps it.Archiving keeps the budget and stops it from taking part in new holds. The holds already taken are still settled.
statusset toactiverestores it.The list leaves the archived budgets out unless you send
include_archived=1.
A change waits for the calls that are holding money under the budget at that moment. When the wait is too long, it answers 503 with the code service_unavailable and a Retry-After header, and nothing is changed. Send the same request again.
The events of a budget
A hold announces how far a budget is used: budget.threshold_reached at 50 % and at 80 % of the limit, budget.exceeded at the whole limit.
Each level is announced at most once a window, and a hold that passes two levels at once announces only the higher one.
Budgets of the types
per_operationandconcurrencysend no such events.A change of the limit sends none. A limit lowered below what the window has used admits no hold, so
budget.exceededdoes not come for it in that window. A limit raised does not announce again the levels this window has announced.
Operations
List budgets with their live headroom
GET /v1/budgets
- Scope:
read
Parameters
| Parameter | Description |
|---|---|
include_archived optionalquery |
|
Responses
Budgets of the caller's organization/project
{
"data": [
{
"budget_id": "74438200973594624",
"name": "string",
"project_id": "74438200973594624",
"type": "daily",
"scope": "organization",
"operation_code": "generate_email",
"status": "active",
"limit_amount": "0.02700000",
"reserved_amount": "0.02700000",
"captured_amount": "0.02700000",
"available_amount": "0.02700000",
"currency": "USD",
"resets_at": "2026-10-02T09:30:00Z"
}
]
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers | |
| 429 | Rate limited for this API key |
Headers
X-Request-IdRetry-After429
The body of every error is the error envelope.
Create a budget
POST /v1/budgets
- Scope:
management:write
A budget is configuration of what may be spent: the key an application runs operations with (gateway:write) reads budgets and cannot create, change or archive one (403 insufficient_scope). In the console that is the owner and the admin; a developer reads. A successful create, change or archive is written to the audit journal as budget.create, budget.update, budget.archive (GET /v1/management/audit-logs, resource_type budget).
scope=organization needs a caller that reaches every project (403 project_access_restricted); scope=project is the active project. The answer already carries resets_at.
Request body
{
"type": "daily",
"limit_amount": "0.02700000",
"scope": "organization",
"operation_code": "generate_email",
"name": "string"
}Responses
The created budget
{
"data": {
"budget_id": "74438200973594624",
"name": "string",
"project_id": "74438200973594624",
"type": "daily",
"scope": "organization",
"operation_code": "generate_email",
"status": "active",
"limit_amount": "0.02700000",
"reserved_amount": "0.02700000",
"captured_amount": "0.02700000",
"available_amount": "0.02700000",
"currency": "USD",
"resets_at": "2026-10-02T09:30:00Z"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers | |
| 429 | Rate limited for this API key |
Headers
X-Request-IdRetry-After429
The body of every error is the error envelope.
Change the limit or restore an archived budget
PATCH /v1/budgets/{id}
- Scope:
management:write
Audited as budget.update (see createBudget).
A new limit_amount binds the very next authorization, in the window that is already open — the current day, week or month, or the one window of a budget that never resets. A limit below what the window has already spent is accepted: it admits no further hold, and the holds taken before it still capture and release.
The budget webhooks are about holds, not about this request: changing a limit sends none. budget.threshold_reached (50 %, 80 %) and budget.exceeded (100 %) are announced by the first successful hold that finds a level crossed under the limit in force, each level once per window. Two consequences. A limit lowered below what the window already holds admits no hold, so budget.exceeded is not sent for it until the window turns — never, for a budget that does not reset; read available_amount in the answer instead. A limit raised does not re-arm the levels already announced in this window: they stay silent until the window turns, although under the new limit they are not reached.
A budget of no project caps every project: only a caller that reaches every project changes or archives it (403 project_access_restricted).
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Request body
{
"limit_amount": "0.02700000",
"status": "active",
"name": "string"
}Responses
The updated budget
{
"data": {
"budget_id": "74438200973594624",
"name": "string",
"project_id": "74438200973594624",
"type": "daily",
"scope": "organization",
"operation_code": "generate_email",
"status": "active",
"limit_amount": "0.02700000",
"reserved_amount": "0.02700000",
"captured_amount": "0.02700000",
"available_amount": "0.02700000",
"currency": "USD",
"resets_at": "2026-10-02T09:30:00Z"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 422 | Validation failure or a semantically impossible request. Field-level validation from the framework answers | |
| 503 |
|
Headers
X-Request-IdRetry-After503
The body of every error is the error envelope.
Archive a budget
DELETE /v1/budgets/{id}
- Scope:
management:write
Archive, not delete: reservations settle against the budget_usage rows this budget owns. Audited as budget.archive (see createBudget).
A budget of no project caps every project: only a caller that reaches every project changes or archives it (403 project_access_restricted).
Parameters
| Parameter | Description |
|---|---|
id requiredpath | Snowflake identifier, as a string.
|
Responses
The archived budget
{
"data": {
"budget_id": "74438200973594624",
"name": "string",
"project_id": "74438200973594624",
"type": "daily",
"scope": "organization",
"operation_code": "generate_email",
"status": "active",
"limit_amount": "0.02700000",
"reserved_amount": "0.02700000",
"captured_amount": "0.02700000",
"available_amount": "0.02700000",
"currency": "USD",
"resets_at": "2026-10-02T09:30:00Z"
}
}Errors
| Status | Codes | When |
|---|---|---|
| 401 | No credential, or one that is not valid | |
| 403 | Authenticated, but not allowed: scope ( | |
| 404 | No such resource for this tenant. Somebody else's resource — and a resource of a project the caller does not reach — answers the same way on purpose: the two must not be distinguishable. | |
| 503 |
|
Headers
X-Request-IdRetry-After503
The body of every error is the error envelope.
Examples
List the budgets
The monthly budget of the project has used 432 of its 500: 419.60 captured and 12.40 still held.
curl https://api.slen.ai/api/v1/budgets \
-H "Authorization: Bearer $SLEN_API_KEY"{
"data": [
{
"budget_id": "74437302811459584",
"name": "Production",
"project_id": "74438200973594624",
"type": "monthly",
"scope": "project",
"operation_code": null,
"status": "active",
"limit_amount": "500.00000000",
"reserved_amount": "12.40000000",
"captured_amount": "419.60000000",
"available_amount": "68.00000000",
"currency": "USD",
"resets_at": "2026-11-01T00:00:00+00:00"
},
{
"budget_id": "74437291503616000",
"name": null,
"project_id": null,
"type": "total",
"scope": "organization",
"operation_code": null,
"status": "active",
"limit_amount": "10000.00000000",
"reserved_amount": "12.40000000",
"captured_amount": "2361.27500000",
"available_amount": "7626.32500000",
"currency": "USD",
"resets_at": null
}
]
}Create a budget
A daily budget of one operation in the project of the key. The answer already says when its window ends.
curl https://api.slen.ai/api/v1/budgets \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "daily",
"scope": "project",
"operation_code": "generate_email",
"limit_amount": "25",
"name": "Follow-up emails"
}'{
"data": {
"budget_id": "74443127894704128",
"name": "Follow-up emails",
"project_id": "74438200973594624",
"type": "daily",
"scope": "project",
"operation_code": "generate_email",
"status": "active",
"limit_amount": "25.00000000",
"reserved_amount": "0.00000000",
"captured_amount": "0.00000000",
"available_amount": "25.00000000",
"currency": "USD",
"resets_at": "2026-10-03T00:00:00+00:00"
}
}Raise a limit
The new limit binds the next hold. What is held and captured stays as it was.
curl -X PATCH https://api.slen.ai/api/v1/budgets/74437302811459584 \
-H "Authorization: Bearer $SLEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"limit_amount": "750"
}'{
"data": {
"budget_id": "74437302811459584",
"name": "Production",
"project_id": "74438200973594624",
"type": "monthly",
"scope": "project",
"operation_code": null,
"status": "active",
"limit_amount": "750.00000000",
"reserved_amount": "12.40000000",
"captured_amount": "419.60000000",
"available_amount": "318.00000000",
"currency": "USD",
"resets_at": "2026-11-01T00:00:00+00:00"
}
}Archive a budget
The answer is the budget with status set to archived.
curl -X DELETE https://api.slen.ai/api/v1/budgets/74443127894704128 \
-H "Authorization: Bearer $SLEN_API_KEY"{
"data": {
"budget_id": "74443127894704128",
"name": "Follow-up emails",
"project_id": "74438200973594624",
"type": "daily",
"scope": "project",
"operation_code": "generate_email",
"status": "archived",
"limit_amount": "25.00000000",
"reserved_amount": "0.00000000",
"captured_amount": "0.00000000",
"available_amount": "25.00000000",
"currency": "USD",
"resets_at": "2026-10-03T00:00:00+00:00"
}
}Where to go next
Budgets — the same budgets in the console.
How budget reservations work — what a hold is, and how it is settled.
Webhooks — how the events of a budget arrive.
Built from openapi b070e1f, sdk contract 5cc7055, php ae4a53b, node 84bc62d