Quickstart
Install
npm i @formata/limitr
Limitr runs in Node, Bun, Deno, and the browser. The package includes the engine, so there's nothing else to run.
Your first policy and calls
Save this as quickstart.mts and run it with npx tsx quickstart.mts (or Bun or Deno):
import { Limitr } from '@formata/limitr';
// 1. The policy: what each plan includes, what it costs you, and what you charge.
const limitr = await Limitr.new(`
policy: {
credits: {
request: { label: 'API request' }
ai_token: { label: 'AI token', price: { amount: 0.00002 }, overhead_cost: 0.000008 }
}
plans: {
free: {
default: true
entitlements: {
api: {
limit: {
credit: 'request', value: 1000
resets: true, reset_inc: 1day
}
}
chat: {
limit: {
credit: 'ai_token', value: 50_000
resets: true, reset_sch: 'monthly:1'
}
}
}
}
pro: {
entitlements: {
api: {
limit: {
credit: 'request', value: 100_000
resets: true, reset_sch: 'monthly:1'
}
}
chat: {
limit: {
credit: 'ai_token', mode: 'soft', value: 2_000_000
resets: true, reset_sch: 'monthly:1'
}
}
export_pdf: {}
}
}
}
}`);
// 2. Events: bill overage as it happens.
limitr.addHandler('billing', (name, value) => {
if (name === 'meter-overage') {
const event = JSON.parse(value as string);
console.log(`bill ${event.customer.id}: $${event.overage_price}`);
}
});
// 3. Customers: created on first sight, on the default plan unless you say otherwise.
await limitr.ensureCustomer('ada');
await limitr.ensureCustomer('acme', 'pro');
// 4. Ask before doing anything that costs money.
console.log('ada, 1 API request:', await limitr.allow('ada', 'api', 1));
console.log('ada, PDF export:', await limitr.allow('ada', 'export_pdf'));
console.log('ada, 60k tokens:', await limitr.allow('ada', 'chat', 60_000));
console.log('acme, PDF export:', await limitr.allow('acme', 'export_pdf'));
console.log('acme, 2.1M tokens:', await limitr.allow('acme', 'chat', 2_100_000));
console.log('acme tokens left:', await limitr.remaining('acme', 'chat'));
It prints:
ada, 1 API request: true
ada, PDF export: false
ada, 60k tokens: false
acme, PDF export: true
bill acme: $2
acme, 2.1M tokens: true
acme tokens left: -100000
What happened
- The policy has two credits (the things you count) and two plans. Each plan lists entitlements: what its
customers can do.
export_pdfhas no limit, so it's a feature flag: Pro has it, Free doesn't.apiandchathave limits that reset daily or monthly. - Ada was created without a plan, so she's on the default plan (Free). Her PDF export was denied (not on her plan), and so was 60k tokens: Free's chat limit is hard, at 50,000 a month.
- Acme is on Pro, where chat is
soft: the call went 100,000 tokens past the 2 million included, was allowed, and the overage was billed through themeter-overageevent, at the credit's price ($20 per million). allowdecides and, when the answer is yes, counts the usage. A denied call changes nothing. Unknown customers and entitlements are denied; nothing throws.
In your app
Create the engine once when your server starts, and ask in one place, like a middleware:
limitr.ts
import { Limitr } from '@formata/limitr';
import { readFileSync } from 'node:fs';
export const limitr = await Limitr.new(readFileSync('./policy.stof', 'utf8'));
export async function gate(userId: string, entitlement: string, amount = 1): Promise<boolean> {
await limitr.ensureCustomer(userId);
return limitr.allow(userId, entitlement, amount);
}
- Keep one engine per process. It holds customer state in memory, which is why calls are fast.
- Save customer state if you self-host: see Save customer state. Or use
Limitr Cloud, where
Limitr.cloud({ token })replacesLimitr.newand state is managed for you. - Await every call.
if (limitr.allow(...))withoutawaitis always true.
Next
- How Limitr works: credits, plans, entitlements, customers, and how a call is decided.
- AI calls: reserve and settle: for calls whose size you only know afterwards.
- Map your pricing: turn your pricing page into a policy.
- The tutorial: everything, one runnable chapter at a time.