Entitlements and limits
An entitlement is something a plan allows: a feature, an API, AI tokens, seats. With no limit it's a feature flag (the plan has it or it doesn't). With a limit it's metered in a credit, and each customer gets a meter for it.
policy: {
credits: {
request: { label: 'Request' }
ai_token: { label: 'AI token', price: { amount: 0.00002 } }
}
plans: {
pro: {
default: true
entitlements: {
// A feature flag
sso: {}
api: {
limit: {
credit: 'request', value: 100_000
resets: true, reset_sch: 'monthly:1'
}
}
chat: {
description: 'AI chat'
estimate_basis: 'input'
estimate_segment: 'model'
limit: {
credit: 'ai_token', mode: 'soft', value: 2_000_000
resets: true
}
}
}
}
}
}
Entitlement fields
| Field | Type | Default | Notes |
|---|---|---|---|
limit | Limit | null | No limit: a feature flag. |
description | str | '' | |
scope | str | null | Meter on the nearest customer of this type through refs (Ex. 'org'). See Scope. |
enabled | bool or fn | (absent) | Whether it's on for a call. See Feature rules. |
track | bool | false | Send feature-checked each time it's checked. See Feature rules. |
estimate_basis | str | null | Event data field a call's size depends on (Ex. 'input'). See Reservations. |
estimate_segment | str | null | Event data field that keeps separate estimates (Ex. 'model'). |
hidden | bool | false | Not used by the engine. A flag for your own UI. |
The same entitlement name can appear in several plans with different limits: api might be 1,000 a day on Free and
100,000 a month on Pro. Your code calls allow(id, 'api', 1) either way.
Limit fields
| Field | Type | Default | Notes |
|---|---|---|---|
credit | str | (required) | The credit this limit is counted in. |
value | number or units | 0 | The limit, in the credit's units (1000, '1GB', 2hr). |
mode | str | 'hard' | hard, soft, or observe (below). |
resets | bool | false | Start the meter over each period. Setting reset_sch turns it on. |
reset_inc | duration | 30 days | Period length (1s, 1hr, 1day, 30days). Used when resets and no reset_sch. |
reset_sch | str | null | A calendar schedule, in UTC (below). Can't be used with reset_inc. |
increment | number or units | 1 | The step for increment and decrement, and the standard size of a call. |
minimum | number or units | null | The meter never goes below this. |
hard_trailing | bool | false | Hard only: allow a call while the meter is under the limit, even if the call crosses it. |
grants_apply | bool | true | Whether grants can cover usage past the limit. Set false on safety limits. |
governor_enabled | bool | false | Add a token-bucket rate limit (below). |
governor_capacity | number | null | Bucket size: the largest burst. Required with a governor. |
governor_refill_rate | number | null | Tokens added per millisecond (0.001 is one a second). Required with a governor. |
ewma_alpha | float | 0.2 | Smoothing for the usage rate trend (0 to 1). |
Modes
| Mode | Past the limit | Event |
|---|---|---|
hard | Denied. Nothing changes. | meter-limit |
soft | Allowed. The part past the limit is overage: grants cover what they can, and the rest is billed. | meter-overage, with overage_price |
observe | Allowed. Usage is only recorded. | meter-changed |
Use observe to track a feature before pricing it, or to try a new limit without blocking anyone. remaining is
null for observe limits.
With hard_trailing: true, a hard limit lets through the call that crosses it (the meter was under before the call)
and denies the calls after. It suits usage you only know after the fact. On a 100 limit at 90, a 50-unit call is
allowed (140), and the next call is denied.
Resets
A limit with resets: true, or a reset_sch, starts a new period:
- Rolling with
reset_inc: that long after the last period began. 30 days if you don't set one. - Calendar with
reset_sch, in UTC:
| Schedule | Resets |
|---|---|
monthly:1 | On the 1st of each month. Use a day from 1 to 28, or monthly:last for month ends. |
monthly:last | On the last day of each month. |
weekly:mon | Every Monday (mon to sun). |
nth_weekday:2:tue | On the second Tuesday of each month. |
quarterly:1 | On day 1 of each quarter. |
yearly:1-1 | On January 1 (month-day). |
Nothing runs on a timer. The engine notices a period ended the next time the meter is used or read, so there are no
jobs to keep alive. A call that starts a new period also sends meter-reset. resets(id, entitlement) returns when the
current period ends, in milliseconds.
A limit with no reset is a running total (storage, seats), and usage can go back down with a negative value or
decrement.
Governors
A governor limits the rate of calls, below the limit, with a token bucket: governor_capacity tokens, refilled at
governor_refill_rate tokens per millisecond. Each call spends its value in tokens. A call without enough tokens is
denied and sends meter-governed.
chat_rate: {
limit: {
credit: 'request', value: 1e9, grants_apply: false
// Bursts of 5, then 1 a second
governor_enabled: true, governor_capacity: 5, governor_refill_rate: 0.001
}
}
A huge value makes the governor the only thing that matters. Governors work on hard limits. allowance(id, entitlement) is what can run right now: the room left, capped by the tokens in the bucket. See
Rate limits.
Scope
scope: 'org' meters the entitlement on the nearest customer of type org through the caller's refs, whichever user
calls. Seats are the usual case: any user's invite counts against the org's seats. A caller with no customer of that
type in its refs is denied. Scope works with any customer type ('team', 'workspace').
How a call is decided
allow and check go through these steps. Nothing changes until every step passes.
- The entitlement. The customer's plan has it (or it was given, and not taken), and its
enabledrule (if any) is on. A feature flag stops here: allowed. - Period reset. If the limit resets and the period ended, the meter starts from zero.
- Spend caps. Every cap that applies has room: the customer's own, shared caps from its refs, and any cap passed with the call.
- Grants. Usage past the limit is drawn from the customer's grants first.
- Governor. There are enough tokens in the bucket.
- Limit. Hard denies past the limit, soft allows and bills the overage, observe allows.
- Commit (
allowonly). The meter, its recorded cost, grants, and caps move, and events are sent.
check makes the same decision without changing anything. Two checks can both pass when only one call fits, so use
reserve and settle when calls run at the same time.
Unknown customers and entitlements are denied. Nothing throws, and nothing is allowed by mistake.
Values
- Units. Values can carry units in a string (
'250MB','2.5GB') or in Stof (250MB). - Negative values decrement. They're always allowed, and the meter won't go below
minimum. increment/decrementmove the meter by the limit'sincrement.force(onallow) records usage that already happened, past a hard limit or cap.settleuses it.
Stacking
One action can be metered by several entitlements at once, each with its own credit, mode, and reset: a report counts against a monthly report allowance and uses AI tokens billed past an included amount. Check every entitlement, then count every one. See the stacking tutorial.
Overrides
An override replaces an entitlement's limit for one customer: a bigger allowance, a different mode, a trial. See Customers.
Reads
Reads never meter anything, and they show the current period even before a pending reset happens.
| Call | Returns |
|---|---|
value(id, entitlement, percent = false) | Usage this period (or the percent of the limit, up to 100). |
remaining(id, entitlement, percent = false) | The limit minus usage, plus grants. Null for observe limits. |
limit(id, entitlement) | The limit, plus grants that apply to it. id can be a plan. |
allowance(id, entitlement) | What can run right now, counting the governor. |
cost(id, entitlement) | The standard increment. |
resets(id, entitlement) | When the meter resets next (ms), or null. |
rate(id, entitlement) / acceleration(id, entitlement) | Usage per millisecond, and its change. |
projectedExhaustion(id, entitlement) | When the limit runs out at the current rate (ms), or null. |
entitlement(id, entitlement) | The entitlement as the customer has it. |
meterObject(id, entitlement) / limitObject(id, entitlement) | The meter and limit records. |
The grants argument (default true) on value, remaining, limit, and allowance says whether to count grants.