token_rate_limiter. One policy type, two names; nothing behaves differently.
Configure the policy
Open Policies → Library → LLM Budget.Budget
The form shows the pair back as a sentence — Allow 100000 tokens per hour.
The window is fixed, and it opens on the first charge. It is not a rolling
window and not aligned to the clock: the counter is created when usage is first
charged to it, lives exactly the window’s length, then disappears and the whole
allowance returns at once. An hour-long budget exhausted at 10:59 is empty for
one minute and full again at 11:00 — if the counter happened to open at 10:00.
When limit is exceeded
Downgrading keeps the application working at its ceiling —
gpt-4o-mini instead
of gpt-4o — instead of returning errors. Reject is for spend that must not
happen at all.
Per model limits
Optional. Each line is a Model pattern — a model name, or a wildcard such asclaude-opus-* — a Max, and its own Time window.
A request is checked against the overall budget and against the most specific
per-model rule matching its model. Exceeding either triggers the behaviour, and
usage is charged to both. An exact slug beats a wildcard; a longer wildcard beats
a shorter one.
The form makes the overall budget mandatory, so per-model rules are always a
tightening, never the whole policy. A policy that came from somewhere else can
carry per-model rules with no overall budget — and then a model matching no rule
has no budget at all. If your policies are built that way, one of them should be
a catch-all.
Advanced Settings
Scope
Mode
Enforce and Observe. Observe is a real dry run here, and a better one than most policies offer: usage is still charged, the headers are still emitted, and a request over the ceiling is still recorded — it is simply never refused or downgraded. Run a window in Observe, read the counts, then switch.Dollars are only as true as the rates
A dollar budget prices tokens the same way Analytics does: your contract pricing where you set it, otherwise list price. Enter the contract before the budget, or a discounted contract stops too early.How the decision is made
1
The counters for this request are read
The overall budget’s counter, and the counter of the most specific per-model rule
matching the request’s model. Both keyed by the policy, its scope subject, and
the Group by header value if there is one.Nothing about this request is measured. No prompt is tokenised, no cost is
estimated, nothing is reserved.
2
At or above either ceiling, the behaviour applies
In Enforce, Reject request refuses with
429; Downgrade model rewrites
the request’s model and lets it continue. In Observe, the violation is
recorded and the request continues unchanged.The ceiling that is reported — in the headers, and on the request’s Policies
tab — is the first one found to be breached.3
The request is forwarded
Under budget, it goes upstream untouched, and the response carries the remaining
allowance.
4
After the response, the actual usage is charged
The gateway reads the usage the provider reported, applies Counting, converts
to dollars if the unit is dollars, and adds it to every window in play. This
happens after the response has been sent to the caller, on its own, so it
never delays the answer — and never gates it either.
What the caller receives
Every response, refused or not, carries the state of the reported window:
So the unit, the scope and the window are on every refusal, whichever unit
the budget is in — a token refusal is not a lesser one.
Both reset headers —
X-Ratelimit-Reset-Tokens and X-Budget-Reset — are a
number of seconds with a seconds suffix: 3600s, not 3600. A client that
reads them as a bare number will not parse them.
A refusal is 429, and the response says which budget was exhausted — the token
one or the dollar one — whether the ceiling was the gateway-wide one or the one
targeted at this application, and which window was breached.
Modes and failures
Limits
- No pre-flight estimate, no reservation. See the warning at the top of this page. The ceiling is enforced against what has already been charged.
- Accrual is asynchronous, so the counter lags the traffic by roughly one response.
- The windows are fixed, open on the first charge, and return the whole allowance at once when they close.
- Windows shorter than a minute are raised to a minute, without the form saying so.
- LLM traffic only. MCP (Model Context Protocol) tool calls are outside this policy entirely; the Per-Tool Rate Limiter is the tool equivalent.
- Only usage the provider reports is charged. A response without a usage block is free.
- Abandoned and oversized streams are not charged.
- Unpriced models are free against a dollar budget.
- Prompt-cache reads are free against a token budget. Input tokens the provider served from its prompt cache are subtracted before anything is counted, so a long system prompt reused across a conversation charges the budget once and then stops charging for itself. There is no setting in the console to count them. A dollar budget does count them, at the provider’s cheaper cache-read rate rather than the full input rate. An application that caches heavily will therefore travel much further inside a token budget than its raw token counts suggest.
- A downgrade target must be a single model name the application may call, and not a pool alias or a wildcard; anything else silently becomes a refusal. A name the request’s provider does not serve is not caught — it is applied, and the provider errors.
Retry-Afteris not sent. The refusal carries the reset in the budget headers instead, so a client that only readsRetry-Afterlearns nothing about when to come back.- The refusal body is the gateway’s own shape, not your provider’s error shape, whichever wire format your client speaks.
- The counters live in the gateway’s shared store. They are operational state, not accounting: if that store is cleared, every budget starts again from zero. Use Analytics for what was actually spent.
Verify the policy
Set Max low — a few hundred tokens, or a fraction of a dollar — and the window to a minute while you test. Send these in this order.1
Control — the path works at all
In the Playground, pick the application the policy applies to, and send:Expect a normal reply. If this fails, the problem is the application, the model
or the provider — not the policy. In Enforce, a
502 here means the gateway
could not reach its counter store. Fix it before going on.2
The budget is being charged
Pick a specific model in the Playground first, not Auto. With Auto the
gateway chooses the model for you, and on that path the Playground does not ask
the provider to report what the request used. Providers that only report it when
asked — OpenAI among them — then report nothing, so nothing is charged and a
budget you are watching never moves however many prompts you send. Open the
model selector and choose one model by name.Then send the same prompt a few more times and look those requests up in
Activity. Open one and read its Policies tab: this policy should be
listed with the usage it charged.The remaining allowance is not something the Playground shows you. The gateway
puts it in the response headers listed above, so a client of your own watches it
fall as it goes; the Playground does not display response headers.If nothing is being charged, work down this list:
- Auto is still selected in the Playground’s model selector. Pick a model by name and send again.
- The traffic is not in this policy’s scope. Check Coverage on the policy, and that its Scope does not read Matches nothing.
- The responses carry no usage at all.
- On a dollar budget, the model has no price. The request’s Policies tab marks it as unpriced. Price it in Contract pricing before assuming the policy is broken.
3
The ceiling acts
Keep sending. Expect to cross the ceiling without being refused — that
request is served in full — and to be refused on a later one. That is the
policy working as designed, not a fault.With Reject request, expect
429 and a refusal naming the budget that was
exhausted. Then confirm the refused request never reached the model: open
Activity, click the request, open its Policies tab and read Aggregated
Latency — the Provider row should be empty. That the provider leg is empty
is the evidence that matters.With Downgrade model, expect 200 and the downgrade header on the response,
naming the model asked for and the one that answered. Read which model
answered, not just the status. A 429 instead means the target is not valid
for this request; an error from the provider about an unknown model means the
target was applied but that provider does not serve it.4
The partition holds (only if you set Group by header)
Send a request with a different value in the header you named. It must be allowed
on its own fresh budget while the exhausted value is still refused. Then send one
with the header omitted: it is not exempt — it joins the shared counter.
Troubleshooting
Related
- Rate Limiter — the same shape of control, for volume rather than spend
- Smart routing — use an ordered model ladder with cache-aware conversation routing; the budget adds a spend ceiling
- Contract pricing — the rates a dollar budget counts with
- Policies overview — scope, modes and policy chains
- Event schema — the fields each decision records