TypeScript SDK
npm i @formata/limitr
@formata/limitr runs the Limitr engine in-process, as WebAssembly, in Node, Bun, Deno, and the browser. It depends
on @formata/stof (0.10.6 or later). Every method is async and returns plain values or records.
import { Limitr } from '@formata/limitr';
const limitr = await Limitr.new(policyText); // or Limitr.cloud({ token })
The package also ships two skills for AI coding agents in node_modules/@formata/limitr/skill/: limitr (policies,
engine functions, events) and limitr-ts (this SDK). See the introduction.
Creating the engine
| Call | Notes |
|---|---|
Limitr.new(policy?, format = 'stof', validate = true) | Loads the engine and the policy and validates it (throws with the message when invalid). policy is a string (Stof, JSON, YAML, TOML), an object with 'json', or bytes with 'bstf'. |
Limitr.cloud(options) | Connects to Limitr Cloud. The policy and customers come from Cloud. |
new Limitr(policy?, format?) | Synchronous; call await initStof() from @formata/stof first. |
Create one engine per process (or per policy) and keep it. It holds every customer's state in memory.
| Method | Returns |
|---|---|
valid() | [ok, message] |
version() | The engine version. |
setMaxExecutionTime(ms | null) | Time limit for each policy call (default 5,000 ms; null for none). |
difference(other, symmetric = false) | A diff of two policies. |
docCall(path, ...args) | Calls any engine function, Ex. docCall('policy.set_customer_refs', id, refs). |
doc | The underlying Stof document. See Runtime. |
Metering
| Method | Returns | Notes |
|---|---|---|
allow(customer, entitlement, value = 0, event = true, overhead?, force = false) | bool | Decide and meter. event: true, false, or event data (object or string). overhead: the actual cost in runes. force: record usage that already happened. |
check(customer, entitlement, value = 0, context?, overhead?) | bool | The same decision, no changes. |
increment(customer, entitlement, event = true) / decrement(...) | bool | By the limit's increment. |
checkIncrement(customer, entitlement) / checkDecrement(...) | bool | |
set(customer, entitlement, value, event = true) | bool | Moves the meter to value (allows the difference). |
features(customer, context?) | string[] or null | Entitlements the customer can use now. |
value can carry units in a string ('250MB').
Reservations and estimates
| Method | Returns |
|---|---|
reserve(customer, entitlement, { value?, context?, basis?, segment?, ttl?, quantile?, overhead? }) | Hold ID, or null when not allowed now. |
settle(customer, entitlement, holdId, value, event = true, overhead?, basis?, segment?) | bool |
release(customer, entitlement, holdId) | bool |
held(customer, entitlement) | Room held now. |
estimate(customer, entitlement, { context?, quantile?, basis?, segment? }) | { value, overhead, samples, source, scope } or null. |
observe(customer, entitlement, value, { context?, overhead?, basis?, segment? }) | bool |
See Reservations and estimates.
Reads
| Method | Returns |
|---|---|
value(customer, entitlement, percent = false, grants = true) | Usage this period. |
remaining(customer, entitlement, percent = false, grants = true) | Room left (null for observe limits). |
limit(id, entitlement, grants = true) | The limit (id can be a plan). |
allowance(customer, entitlement, grants = true) | What can run now, counting the governor. |
cost(id, entitlement) | The standard increment. |
resets(id, entitlement) | When the meter resets next (ms). |
rate(customer, entitlement) / acceleration(...) | Usage per ms, and its change. |
projectedExhaustion(customer, entitlement, smoothed = false, grants = true) | When the limit runs out at this rate (ms). |
entitlement(id, entitlement) | The entitlement as the customer (or plan) has it. |
meterObject(id, entitlement) / limitObject(id, entitlement) | The meter and limit records. |
remainingCredit(customer, credit) | Grant balance in a credit. |
Customers
| Method | Returns | Notes |
|---|---|---|
ensureCustomer(id, plan = '', type = 'user', label = 'User', refs?, alts?, metadata?) | bool | Creates it if missing. True if it was created. IDs can't contain a period (IDs). |
createCustomer(...) (same arguments) | record | Creates or updates. Undefined if it couldn't be created. |
customer(id) | LimitrCustomer or undefined | |
customers() | records by ID | |
customerMetadata(id) / customerRefs(id) | ||
setCustomer(record, event = true) | Replace from a saved record. | |
ensureSetCustomer(record, event = true) | bool | Set only if not loaded. |
loadCustomers(records) | An array, or an object keyed by ID. | |
removeCustomer(id) | bool | |
setCustomerPlan(id, plan, overwrite_meters = true) | bool | Pass false to keep usage (the engine's own default). Included packs move by plan_change (Plan changes). |
addAltID(existing, alt, event = true) / removeAltID(alt, event = true) | bool | False for an alt ID with a period. |
createCustomerOverride(id, entitlement, value?, expires_on?, credit?, mode?, increment?, resets?, reset_inc?, reset_sch?) | Per-customer limit. | |
removeCustomerOverride(id, entitlement) | bool | |
giveEntitlement(customer, entitlement, plan?, expiresOn?) | bool | |
takeEntitlement(customer, entitlement, expiresOn?) | bool | |
restoreEntitlement(customer, entitlement) | bool | |
applyCustomerTopup(id, topup) | bool | |
ensureCustomerIncludedTopups(id, event = true) | Grants included topups the customer is missing. Creating a customer and changing its plan already do this. | |
ensureCustomerPlanQuantity(id) | bool | Charge the plan's subscription once (after its trial). |
See Customers.
Spend caps and margin
| Method | Returns |
|---|---|
addCustomerCap(id, value, options) | LimitrCap or null. Options: cap_id, credit, exchangeable, scope, overage_only, ignore_grants, overhead_cost, observe_only, hard_trailing, follow_decrements, shared, shared_with, resets, reset_inc, reset_sch, expires_on, send_events. |
customerCap(id, capId) | LimitrCap or undefined. |
resetCustomerCap(id, capId, event = true) / removeCustomerCap(...) | bool |
customerMarginSnapshot(customer) | Revenue, cost, and margin this period. |
marginSnapshot(plan, usage) | Margin for hypothetical usage. |
startMarginMeasurement(customer, capId, overage_only = true, ignore_grants = true, credit = 'rune') | Starts measuring a session. |
captureMarginMeasurement(customer, capId, stop = true) | { charged, costs, margin, rawMargin }. |
See Spend caps and Cost and margin.
Plans and credits
| Method | Returns |
|---|---|
plan(id, def = true) / defaultPlan() | A plan record. |
setPlan(id, planStof) / deletePlan(id) | Add, replace, or remove a plan. |
planPeriod(id) / planTrialPeriod(id) / planSubEntitlementName(id) | |
credit(id) / creditFor(id, entitlement) | A credit record. |
creditExchange(in, out = 'rune', value = 1) | A conversion. |
setExchangePair(name, value, currency = 'rune') | Add or replace an exchange pair. False, with nothing changed, if it can't reach runes. |
exchangePair(name) / removeExchangePair(name) | Read a pair ({ value, currency }), or remove one. |
Events
| Method | Notes |
|---|---|
addHandler(name, handler) | handler(key, value) gets every event. Objects arrive as JSON strings. |
removeHandler(name) / clearHandlers() | |
setNotifications(contents, format = 'stof') | Add notifications. |
Handlers run during the call that sent the event, and async handlers aren't awaited. A handler that throws is logged and never fails the call. See Events.
Capabilities
| Method | Returns |
|---|---|
setCapabilities(contents, format = 'stof') | Adds capabilities. |
capability(name) | The capability. |
runCapability(name, args = {}, customerId?) | Its result. |
claudeTools(customerId?) | Tool definitions for the Claude API. |
claudeToolUse(toolUse, customerId?) | Runs a tool_use block; returns the tool_result. |
See AI tools.
Limitr Cloud
Limitr.cloud(options):
| Option | Default | Notes |
|---|---|---|
token | (required) | Your Cloud API token. |
policy | 'active' | A policy ID, or the active policy. |
connectTimeout | 5,000 ms | How long to wait for the first connection. |
denyUnconnected | true | Deny allow while disconnected, so servers can't drift apart. |
validate | true | Validate the policy Cloud sends. |
httpHosts | ['api.limitr.dev'] | Hosts the policy may call. true for any, false for none. |
maxExecutionTime | 5,000 ms | Time limit for each policy call. |
| Method | Notes |
|---|---|
addCloudCustomer(id, timeout = 3000) | Fetch a customer from Cloud. Done for you on first use. |
addVoucher(code) | Redeem a Limitr voucher as a customer. |
close() | Send pending updates. Call it on shutdown. |
Types
LimitrCustomer, LimitrCap, LimitrCapOptions, LimitrEstimate, LimitrReserveOptions,
LimitrEstimateOptions, LimitrObserveOptions, and LimitrCloudInit are exported from the package.
Concurrency
Each method call runs on its own, one at a time, so a single allow is safe under load. Two calls in a row aren't one
unit: another request can run between a check and the allow after it. Use reserve when
concurrent calls mustn't overspend.