Skip to main content

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​

FieldTypeDefaultNotes
limitLimitnullNo limit: a feature flag.
descriptionstr''
scopestrnullMeter on the nearest customer of this type through refs (Ex. 'org'). See Scope.
enabledbool or fn(absent)Whether it's on for a call. See Feature rules.
trackboolfalseSend feature-checked each time it's checked. See Feature rules.
estimate_basisstrnullEvent data field a call's size depends on (Ex. 'input'). See Reservations.
estimate_segmentstrnullEvent data field that keeps separate estimates (Ex. 'model').
hiddenboolfalseNot 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​

FieldTypeDefaultNotes
creditstr(required)The credit this limit is counted in.
valuenumber or units0The limit, in the credit's units (1000, '1GB', 2hr).
modestr'hard'hard, soft, or observe (below).
resetsboolfalseStart the meter over each period. Setting reset_sch turns it on.
reset_incduration30 daysPeriod length (1s, 1hr, 1day, 30days). Used when resets and no reset_sch.
reset_schstrnullA calendar schedule, in UTC (below). Can't be used with reset_inc.
incrementnumber or units1The step for increment and decrement, and the standard size of a call.
minimumnumber or unitsnullThe meter never goes below this.
hard_trailingboolfalseHard only: allow a call while the meter is under the limit, even if the call crosses it.
grants_applybooltrueWhether grants can cover usage past the limit. Set false on safety limits.
governor_enabledboolfalseAdd a token-bucket rate limit (below).
governor_capacitynumbernullBucket size: the largest burst. Required with a governor.
governor_refill_ratenumbernullTokens added per millisecond (0.001 is one a second). Required with a governor.
ewma_alphafloat0.2Smoothing for the usage rate trend (0 to 1).

Modes​

ModePast the limitEvent
hardDenied. Nothing changes.meter-limit
softAllowed. The part past the limit is overage: grants cover what they can, and the rest is billed.meter-overage, with overage_price
observeAllowed. 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:
ScheduleResets
monthly:1On the 1st of each month. Use a day from 1 to 28, or monthly:last for month ends.
monthly:lastOn the last day of each month.
weekly:monEvery Monday (mon to sun).
nth_weekday:2:tueOn the second Tuesday of each month.
quarterly:1On day 1 of each quarter.
yearly:1-1On 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.

  1. The entitlement. The customer's plan has it (or it was given, and not taken), and its enabled rule (if any) is on. A feature flag stops here: allowed.
  2. Period reset. If the limit resets and the period ended, the meter starts from zero.
  3. Spend caps. Every cap that applies has room: the customer's own, shared caps from its refs, and any cap passed with the call.
  4. Grants. Usage past the limit is drawn from the customer's grants first.
  5. Governor. There are enough tokens in the bucket.
  6. Limit. Hard denies past the limit, soft allows and bills the overage, observe allows.
  7. Commit (allow only). 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 / decrement move the meter by the limit's increment.
  • force (on allow) records usage that already happened, past a hard limit or cap. settle uses 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.

CallReturns
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.