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.

typeWhat it caps
daily, weekly, monthlyWhat is held and captured in the current day, week or month. The next one starts from zero, and resets_at says when
totalWhat is held and captured, for good. It never starts again
per_operationOne hold: a hold larger than the limit is refused with operation_limit_exceeded. Nothing is added up
concurrencyThe 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

  • scope set to project makes a budget of the project of the key. organization makes one of the whole organization, which counts the spend of every project.

  • operation_code narrows a budget to the calls of one operation. Without it the budget covers every call.

  • type, scope and operation_code cannot 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_amount binds 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.

  • name is a label of your own. null clears 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. status set to active restores 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_operation and concurrency send no such events.

  • A change of the limit sends none. A limit lowered below what the window has used admits no hold, so budget.exceeded does 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

ParameterDescription
include_archived optional

queryinteger

1 lists the archived budgets too; 0, the default, does not — the reserve path only knows active budgets. A flag in the query string is 1 or 0: true and false are a field-validation 422.

  • One of: 0, 1
  • Default: 0

Responses

200

Budgets of the caller's organization/project

application/json
{
  "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

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

429

Rate limited for this API key

Headers

  • X-Request-Id
  • Retry-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

application/json
{
  "type": "daily",
  "limit_amount": "0.02700000",
  "scope": "organization",
  "operation_code": "generate_email",
  "name": "string"
}

Responses

201

The created budget

application/json
{
  "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

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

422

Validation failure or a semantically impossible request. Field-level validation from the framework answers {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

429

Rate limited for this API key

Headers

  • X-Request-Id
  • Retry-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

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Request body

application/json
{
  "limit_amount": "0.02700000",
  "status": "active",
  "name": "string"
}

Responses

200

The updated budget

application/json
{
  "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

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

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 {"message", "errors"}; domain refusals answer the standard error envelope. An Idempotency-Key longer than 190 characters is invalid_request in that envelope.

503

service_unavailable: the change waited for calls in flight on this budget — a reserve, capture or release holding its window — for longer than the database lets a lock be waited for, and was rolled back whole. Nothing was changed: neither the limit, nor the status, nor the name. Send the same request again.

Headers

  • X-Request-Id
  • Retry-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

ParameterDescription
id required

pathId

Snowflake identifier, as a string.

  • Pattern: ^\d+$
  • Example: 74438200973594624

Responses

200

The archived budget

application/json
{
  "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

StatusCodesWhen
401

No credential, or one that is not valid

403

Authenticated, but not allowed: scope (insufficient_scope), policy or tenant (not_a_member); a blocked end customer (customer_blocked); an act only a signed-in person may take (session_required); the role rule on members and invitations (member_forbidden, invite_forbidden); a write to the organization's own rows, or a read of the audit journal, by a caller that does not reach every project (project_access_restricted)

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

service_unavailable: the change waited for calls in flight on this budget — a reserve, capture or release holding its window — for longer than the database lets a lock be waited for, and was rolled back whole. Nothing was changed: neither the limit, nor the status, nor the name. Send the same request again.

Headers

  • X-Request-Id
  • Retry-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.

Terminal
curl https://api.slen.ai/api/v1/budgets \
  -H "Authorization: Bearer $SLEN_API_KEY"
200 OK
{
  "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.

Terminal
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"
  }'
201 Created
{
  "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.

Terminal
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"
  }'
200 OK
{
  "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.

Terminal
curl -X DELETE https://api.slen.ai/api/v1/budgets/74443127894704128 \
  -H "Authorization: Bearer $SLEN_API_KEY"
200 OK
{
  "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

Built from openapi b070e1f, sdk contract 5cc7055, php ae4a53b, node 84bc62d