Skip to main content

Entitlements

Resources a customer on a plan is allowed to use.

Entitlements live inside plan definitions, identified by a string ID. An entitlement without a limit is a boolean flag — present means the customer has access. An entitlement with a limit is metered.


Entitlement schema

entitlements:
<entitlement-id>:
description: string # Optional. Appears in event payloads.
hidden: bool # Exclude from public serialization. Default: false.
scope: string # Optional. Customer type scope (e.g. 'org').
limit: # Optional. Omit for a boolean access entitlement.
<Limit fields>

description

Human-readable description. Included in event payloads (meter-limit, meter-overage, meter-changed) and in the customer object. Useful for support tooling and dashboards.

hidden

If true, the entitlement is excluded from public policy serialization. Use for internal metering that customers shouldn't see.

scope

Ties the entitlement to a customer type. When set, enforcement operations for this entitlement look through the customer's refs to find a customer of the matching type, and operate against that customer's meter instead.

The canonical use case is org-scoped entitlements — a seats entitlement with scope: org means all users who ref the same org draw from the org's shared seat pool, not their individual meters.

seats:
scope: org
limit:
credit: seat
mode: hard
value: 10
increment: 1

Any member of the org calling policy.increment('user_123', 'seats') draws from the org's pool.


Limit schema

limit:
credit: string # Required. Credit ID for this limit.
mode: string # 'hard' | 'soft' | 'observe'. Default: 'hard'.
grants_apply: bool # Whether grants can extend this limit. Default: true.
value: float | string # Maximum value. Supports unit strings (e.g. '2GiB'). Default: 0.
increment: float | string # Amount per increment()/decrement(). Default: 1.
minimum: float | string # Optional floor value for decrement().
resets: bool # Does this meter reset? Default: false.
reset_inc: duration # Duration-based reset interval. Mutually exclusive with reset_sch.
reset_sch: string # Calendar reset schedule. Mutually exclusive with reset_inc.
override_expires_on: ms # Expiry for customer overrides only.
governor_enabled: bool # Rate ceiling below the hard limit. Default: false.
governor_capacity: float | string # Max burst tokens. Required when governor_enabled is true.
governor_refill_rate: float # Tokens per ms refill rate. Required when governor_enabled is true.
ewma_alpha: float # EWMA smoothing factor (0.0–1.0). Default: 0.2.

credit

The credit ID this limit is denominated in. Must reference a credit defined in policy.credits. Required.

mode

Controls what happens when consumption reaches the limit value.

ModeBehavior
hardBlocks the operation. allow() returns false. Fires meter-limit. No overage unless covered by a grant. Default.
softAllows the operation. Fires meter-overage. Limitr draws from applicable grants first before firing the event.
observeNever blocks. Meters indefinitely. No enforcement. Useful for tracking usage without restricting it.

value

The enforced maximum. When a customer's meter reaches this value, hard mode blocks and soft mode fires overage. Supports raw floats or unit strings when the credit has stof_units defined.

value: 500000 # raw float
value: '2GiB' # unit string — converted to the credit's stof_units
value: 0 # soft limit of 0: every increment fires meter-overage immediately

increment

The amount consumed or released per increment() / decrement() call. Defaults to 1. Supports unit strings.

increment: 1 # one seat per call
increment: '100MB' # 100MB per upload slot

minimum

An optional floor value. decrement() will not reduce the meter below this value.

grants_apply

Whether credit grants can extend this limit. Default true. Set to false on enforcement or rate-governing entitlements where grants should never punch through a rate ceiling, or on observe-mode entitlements where you want a clean, unmodified consumption signal.

# Enforcement entitlement — grants don't extend this ceiling
ai_tokens_daily:
limit:
credit: ai_token
mode: hard
grants_apply: false
value: 50000
resets: true
reset_sch: 'monthly:1'

resets, reset_inc, and reset_sch

Whether and how the meter resets. When resets: true, set either reset_inc for a fixed duration or reset_sch for a calendar schedule. The two are mutually exclusive. If neither is set, reset_inc defaults to 30days.

Duration-based reset (reset_inc)

The meter resets on a fixed interval from when it was last reset.

resets: true
reset_inc: 1day # resets every 24 hours
reset_inc: 30days # resets every 30 days (default)

Valid time units: ms, s, min, hr, day, days.

Calendar-based reset (reset_sch)

The meter resets on a calendar boundary, regardless of when the customer was created. All schedules use UTC.

resets: true
reset_sch: 'monthly:1' # 1st of every month
reset_sch: 'monthly:15' # 15th of every month
reset_sch: 'monthly:last' # last day of every month
reset_sch: 'weekly:mon' # every Monday
reset_sch: 'nth_weekday:1:tue' # first Tuesday of every month
reset_sch: 'nth_weekday:2:fri' # second Friday of every month

governor_enabled, governor_capacity, governor_refill_rate

An optional rate ceiling below the hard limit. When enabled, consumption is shaped before the wall is reached — customers are governed rather than cut off.

The governor uses a token bucket model: the bucket holds up to governor_capacity tokens and refills at governor_refill_rate tokens per millisecond. Each allow() call consumes tokens equal to the requested value. When the bucket is empty, the request is denied even if the hard limit hasn't been reached, and a meter-governed event fires.

limit:
credit: ai_token
mode: hard
value: 1000000
resets: true
reset_sch: 'monthly:1'
governor_enabled: true
governor_capacity: 50000 # max burst: 50k tokens at once
governor_refill_rate: 0.5 # sustained rate: 500 tokens/sec (0.5/ms)

The governor is most useful for enterprise customers with SLA obligations — it prevents a hard cutoff mid-operation while still protecting your infrastructure cost. Use allowance() to pre-size operations against the current governor state.

governor_capacity and governor_refill_rate are required when governor_enabled: true.

ewma_alpha

The smoothing factor for the exponentially weighted moving average rate calculation (0.0–1.0). Lower values smooth more aggressively (better for bursty workloads). Higher values track recent changes faster. Default: 0.2.

Used by projectedExhaustion(smoothed: true) for trend-based alerting rather than instantaneous rate projection.


Boolean entitlements

An entitlement with no limit field is a boolean flag. allow() or check() against it returns true if the entitlement exists on the plan, false if it doesn't.

entitlements:
pdf_export:
description: Access to PDF export feature
const canExport = await policy.check('user_123', 'pdf_export');
if (!canExport) return { error: 'Upgrade to export PDFs' };

Customer overrides

An override replaces the limit for a specific customer without changing the plan. Any limit field can be overridden. Overrides can have an expiry, after which the customer reverts to their plan's limit.

// Give enterprise_org 500 seats instead of the plan default
await policy.createCustomerOverride('enterprise_org', 'seats', 500);

// Override with an expiry
await policy.createCustomerOverride(
'user_123',
'chat_input',
2000000,
Date.now() + 30 * 24 * 60 * 60 * 1000 // expires in 30 days
);

// Remove an override — customer reverts to plan limit
await policy.removeCustomerOverride('user_123', 'chat_input');

Full signature:

policy.createCustomerOverride(
id: string, // customer ID
entitlement: string, // entitlement name
value?: string | number, // limit value
expires_on?: number, // expiry timestamp (ms)
credit?: string, // credit ID (if changing the credit)
mode?: string, // 'hard' | 'soft' | 'observe'
increment?: number | string,
resets?: boolean,
reset_inc?: number | string, // mutually exclusive with reset_sch
reset_sch?: string // mutually exclusive with reset_inc
): Promise<string | null> // returns override node ID, or null on failure

SDK

policy.entitlement()

Returns the entitlement record for a given plan ID or customer ID and entitlement name. Includes the resolved limit with any customer override applied.

const ent = await policy.entitlement('user_123', 'chat_input');
// { description, limit: { credit, mode, value, resets, reset_inc, reset_sch, ... } }

policy.limit()

Returns the enforced limit value for a customer's entitlement. Includes credit grant balances in the effective limit when grants is true (default). Returns null if no limit is defined.

const limit = await policy.limit('user_123', 'chat_input'); // with grants
const limit = await policy.limit('user_123', 'chat_input', false); // without grants

policy.remaining()

Returns the remaining balance (limit - value). Includes grant balances by default.

const left = await policy.remaining('user_123', 'chat_input');

policy.allowance()

Returns how much of an entitlement a customer can consume right now — the binding constraint of either the governor token balance or the remaining period balance, whichever is smaller. Falls back to remaining() when no governor is configured.

Use this to pre-size operations before submitting them, so they're guaranteed to pass allow().

const budget = await policy.allowance('user_123', 'ai_tokens');
const duration = Math.min(desiredDuration, budget);

if (await policy.check('user_123', 'ai_tokens', duration)) {
await policy.allow('user_123', 'ai_tokens', duration);
generateVideo(duration);
}

policy.projectedExhaustion()

Returns the estimated time in milliseconds until a customer's entitlement is exhausted at their current rate of consumption. Returns null if there is no consumption history or the rate is zero. Meaningful after at least two allow() calls.

// Instantaneous rate projection
const msUntilEmpty = await policy.projectedExhaustion('user_123', 'ai_tokens');

// Smoothed rate projection (EWMA) — better for alerting
const msSmoothed = await policy.projectedExhaustion('user_123', 'ai_tokens', true);

if (msSmoothed !== null && msSmoothed < 24 * 60 * 60 * 1000) {
// Customer will exhaust their allocation within 24 hours
notifyCustomerSuccess(customerId);
}

Parameters: customer, entitlement, smoothed? (default false), grants? (default true)

policy.rate()

Returns the current instantaneous consumption rate in units per millisecond, derived from the last two allow() calls. Returns 0 if fewer than two calls have been made.

const rate = await policy.rate('user_123', 'ai_tokens'); // tokens/ms

policy.acceleration()

Returns the rate of change between the two most recent consumption intervals. Positive means consumption is speeding up, negative means slowing down. Returns 0 if fewer than three allow() calls have been made.

const acc = await policy.acceleration('user_123', 'ai_tokens');

policy.resets()

Returns the timestamp (unix ms) when this entitlement's meter will next reset. Returns null if the entitlement doesn't reset.

const nextReset = await policy.resets('user_123', 'ai_tokens');
// Display as a date: new Date(nextReset).toLocaleDateString()

policy.createCustomerOverride()

See Customer overrides above.

policy.removeCustomerOverride()

Removes a customer override. The customer reverts to their plan's limit.

await policy.removeCustomerOverride('user_123', 'chat_input');

Patterns

Layered governors with billing meter

The recommended pattern for enterprise AI products: multiple hard-limited enforcement entitlements govern rate and period, with a soft-limited entitlement recording billable consumption.

entitlements:
ai_tokens_daily:
limit:
credit: ai_token
mode: hard
grants_apply: false
value: 50000
resets: true
reset_inc: 1day
governor_enabled: true
governor_capacity: 5000
governor_refill_rate: 0.058
ai_tokens_monthly:
limit:
credit: ai_token
mode: hard
grants_apply: false
value: 1000000
resets: true
reset_sch: 'monthly:1'
governor_enabled: true
governor_capacity: 50000
governor_refill_rate: 0.5
ai_tokens_billing:
limit:
credit: ai_token
mode: soft
value: 0
const budget = Math.min(
await policy.allowance(id, 'ai_tokens_daily'),
await policy.allowance(id, 'ai_tokens_monthly')
);

if (
await policy.check(id, 'ai_tokens_daily', budget) &&
await policy.check(id, 'ai_tokens_monthly', budget)
) {
await policy.allow(id, 'ai_tokens_daily', budget);
await policy.allow(id, 'ai_tokens_monthly', budget);
await policy.allow(id, 'ai_tokens_billing', budget); // always records
}