Filter vs apply-to
Every rule answers two questions: which calls does it watch, and who gets their own budget — plus how the window resets.
When you build a custom rule (or open a template to see how it works), the two settings that matter most are the IF filter and the APPLY TO pool. Get these right and the rest is just numbers.
IF — which calls the rule watches
The filter picks the subset of traffic the rule cares about. Rules can match on any of these fields:
| Field | What it matches |
|---|---|
user_id | The end user your app identifies on the call. |
paid_plan | The plan string your app stamps on the call. |
model | The model that ran. |
provider | The provider that ran it. |
operation | The kind of call — chat, embedding, image, and so on. |
session_id | Your app's conversation or session id. |
trace_id | One agent run. |
metadata.<key> | Any custom tag your app attaches — metadata.team, metadata.feature, metadata.department. |
The engine accepts all of them. What you can author today is narrower, and it is worth being precise about where the line falls:
| Custom rule builder | Templates | API | |
|---|---|---|---|
Match on user_id, paid_plan, operation, metadata.<key> | ✅ | ✅ (paid_plan, user_id, a metadata key) | ✅ |
Match on session_id | — | ✅ (the session templates) | ✅ |
Match on model, provider, trace_id | — | — | ✅ |
| Operators is, contains, exists | ✅ | ✅ (is, exists) | ✅ |
| Operators is not, in | — | — | ✅ |
| Composite AND / OR filters | — | — | ✅ |
"API" means a direct call to the rules API with a management key — there is no dashboard or template route to those rows yet. If you need one of them, build the rule through the API and it will show up in the rules table like any other.
is is an exact match — case- and whitespace-sensitive. The dashboard says it plainly:
"Values are compared case-sensitively, spaces included — free never matches Free." The
same is true of APPLY TO: pooling by paid_plan gives free and Free two separate
budgets. Send one canonical spelling from your app (lowercase and trimmed is the safe
convention) and use exactly that string in the rule.
More than one condition
A rule's filter can be a single condition or a composite one: an AND or OR group of up to
20 conditions. Groups don't nest — one combinator, one flat list. The rules table renders
composite filters as IF … AND … / IF … OR …, and flags two degenerate shapes for you: an
empty AND matches everything, an empty OR matches nothing.
Composite filters are API-only today. The enforcement engine evaluates them, and the rules table displays them, but neither the Custom rule builder nor any template will author one — the builder sends a single condition. Create composites through the rules API.
APPLY TO — who gets their own budget
This decides how the limit is pooled:
| Apply to | Meaning |
|---|---|
| Everyone matching this rule (shared pool) | One budget shared across all matching traffic. |
Each unique user_id (individual pool) | Every user gets their own copy of the limit. |
Each unique <field> (individual pool) | Every distinct value of the field gets its own copy. |
You can group by any of the fields above, not just user_id — grouping by metadata.team gives
every team its own budget from one rule.
The filter and the pool are independent. The filter says which calls count; the pool says how many separate budgets the limit becomes.
A worked example
Give every user on the free plan their own $5 / month cap.
- IF
paid_planisfree— the rule only watches free-plan calls. - APPLY TO each unique
user_id— so the $5 is per user, not $5 shared across all of them.
Flip APPLY TO back to everyone matching and the same rule becomes "$5 / month shared across the entire free tier" — a very different limit from the same filter.
When the limit resets
The third setting that changes a rule's meaning is its reset period. There are three kinds, and they behave differently.
| Reset period | Set it with | When a paused pool comes back |
|---|---|---|
| Calendar — hourly, daily, weekly, monthly | The Resets every dropdown in the builder and most templates | At the start of the next period, aligned to the UTC calendar: a day starts at UTC midnight, a week on Monday, a month on the 1st. |
| Sliding — 5 to 1440 minutes | The session and per-tag templates ("resets after N minutes of no spend") | Only after that many minutes with no matching spend for that pool. |
| Lifetime | Choosing Lifetime, or setting a limit with no period | Never. The cap is permanent until you change the rule. |
A sliding window is an inactivity window, not a rolling "last N minutes" window. While a pool keeps spending with no quiet gap that long, its total keeps growing and its budget never resets. That fits something that genuinely goes idle — a chat session, one customer's burst of work. For a pool that is always busy, or a cap that must reset on a schedule, use a calendar period instead.
The rules table shows which one a rule uses: per DAILY, resets 30m after last spend, or LIFETIME.

