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.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 three 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 |
Event payload shape
All three event types share the same base structure. 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
}
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);
}
});