Set Spend Limits with Gateway Budgets and Rate Limits
What you'll build: a spend cap enforced before a single provider dollar
is spent, and a request-rate cap enforced independently of it — with real
402 and 429 responses proving both actually stop a call, not just log
one.
A budget is a spend cap: team-wide (every call, regardless of key) or
scoped to one virtual key, over a day, week, month, or total (never
resets) window. A key's rate limit (maxRpm/maxTpm, set when you
create or edit the key) is a separate
mechanism — capping request/token rate, not spend. Both are enforced in the
same place: before the gateway calls a provider at all.
1. See what's already capped
Open Gateway → Budgets. This account already has one budget scoped to
the checkout-service key and one team-wide budget covering everything.

2. Create a budget
Click New budget. Scope it to Team-wide or to one virtual key, pick a period, and set the limit in USD.

Click Create budget. It appears immediately in the list with $0 spent against the limit, and a countdown to when the period resets.

3. Prove the spend cap actually rejects a call
A key whose budget limit was set below what it had already spent this period gets rejected — no partial call, no charge:
curl -X POST "$ACRUXCORE_BASE_URL/gateway/chat/completions" \
-H "Authorization: Bearer $SCOPED_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Say OK"}], "max_tokens": 5}'
{"error":{"code":"BUDGET_EXCEEDED","message":"Virtual key budget exceeded."}}
That's a 402. The rejection happens in the gateway before any request
reaches the provider — an over-budget call costs nothing, not even a
partial completion.
4. Prove the rate cap actually rejects a call
Rate limits are independent of budgets and checked the same way — before the
provider call. A key already near its maxRpm for the current window gets:
curl -i -X POST "$ACRUXCORE_BASE_URL/gateway/chat/completions" \
-H "Authorization: Bearer $SCOPED_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Say OK"}], "max_tokens": 5}'
HTTP/1.1 429 Too Many Requests
Retry-After: 19
{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded."}}
Retry-After is in seconds until the window rolls forward — worth reading
and backing off on, rather than retrying immediately. One thing worth
knowing: lowering a key's maxRpm takes effect on calls already counted in
the current window, not just future ones — if a key already made several
calls this minute, dropping its cap to 1 rpm can rate-limit its very next
call, not just its eleventh.
Doing this over the API
- curl
# Create a budget — omit virtualKeyId (or send null) for team-wide
curl -X POST "$ACRUXCORE_BASE_URL/gateway/budgets" \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"period": "week", "limitUsd": 50}'
{
"id": "8820e264-d1ef-4c81-8050-2982fabd13ac",
"virtualKeyId": null,
"period": "week",
"limitUsd": 50,
"spendUsd": 0,
"resetsAt": "2026-08-03T00:00:00.000Z",
"createdBy": "00cc9833-13ca-42b4-a102-b403e85ea250",
"createdAt": "2026-08-01T04:41:58.988Z",
"updatedAt": "2026-08-01T04:41:58.988Z"
}
# Set a rate limit on a key (also settable at creation time)
curl -X PATCH "$ACRUXCORE_BASE_URL/gateway/keys/<key-id>" \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"maxRpm": 60}'
Only one budget may exist per (team, scope, period) combination — a
duplicate returns 409. Creating and updating budgets requires owner or
admin; listing them is open to any team member.
What's next
- Create the scoped key a per-key budget or rate limit applies to — see Scope Access with Virtual Keys.
- Chain a fallback so a provider outage doesn't just fail the call — see Automatic Model Fallbacks.
- Full field reference: Gateway Budgets in the API Reference.