← All posts
Guides

Hard LLM Spend Caps at Org, Team, User, and Key Scope

A budget is a dollar allowance attached to a scope and a window. Here is how to create one on each of the four scopes, which status code each returns when it fires, and how to prove the cap bites before you trust it in production.

nRouter team · 11 min read
Hard LLM Spend Caps at Org, Team, User, and Key Scope

The short answer: create a budget on the Budgets page (/[organization]/budgets) with three fields — Budget Name, Max Spend, Duration — then attach it to an organization, a team, a user, or a single API key. Organization, team and user budgets reject with 402 and code budget_exceeded. A per-key budget rejects with 429 and code key_budget_exceeded. The status code differs by scope, so branch on the code field, never on the number.

The short answer

A hard spend limit is not a monitoring feature. It is a refusal: past the number you set, the gateway stops calling providers on your behalf, before any money moves and before any provider sees the request. That refusal is what bounds the worst case, and the worst case is the only thing worth designing for — a runaway agent loop, a key pasted into a public repository, a batch job that retries forever because nobody wrote an exit condition.

Everything else in this guide is mechanics: which of the four scopes to attach a cap to, which window to reset it on, what happens at the line, and how to watch it fire on a throwaway key so you are relying on a control you have seen work rather than a form you filled in once. If you want the wider map first — how a budget sits alongside your credit balance, your rate limits and your guardrails — read the four pre-flight gates and come back.

When you need this

Three situations bring people to this page, and they need different caps.

The first is a bill that arrived larger than anyone expected. Nothing broke; usage simply grew faster than the person watching it. This is a monthly cap at organization scope with a soft threshold underneath it — a backstop, not a handbrake, because the goal is early warning rather than a hard stop on real traffic.

The second is one team quietly consuming everything. The account is funded and the total is defensible, but a single squad's experiment has absorbed the headroom three other squads were counting on. An organization cap does nothing here by construction: it caps the total, and the total is fine. This needs a cap per team.

The third is an automated key you do not fully trust. A nightly batch, a CI job, a demo key handed to a customer, an agent that decides its own next call. Here the right unit is the key, the right duration is often Daily or Total, and the number should be small enough that the worst case is uninteresting. Blast-radius reasoning for keys is in virtual keys vs master key.

Most production accounts end up with all three at once, stacked. A request is rejected if it exceeds any budget that applies to it, so stacking is safe and the tightest applicable cap is the one that binds.

What you need first

You can read this guide with nothing. To run it you need four things.

  • A funded organization. nRouter is credit-based and signup is card-required: the minimum credit purchase is $5. The platform fee rides on top of the purchase rather than being skimmed out of it — see Pricing and Billing and Credits.
  • Owner or admin role, if you want to cap at organization, team or user scope. Those three are restricted by scope-ownership enforcement. A key-level budget can be set by a member on their own key at creation; editing it afterwards is again owner or admin. Roles are in Team Management.
  • At least one virtual key from /[organization]/keys. The full sk-nrouter-… value is displayed exactly once, at creation. Mechanics in API Key Management.
  • NROUTER_API_KEY in your shell. Every code block below runs as written against https://api.nrouter.ai/v1.
export NROUTER_API_KEY="sk-nrouter-your-key-here"

curl -sS https://api.nrouter.ai/v1/models \
  -H "Authorization: Bearer $NROUTER_API_KEY" | head -c 300

A model list means you are ready. A 401 means the key is wrong or revoked — check Authentication.

Step 1 — Decide the scope before you decide the number

The scope is the decision; the dollar figure is arithmetic afterwards. Pick the scope by asking who you are protecting from whom.

ScopeCapsReach for it whenWho can set it
OrganizationTotal spend across the whole accountYou want one number the invoice can never exceedOwner / admin
TeamEvery key belonging to one teamOne squad should not be able to spend another squad's headroomOwner / admin
UserOne member's spend inside the orgPer-person allowances, contractors, trialsOwner / admin
API keyA single virtual keyA batch job, a CI runner, an agent, a customer demoKey owner at creation

A useful default for a small engineering org is three layers: an organization monthly cap as the invoice ceiling, a monthly cap per team below it, and a daily or total cap on every automated key at the bottom. The layers do not need to sum to anything in particular — budgets do not reserve credits, so an unallocated remainder simply stays available to keys that have no budget of their own.

That last point catches people out. A $1,000 balance with a $300 team cap and a $500 team cap has $200 that is not budgeted, and that $200 is spendable by any key without a budget. Budgets cap; they do not ring-fence. The distinction, and why the credit balance is a separate control entirely, is in gateway credits vs prepaid tokens.

It is worth naming the alternative design so you can tell which one you are looking at, because the two are easy to confuse. Azure's model quota genuinely is allocated: "you assign TPM to each deployment as it is created, and the available quota for that model is reduced by that amount", so creating a new deployment means "reducing the TPM assigned to other deployments of the same model (thus freeing TPM for use)" (manage quota). That is a reservation: one consumer's allocation is subtracted from another's. A spend budget is the opposite — an independent ceiling with no claim on the balance behind it — and expecting subtraction from something that only caps is how a team ends up over-committed on paper and under-protected in practice.

Step 2 — Create the budget on the Budgets page

Open Budgets at /[organization]/budgets and click Create Budget. Three fields do the work.

FieldWhat it meansExample
Budget NameThe label that appears inside the error message when it firesBackend API — Monthly
Max SpendThe hard cap in USD$500.00
DurationWhen the counter resetsMonthly

Choosing Max Spend honestly means doing the arithmetic against a real rate card, not a feeling. Model prices are published per million tokens and vary by more than an order of magnitude between the small and flagship tiers of a single vendor — OpenAI lists gpt-5.4-mini at $0.15 in and $0.60 out per 1M tokens on its pricing page, and Anthropic publishes a comparable per-MTok table plus the multipliers that change the answer: a 50% Batch API discount and a cache read billed at 0.1x the base input rate (pricing). Take your measured token volume, price it at the model you actually call, then set the cap above that with headroom — not the other way round.

Name it for the thing it protects, not for the number. The name is quoted back to you in the rejection body, so Nightly Batch — Daily tells an on-call engineer at 3am exactly which cap fired and which job to look at, while Budget 4 tells them to open the dashboard and start guessing.

Then assign the budget to a key or a team, or set it at organization or user scope. Assignment is what turns a number into a control.

Step 3 — Pick the duration deliberately

Duration decides when the counter goes back to zero, and the four options are not interchangeable.

DurationResets
DailyMidnight UTC, every day
WeeklyMidnight UTC each Monday
MonthlyThe 1st of the month at midnight UTC
TotalNever — a hard lifetime cap

Daily is the right default for anything automated. A daily cap turns an unbounded failure into a bounded one: the worst a broken loop can do is spend one day's allowance, and tomorrow it gets another chance to behave. Monthly suits product lines and teams where the natural unit of accountability is the invoice period. Total is the one people underuse — it never resets, which makes it the correct cap for a proof of concept, a fixed-scope project, or a key you handed to someone outside the company. Set it once at $100 and the exposure is $100, in perpetuity, with no calendar involved.

Note the UTC boundaries. A daily cap does not reset at midnight where you are, and a team that reliably runs its heaviest batch at 23:00 local time may be straddling two budget days without realising it.

Step 4 — Choose an enforcement mode, and a soft threshold under it

A budget also decides what happens at the cap. Three modes ship, and only one of them is a spend limit.

ModeAt the capUse it for
BlockThe request is rejected — 402 at org/team/user scope, 429 at key scopeProduction backstops. The default.
WarnAn alert fires; requests keep flowingVisibility while a team negotiates its allowance
ThrottleAn alert fires and the budget is flagged for rate reduction; no hard blockA high-traffic key you do not want to cut off entirely

If the requirement is "this can never cost more than X", the mode is Block. Warn is a monitor with a nice name, and a Warn budget described in a planning document as a spend cap is the most common way a team ends up believing it has a control it does not have.

Underneath the cap, configure soft thresholds — 50%, 80% and 100% is a reasonable set. Each crossing dispatches an alert to your connected channels while requests keep flowing, which is what buys you the chance to act before anything is rejected. Wire the destinations under Observability → Alerts → Channels: Email, Slack, Microsoft Teams, Jira, or a generic webhook. Details in Alerts and Notifications.

This is the same discipline Google's SRE book calls keeping a safety margin: "using a tighter internal SLO than the SLO advertised to users gives you room to respond to chronic problems before they become visible externally" (Service Level Objectives). The chapter's other advice transfers cleanly too — "it's better to start with a loose target that you tighten than to choose an overly strict target that has to be relaxed" — which is an argument for shipping a generous cap this week rather than a perfect one next quarter.

Budgeting is also not a control you invent per company. The FinOps Foundation's framework lists Budgeting and Forecasting as named capabilities under Quantify Business Value, sitting downstream of allocation and anomaly management under Understand Usage & Cost (FinOps Framework) — which is the same ordering this guide uses: know who spends what, forecast it, then cap it.

The pairing to internalise is: thresholds tell a human, the mode tells the gateway. Neither substitutes for the other.

Step 5 — Pair every dollar cap with a rate limit

A budget bounds a window. It does not bound a minute, and that gap is where the expensive incidents live. A retry loop firing two thousand times in ten minutes stays comfortably inside a $5,000 monthly cap right up to the moment it does not, and by then the money is spent. The dollar cap was never violated; it was simply consumed at a rate nobody intended.

Velocity is a separate control. Set RPM (requests per minute) and TPM (tokens per minute) on the key when you create or edit it. Both use a sliding window; exceeding either returns 429 with rate_limit_exceeded and a Retry-After header:

{
  "error": {
    "message": "Rate limit exceeded. Retry after 12 seconds.",
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded"
  }
}

Plan tier sets the defaults and a limit set explicitly on a key overrides them. The important property is that a rate limit is not overridable per request — it is a boundary, not a parameter the caller can pass. That is exactly what makes it useful against a key you no longer control. The decision framework for reaching for one control versus the other is in budgets vs rate limits, and the per-scope mechanics are in RPM and TPM rate limiting.

Verifying it worked

A cap you have never watched fire is a hypothesis. Prove it on a disposable key, in five minutes, before you rely on it.

  1. Create a throwaway key on /[organization]/keys and name it cap-test so you can revoke it without thinking about it afterwards.
  2. Attach a $0.01 budget with Duration Total and mode Block to that key from /[organization]/budgets.
  3. Send two calls. The first should succeed and settle above a cent; the second should be rejected.
for i in 1 2; do
  curl -s -o /dev/null -w "attempt $i → %{http_code}\n" \
    https://api.nrouter.ai/v1/chat/completions \
    -H "Authorization: Bearer $NROUTER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"model":"gpt-5.4-mini","messages":[{"role":"user","content":"ping"}]}'
done
  1. Read the rejection body, not just the status. A per-key budget returns 429, and the body names the budget, the window, the amount used and the limit:
{
  "error": "Team budget 'Data Science — Monthly' (monthly) exceeded: $2000.12 used of $2000.00 limit.",
  "code": "budget_exceeded",
  "request_id": "req_..."
}
  1. Repeat at team scope and confirm you get 402 with budget_exceeded instead. Watching both codes once is what stops you writing retry logic that treats them identically.
  2. Confirm the successful path still prices correctly. Read the cost header off an ordinary call:
curl -sS -D - -o /dev/null https://api.nrouter.ai/v1/chat/completions \
  -H "Authorization: Bearer $NROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5.4-mini","messages":[{"role":"user","content":"ping"}]}' \
  | grep -i '^x-nr-'

x-nr-request-cost carries the settled cost in USD and pairs with x-nr-cost-status. When a call cannot be priced the cost header is absent — that is unpriced, and it is not the same as zero. Then revoke cap-test.

For the live view, open Advanced → Budgets at /[organization]/advanced/budgets. The enforcement panel shows your org spend meter, the top team budget with its utilisation, how many budgets are rejecting requests right now, and the Block/Warn/Throttle split. Because a budget block happens at pre-flight, before any provider call, a blocked request writes no spend log — so the panel reports what is blocking now rather than a historical count. Request-level outcomes live in the request logs at /[organization]/logs.

What goes wrong

You expected a budget to return 429 everywhere. It does not, and this is the single most consequential detail on the page. Organization, team and user budgets return 402 with budget_exceeded; only a per-key budget returns 429 with key_budget_exceeded. Client code that branches on the status number alone will mishandle one of the two. Branch on code. The full client-side playbook is 429 vs 402 on an LLM gateway.

You retried a budget rejection with exponential backoff. A budget block is a policy outcome, not a transient failure. It will return the same code on every call until the window resets or a human raises the cap, so backoff produces a tight pointless loop that adds latency and achieves nothing. Surface it to an operator instead.

You set the cap at the wrong scope. An organization budget cannot stop one team consuming the whole account, because the account total is exactly what it measures. If the concern is a team, the budget belongs at team scope with the org cap sitting above it as the last resort. Scope resolution is covered in org, team, member.

You used Warn and called it a cap. Warn alerts and keeps serving. It is a reasonable mode while a team sorts out its allowance and the wrong mode as a runaway-spend backstop.

You assumed a budget reserves credits. It does not. Budgets cap spend; the credit balance is a separate pool, and unbudgeted credits stay spendable by keys without a budget. If the requirement is "this money is spoken for", express it as a budget on every key that could otherwise reach it.

You read a missing cost header as a free request. Reporting code that defaults an absent x-nr-request-cost to 0.0 will under-report spend in your own dashboards while the ledger stays correct. Treat absent as unknown and reconcile against the ledger — see how to read your credit ledger.

Try it

Budgets, rate limits, guardrails, A/B tests, prompt management and evals are on every plan. Plans vary the platform fee — 4% on Pay as you go, 0% on Pro at $50/mo or $500/yr — and the default rate limits, never the feature set. The reasoning behind that choice is in Every Feature on Every Plan: We Charge a Fee, Not a Gate.

Load the $5 minimum, then do the five-minute exercise above: create a key, attach a one-cent total budget, and watch the second call get refused. Start at app.nrouter.ai/signup, or send a first call from the browser in the Playground. Questions about which scope fits your failure mode? Ask in the nRouter community.

See also

Sources

Verified 2026-06-14. Every field name, status code, duration and enforcement mode attributed to nRouter comes from our own documentation or pricing page; the external references below are cited for the specific claim each supports. If something has drifted, email hello@nrouter.ai and we will correct it.

External

nRouter

Share
Written by nRouter teamEngineering, product, and company posts from the nRouter team — code-first, cost-honest, no vendor-marketing fluff.