Budgets and spend limits
Usage-based pricing worries customers: what if a script runs all weekend? A spend limit they control fixes that. This one is a monthly overage budget in euros, set from your app's billing settings, with a warning at 80%.
import { Limitr } from '@formata/limitr';
const limitr = await Limitr.new(`
policy: {
exchange: { euro: { value: 1.14, currency: 'usd' } }
credits: {
ai_token: { label: 'AI token', price: { amount: 0.00002 }, overhead_cost: 0.000008 }
}
plans: {
pro: {
default: true
entitlements: {
// 1M tokens included each month, then billed.
chat: {
limit: {
credit: 'ai_token', mode: 'soft', value: 1_000_000
resets: true, reset_sch: 'monthly:1'
}
}
}
}
}
}`);
limitr.addHandler('budget', (name, value) => {
const { customer, cap } = JSON.parse(value as string);
if (name === 'cap-threshold-crossed' && cap.id === 'overage_warning') {
console.log(` email ${customer.id}: 80% of your overage budget is used`);
}
if (name === 'cap-limit') {
console.log(` denied: ${customer.id} reached its €${cap.value} budget`);
}
});
await limitr.ensureCustomer('ada');
// The customer sets a monthly overage budget of €10 in your app's billing settings.
async function setBudget(customer: string, euros: number) {
await limitr.removeCustomerCap(customer, 'overage_budget', false);
await limitr.removeCustomerCap(customer, 'overage_warning', false);
// Counted in euros, only on overage, starting over each month.
const monthly = {
credit: 'euro',
overage_only: true,
resets: true, reset_sch: 'monthly:1',
};
await limitr.addCustomerCap(customer, euros, {
...monthly,
cap_id: 'overage_budget',
});
await limitr.addCustomerCap(customer, euros * 0.8, {
...monthly,
cap_id: 'overage_warning',
observe_only: true, // warns, never denies
});
}
await setBudget('ada', 10);
// Usage inside the plan never counts against an overage budget.
console.log('1M included tokens:', await limitr.allow('ada', 'chat', 1_000_000));
// 100k tokens at $20 per million, in euros.
const euros = (100_000 * 0.00002 / 1.14).toFixed(2);
for (let i = 1; i <= 6; i++) {
console.log(`100k more tokens (€${euros}):`, await limitr.allow('ada', 'chat', 100_000));
}
const spent = (await limitr.customerCap('ada', 'overage_budget'))!.meter_value;
console.log(`overage this month: €${spent.toFixed(2)} of €10`);
It prints:
1M included tokens: true
100k more tokens (€1.75): true
100k more tokens (€1.75): true
100k more tokens (€1.75): true
100k more tokens (€1.75): true
email ada: 80% of your overage budget is used
100k more tokens (€1.75): true
denied: ada reached its €10 budget
100k more tokens (€1.75): false
overage this month: €8.77 of €10
How it works
- A spend cap is a ceiling across calls. This one counts in euros (through the policy's
exchange), counts only overage (
overage_only), and starts over each month. - The warning is a second cap at 80% with
observe_only: it never denies, and sendscap-threshold-crossedonce when it's reached. - Denials send
cap-limitwith the cap that stopped the call, so your app can say why. - Caps only restrict. They never allow something a limit would deny.
Changing a budget
A cap's value can't be edited in place. Remove it and add a new one, as setBudget does. The new cap starts counting
from zero, so change budgets at the start of a period, or add what was already spent to the new value.
Budgets for an org and its teams
A cap with shared: true applies to every customer whose refs lead to the holder:
const monthly = { shared: true, resets: true, reset_sch: 'monthly:1' };
await limitr.addCustomerCap('acme', 500, { ...monthly, cap_id: 'org_monthly' });
await limitr.addCustomerCap('team_a', 100, { ...monthly, cap_id: 'team_monthly' });
A user in team A is stopped by whichever budget runs out first. shared_with: ['user'] limits a shared cap to calls
made by users.
Other budgets
| Budget | Cap |
|---|---|
| $50 a month across everything | addCustomerCap(id, 50, { resets: true, reset_sch: 'monthly:1' }) |
| Only some features | { scope: ['chat', 'images'] } |
| What a customer can cost you | { overhead_cost: true } |
| Usage covered by credit packs doesn't count | { ignore_grants: true } |
| A one-off budget for a job | Pass a cap with the call (Stof), or add one and remove it after. |
| Tokens instead of money | { credit: 'ai_token' } |
See Spend caps for every option.