Usage Limits & Budget Rules
The Zuplo AI Gateway provides hierarchical usage limits and budget controls to manage LLM spending across the gateway's Zuplo project. Limits can be set at the gateway, team, and app levels.
Budget Hierarchy
Limits at every level apply together. A request is blocked when the gateway, an ancestor team, or the app is over a Block limit. The error identifies whether a shared node budget or an expression-specific budget ran out:
- Gateway - Limits across the Zuplo project (for example, $1,000/day), covering all teams, sub-teams, and apps combined
- Teams - Limits covering the team's own usage and every descendant sub-team and app (for example, $500/day for the Engineering team)
- Apps - Per-app limits for granular control (for example, $10/day for a hackathon app)
Budgets at different levels don't constrain each other's values, and app budgets under a team may add up to more than the team's own budget - the team's budget is the aggregate cap. For example, take a $100/month team containing a $50/month sub-team with two apps capped at $25/month and $40/month. When the apps have spent $40 and $10, the sub-team's aggregate reaches $50 and requests through both apps are blocked - including the app that is still under its own $25 cap. Apps elsewhere in the $100/month team keep working until that team's own aggregate reaches $100.
Where limits are configured
| Level | Shared budget | Per-expression budgets |
|---|---|---|
| Gateway | Settings → Usage Limits | Supported in configuration, but not authorable in the Portal |
| Team | The team's Usage & Limits tab | The team's Usage & Limits tab |
| App | The Budgets and Costs policy in the app's policy chain | The Budgets and Costs policy |
Gateway and team limits apply independently to every descendant app; they aren't copied into the app's configuration. Each node meters its own usage plus all descendant usage. A request is blocked if any node in its path is over a Block limit. An app's own limits live on the Budgets and Costs policy.
Everything a team enforces lives on its Usage & Limits tab. The Policy Template tab is only the template: a Budgets and Costs entry there is copied into each new app as the app's own starting budget and doesn't enforce the team's budget.
How a budget rule works
Every node's budgets are a list of rules. Each rule answers two questions—what it budgets, and what the caps are.
Scope decides who shares the budget:
| Scope | What it budgets |
|---|---|
Shared (budgetBy: "app") | One shared budget for the gateway, team, or app and its descendants |
By metadata (budgetBy: "expression") | A separate budget at that node for every distinct value of an expression |
A By metadata rule gives each distinct expression value its own allowance.
With the expression request.headers.get("x-user") and a $5 daily cap, every
distinct x-user value gets its own $5 per day—one user exhausting their budget
doesn't affect anyone else. A node can combine a shared rule with several
expression rules.
Ancestor rules of both kinds apply to every request through the node's subtree.
The difference is who gets blocked: a team's Shared rule blocks all of the
team's traffic once the team's aggregate is over, while a team's By metadata
rule meters each value across all of the team's apps combined and blocks only
the exhausted value—the same x-user shares one budget whether it calls App A
or App B.
Limit rows set the caps. Each row is a meter, a period, an amount, and an action:
| Field | Values |
|---|---|
| Meter | Cost (dollars), Tokens (input plus output), Requests |
| Period | Hourly, Daily, Weekly, Monthly |
| Limit | An absolute amount in the meter's own unit |
| Action | Block rejects the request; Warn notifies without blocking |
Add as many rows as you need to cap several meters and periods at once. A Warn row and a Block row on the same meter and period give you warn-then-block—warn at $80, block at $100. The warn amount has to be the lower of the two.
The Overview tab charts daily and monthly usage only. Hourly and weekly rows are enforced, but no meter on that tab tracks them.
Budget rule periods follow the UTC calendar
Hourly periods reset at the top of each hour, daily at 00:00 UTC, weekly on Monday at 00:00 UTC, and monthly on the first of the month. A rule added mid-period gets the full allowance for the rest of that period—the limit isn't prorated—and resets at the next boundary. Changing a limit mid-period doesn't reset the period's usage. Custom anchors aren't configurable.
Setting an app's limits
-
Open the Apps & Teams tab and select the app.
-
On the Policies tab, configure the Budgets and Costs policy (add it from Add Policy if the chain doesn't have it).
-
Select Add rule, choose the scope, and use Add limit to add rows for the meters and periods you want to cap. For a By metadata rule, enter the expression that identifies each caller.
-
Select Apply, then Save changes on the policy chain. The change applies within about a minute.
The app editor groups gateway and ancestor team rules under Inherited. Those rules remain stored and enforced at the gateway or team; edit them where they were created.
Budget expressions
A By metadata rule identifies each caller with an expression evaluated per request. The editor validates it as you type. Available values include:
request.headers.get("x-user")and other request headersrequest.user.subandrequest.user.data.<key>from the authenticated API keyrequest.url,request.method,request.query,request.searchParams, andrequest.paramscontext.contextId,context.requestId,context.route, andcontext.custom
The editor labels each expression by where its value comes from, because that
decides how much the budget can be trusted. A client-supplied value—a header
or query parameter—is attribution, not enforcement: a caller who changes it gets
a fresh budget. Budget on request.user.*, labeled verified, when the
segment itself has to hold, and keep a shared-budget rule alongside it as the
hard cap.
Changing an expression starts a new budget. The old rule's usage stays with the
old expression, so every value's accounting restarts from zero. Expressions are
also case-sensitive: get("X-User") and get("x-user") read the same header
but budget separately.
App rules are stored in options.budgetRules on the app's own Budgets and Costs
policy entry. For the app rule schema, see the
Budgets and Costs policy reference.
This rule set budgets each user, each organization, and the app as a whole:
Code
Every distinct x-user value gets its own 20,000 tokens per hour, resetting at
the top of the hour. Every distinct x-organization value gets its own $50 per
day, resetting at 00:00 UTC. The app rule caps the whole app at $2,500 per
month, warning at $2,000, resetting on the first of the month.
Setting team and gateway limits
Team and gateway limits cover the node's own usage and all descendant usage. They apply to every descendant app whether or not its chain includes Budgets and Costs.
Team rules—shared and per-expression—are added on the team's Usage & Limits tab, using the same meter, period, amount, and action rows as the app editor.
To add a shared gateway rule, open Settings → Usage Limits. The gateway root
supports per-expression rules in metadata.budgetRules, but the Portal doesn't
author them. It displays a stored expression rule as read-only and lets you
delete it.
Each budgetBy: "app" rule is a shared budget for the node where the rule is
stored. At the gateway or team level, app means that gateway or team node, not
an app policy configuration. A team's usage includes its sub-teams and apps, and
the gateway's usage includes the whole project.
Gateway and team rules are stored in metadata.budgetRules on that node. The
editor groups ancestor rules under Inherited, but each rule remains enforced
at the node where it was created.
When a limit is exceeded
If the app has a quota fallback model configured, an exceeded limit routes requests to that model instead of blocking—see Fallback Models, where the fallback model is selected. This applies to gateway and team limits as well as the app's own. The fallback's usage still counts toward the limits.
Without a fallback, the request is rejected with 429 Too Many Requests and a
budget object naming what ran out:
Code
The scope tells the caller which budget they hit. It is application when the
gateway, a team, or the app exhausts a shared node budget. It is dimension
when a value exhausts an expression budget. Quote the ruleId when raising a
support request.
Budgets fail open by default
The Budgets and Costs policy's throwOnFailure option—Fail closed when
metering is unavailable in the editor—controls how an error while checking the
app's own limits is handled. It defaults to false, so the request continues
through the gateway. Set it to true to reject the request instead. Team and
gateway limit checks run independently of this policy.
Monitoring Usage
Each app, team, and the gateway show current usage against their limits:
- Open the Apps & Teams tab and select an app or team
- The Overview tab shows daily and monthly usage—spend, tokens, and requests—with progress against any configured limits
- The same tab's Metrics frame charts request count, token usage, and cost over time; View in Analytics opens the full request-level breakdown scoped to that app or team
Related Resources
- Getting Started - Set up your first AI Gateway project with budget controls
- Managing Teams - Configure team-level budgets
- Managing Apps - Configure app-level limits
- Fallback Models - Serve a cheaper model instead of blocking when a limit is exceeded