Enforcement
Where application code meets policy.
The enforcement API is where your application code meets the Limitr policy. Every operation runs in-process via WebAssembly — no network calls, sub-millisecond latency on the hot path. In Cloud mode, usage syncs asynchronously; enforcement decisions are never gated on network availability.
Core operations
policy.allow()
The primary enforcement and metering method. If the operation is within the customer's entitlement, the meter is updated and true is returned. If blocked, false is returned and a meter-limit event fires.
allow(
customer: string,
entitlement: string,
value: number | string = 0, // amount to consume; 0 = boolean check
event: boolean = true // whether to fire events
): Promise<boolean>
value can be a raw number or a unit string if the credit has stof_units defined:
await policy.allow('user_abc', 'chat_input', 4200); // 4200 tokens
await policy.allow('user_abc', 'file_storage', '500MB'); // unit string
await policy.allow('user_abc', 'chat_access'); // boolean check (value = 0)
When value is 0 and the entitlement has no limit, allow() returns true. This is the boolean access check pattern.
policy.increment()
Consumes the entitlement's defined limit.increment value (default: 1). Returns true if within limit, false if blocked. Equivalent to allow(id, entitlement, increment).
if (await policy.increment('org_xyz', 'seats')) {
await db.addMember(userId, orgId);
} else {
return { error: 'Seat limit reached' };
}
policy.decrement()
Releases the entitlement's defined limit.increment value. Used for resources that can be returned — removing a seat, deleting a file, closing a workspace. Respects limit.minimum if defined.
await policy.decrement('org_xyz', 'seats');
policy.check()
Read-only. Returns whether an allow() call with this value would succeed, without updating the meter. Use for pre-authorization checks — UI gates, pre-flight checks before expensive operations.
// Pre-flight check — don't consume yet
const authorized = await policy.check('user_abc', 'chat_input', estimatedTokens);
if (!authorized) return { error: 'Insufficient token balance' };
// Consume after the operation with actual usage
await policy.allow('user_abc', 'chat_input', actualTokens);
policy.set()
Sets the meter to an absolute value by computing allow(value - current). Use for storage or state-based metering where you know the total, not the delta.
await policy.set('user_abc', 'file_storage', currentUsageBytes);
Read operations
policy.value()
Returns the current meter value for a customer's entitlement.
value(
customer: string,
entitlement: string,
percent: boolean = false, // if true, returns % of limit (0–100)
grants: boolean = true // include grant balances in percentage calculation
): Promise<number | null>
const used = await policy.value('user_abc', 'chat_input'); // raw count
const usedPct = await policy.value('user_abc', 'chat_input', true); // % of limit
Returns null if the customer or entitlement doesn't exist.
policy.remaining()
Returns the remaining balance (limit - value). Includes credit grant balances in the effective limit when grants is true (default).
const left = await policy.remaining('user_abc', 'chat_input');
const leftPct = await policy.remaining('user_abc', 'chat_input', true); // as %
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_abc', 'ai_tokens');
// Size your operation to budget, then call allow()
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.
Pass smoothed: true to use the EWMA rate instead of the instantaneous rate — better for alerting since it doesn't overreact to bursts.
projectedExhaustion(
customer: string,
entitlement: string,
smoothed: boolean = false,
grants: boolean = true
): Promise<number | null> // ms until exhaustion, or null
const msLeft = await policy.projectedExhaustion('user_abc', 'ai_tokens', true);
if (msLeft !== null && msLeft < 86_400_000) {
// Customer will exhaust within 24 hours — trigger upsell flow
}
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_abc', '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_abc', 'ai_tokens');
if (acc > 0) {
// Consumption is accelerating — check projected exhaustion
}
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_abc', 'ai_tokens');
policy.limit()
Returns the enforced limit value, including any active customer override. When grants is true (default), adds the customer's applicable grant balance to the effective limit.
const limit = await policy.limit('user_abc', 'chat_input');
policy.cost()
Returns the rune cost of a standard increment() on this entitlement. Useful for billing calculations.
const cost = await policy.cost('growth', 'seats'); // cost per seat add
Events
Every enforcement operation can fire one of four events. Subscribe with addHandler().
| Event | When it fires |
|---|---|
meter-changed | Every successful allow(), increment(), or decrement() that updates a meter |
meter-limit | When allow() or increment() is blocked by a hard limit |
meter-overage | When allow() or increment() exceeds a soft limit and grants are exhausted |
meter-governed | When allow() is blocked by a governor rate ceiling before the hard limit is reached |
Event payload shape
All three metering event types share the same base structure. meter-governed includes additional governor context. The value argument passed to your handler is a JSON string.
{
customer: {
id: string,
plan: string,
type: string,
},
entitlement: string, // entitlement name
plan: string, // plan ID
credit: {
description: string, // credit description
// ... other credit fields
},
meter: {
value: number, // meter value after operation
limit: number, // effective limit
invalid: number, // value that would have been set (limit events only)
// ... other meter fields
},
overage: number, // meter-overage only: amount over limit after grants
grant_value_applied: number, // meter-overage only: how much grant covered
governor: { // meter-governed only
tokens: number, // token balance at time of denial
capacity: number, // governor bucket capacity
requested: number, // amount requested
},
}
Event handler API
policy.addHandler()
Registers a named event handler. All events from all operations are routed through every registered handler.
policy.addHandler('my-handler', (key: string, value: unknown) => {
const event = JSON.parse(value as string);
if (key === 'meter-changed') {
// fires on every successful consumption
}
if (key === 'meter-limit') {
// fires when a hard limit blocks a request
notifySupport(event.customer.id, event.entitlement);
}
if (key === 'meter-overage') {
// fires when a soft limit is exceeded and grants are exhausted
billing.queueCharge(event.customer.id, event.entitlement, event.overage);
}
});
policy.removeHandler()
Removes a handler by name. Returns true if it existed.
policy.removeHandler('my-handler');
policy.clearHandlers()
Removes all registered handlers.
Patterns
Feature gate
const canExport = await policy.check('user_abc', 'pdf_export');
if (!canExport) return { error: 'Upgrade to export PDFs' };
AI token metering with access check
if (!await policy.check('user_abc', 'chat_access')) {
return { error: 'No chat access on this plan' };
}
if (await policy.allow('user_abc', 'chat_input', estimatedTokens)) {
const response = await callLLM(prompt);
// Meter actual output after the call
await policy.allow('user_abc', 'chat_output', response.usage.output_tokens);
} else {
return { error: 'Token limit reached' };
}
Seat-based SaaS with org scope
if (await policy.increment('org_xyz', 'seats')) {
await db.addMember(userId, orgId);
} else {
return { error: 'Seat limit reached. Upgrade or remove a member.' };
}
// When a member is removed
await policy.decrement('org_xyz', 'seats');
Storage with unit conversion
if (await policy.allow('user_abc', 'file_storage', `${fileBytes}bytes`)) {
await uploadFile(file);
} else {
return { error: 'Storage limit exceeded' };
}
Soft-limit overage billing
policy.addHandler('billing', (key, value) => {
if (key === 'meter-overage') {
const event = JSON.parse(value as string);
// event.overage is what wasn't covered by grants
billing.queueCharge(event.customer.id, event.credit, event.overage);
}
});
Governor rate limiting
policy.addHandler('governor', (key, value) => {
if (key === 'meter-governed') {
const event = JSON.parse(value as string);
// Customer hit the rate ceiling, not the hard limit
// event.governor.tokens — bucket balance at denial
// event.governor.requested — what they asked for
return { error: 'Rate limit reached. Please slow down.', retryAfter: 2000 };
}
});
Projected exhaustion alerting
policy.addHandler('alerts', async (key, value) => {
if (key === 'meter-changed') {
const event = JSON.parse(value as string);
const msLeft = await policy.projectedExhaustion(
event.customer.id,
event.entitlement,
true // smoothed — doesn't overreact to bursts
);
if (msLeft !== null && msLeft < 24 * 60 * 60 * 1000) {
await notifyCustomerSuccess(event.customer.id, event.entitlement);
}
}
});