Skip to main content

Gateway — Budgets API

Base path: /api/v1/gateway/budgets. Management (create/update/delete) requires owner or admin; listing is allowed for any role. Auth is a session cookie or a personal/team API key (requireAnyAuth).

A budget is a spend cap. It is team-wide when virtualKeyId is null, or scoped to a single virtual key when set. period is one of day | week | month | total. For a periodic budget resetsAt is the start of the next period (lazily rolled forward on read); a total budget never resets (resetsAt is null).

Only one budget may exist per (team, scope, period) — a duplicate returns 409.

Enforcement happens in the completion pipeline (POST /api/v1/gateway/chat/completions): an over-budget request is rejected with 402 BUDGET_EXCEEDED before any provider call, and an over-rate request with 429 RATE_LIMITED (Retry-After header). See the bottom of this file.

All examples below were verified with real curl output against a running server.


POST /api/v1/gateway/budgets

Create a spend cap (owner/admin). virtualKeyId defaults to null (team-wide).

curl -X POST $ACRUXCORE_BASE_URL/gateway/budgets \
-H "Content-Type: application/json" \
--cookie "connect.sid=<session>" \
-d '{"period":"month","limitUsd":50}'

Response (status 201):

{
"id": "03307242-ab70-4b38-bb66-bfbab644ff91",
"virtualKeyId": null,
"period": "month",
"limitUsd": 50,
"spendUsd": 0,
"resetsAt": "2026-08-01T00:00:00.000Z",
"createdBy": "6353f9ff-dc41-4b9f-9a83-237002cfc8e7",
"createdAt": "2026-07-04T06:42:50.977Z",
"updatedAt": "2026-07-04T06:42:50.977Z"
}

Creating a second budget for the same scope + period returns 409:

curl -X POST $ACRUXCORE_BASE_URL/gateway/budgets \
-H "Content-Type: application/json" \
--cookie "connect.sid=<session>" \
-d '{"virtualKeyId":null,"period":"month","limitUsd":20}'

Response (status 409):

{ "error": { "code": "BUDGET_EXISTS", "message": "A budget for this scope and period already exists." } }

GET /api/v1/gateway/budgets

List the team's budgets with live spend (any role). Elapsed periods are reset lazily on read.

curl $ACRUXCORE_BASE_URL/gateway/budgets \
--cookie "connect.sid=<session>"

Response (status 200):

[
{
"id": "03307242-ab70-4b38-bb66-bfbab644ff91",
"virtualKeyId": null,
"period": "month",
"limitUsd": 50,
"spendUsd": 0,
"resetsAt": "2026-08-01T00:00:00.000Z",
"createdBy": "6353f9ff-dc41-4b9f-9a83-237002cfc8e7",
"createdAt": "2026-07-04T06:42:50.977Z",
"updatedAt": "2026-07-04T06:42:50.977Z"
}
]

PATCH /api/v1/gateway/budgets/:id

Update limitUsd and/or period (owner/admin). Changing period recomputes resetsAt. Below the limit is dropped to a sub-cent value (limitUsd is stored at 4-decimal precision, so 0.000001 surfaces as 0) to demonstrate 402 enforcement next.

curl -X PATCH $ACRUXCORE_BASE_URL/gateway/budgets/03307242-ab70-4b38-bb66-bfbab644ff91 \
-H "Content-Type: application/json" \
--cookie "connect.sid=<session>" \
-d '{"limitUsd":0.000001}'

Response (status 200):

{
"id": "03307242-ab70-4b38-bb66-bfbab644ff91",
"virtualKeyId": null,
"period": "month",
"limitUsd": 0,
"spendUsd": 0,
"resetsAt": "2026-08-01T00:00:00.000Z",
"createdBy": "6353f9ff-dc41-4b9f-9a83-237002cfc8e7",
"createdAt": "2026-07-04T06:42:50.977Z",
"updatedAt": "2026-07-04T06:43:01.300Z"
}

DELETE /api/v1/gateway/budgets/:id

Remove a cap (owner/admin). Returns 204 with no body.

curl -X DELETE $ACRUXCORE_BASE_URL/gateway/budgets/03307242-ab70-4b38-bb66-bfbab644ff91 \
--cookie "connect.sid=<session>"

Response (status 204): no body.


Enforcement on the completion endpoint

These are behaviours of POST /api/v1/gateway/chat/completions when a budget or a virtual key's rate limit applies.

402 — over budget

With a team-wide budget whose spend has reached limitUsd (here the limit was patched to a sub-cent value above), a call is rejected before any provider request:

curl -X POST $ACRUXCORE_BASE_URL/gateway/chat/completions \
-H "Content-Type: application/json" \
--cookie "connect.sid=<session>" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":10}'

Response (status 402):

{ "error": { "code": "BUDGET_EXCEEDED", "message": "Team-wide budget exceeded." } }

(For a per-key budget the message is Virtual key budget exceeded.)

429 — over rate limit

With a virtual key created with maxRpm: 1, the first call succeeds and carries x-gateway-ratelimit-remaining: 0; a second call within the 60s window is limited.

curl -X POST $ACRUXCORE_BASE_URL/gateway/chat/completions \
-H "Authorization: Bearer agh_sk_...REDACTED" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":5}'

Call 1 returns status 200, with header x-gateway-ratelimit-remaining: 0. Call 2 (within 60s) returns status 429, with header Retry-After: 59.

Response (status 429):

{ "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded." } }