Skip to main content

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​

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

MethodReturns
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).
docThe underlying Stof document. See Runtime.

Metering​

MethodReturnsNotes
allow(customer, entitlement, value = 0, event = true, overhead?, force = false)boolDecide 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?)boolThe same decision, no changes.
increment(customer, entitlement, event = true) / decrement(...)boolBy the limit's increment.
checkIncrement(customer, entitlement) / checkDecrement(...)bool
set(customer, entitlement, value, event = true)boolMoves the meter to value (allows the difference).
features(customer, context?)string[] or nullEntitlements the customer can use now.

value can carry units in a string ('250MB').

Reservations and estimates​

MethodReturns
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​

MethodReturns
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​

MethodReturnsNotes
ensureCustomer(id, plan = '', type = 'user', label = 'User', refs?, alts?, metadata?)boolCreates it if missing. True if it was created. IDs can't contain a period (IDs).
createCustomer(...) (same arguments)recordCreates 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)boolSet only if not loaded.
loadCustomers(records)An array, or an object keyed by ID.
removeCustomer(id)bool
setCustomerPlan(id, plan, overwrite_meters = true)boolPass 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)boolFalse 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)boolCharge the plan's subscription once (after its trial).

See Customers.

Spend caps and margin​

MethodReturns
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​

MethodReturns
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​

MethodNotes
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​

MethodReturns
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):

OptionDefaultNotes
token(required)Your Cloud API token.
policy'active'A policy ID, or the active policy.
connectTimeout5,000 msHow long to wait for the first connection.
denyUnconnectedtrueDeny allow while disconnected, so servers can't drift apart.
validatetrueValidate the policy Cloud sends.
httpHosts['api.limitr.dev']Hosts the policy may call. true for any, false for none.
maxExecutionTime5,000 msTime limit for each policy call.
MethodNotes
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.