Credits and pricing
A credit is a unit of something you count: a request, an AI token, a seat, a megabyte, an image. Limits are written in credits, and credits carry the money: what you charge for a unit and what a unit costs you.
policy: {
credits: {
request: { label: 'API request', unit: 'request' }
storage: { label: 'Storage', stof_units: 'MB', price: { amount: 0.0001 } }
ai_token: {
label: 'AI token'
price: { amount: 0.00002 } // you charge $20 per million tokens
overhead_cost: 0.000008 // it costs you $8 per million
}
}
}
Fields
| Field | Type | Default | Notes |
|---|---|---|---|
label | str | 'Credit' | Name for people. |
unit | str | 'credit' | Singular unit name. |
description | str | '' | |
price | Price | null | Flat price per unit, in runes. Must be null for the tiered models. |
pricing_model | str | 'flat' | flat, tiered, volume, or stairstep. |
tiers | list | null | Price tiers for tiered, volume, and stairstep. |
overhead_cost | float | 0 | What one unit costs you, in runes. Must be 0 or more. |
stof_units | str | 'float' | Units meters are kept in (Ex. 'MB', 's'). 'float' is a plain number. |
resets | bool | false | Informational only. Limits decide resets. |
A credit can also define a cost function, fn overhead(units, context?) -> float, when the cost differs per call.
See Cost functions and margin.
Runes
Prices and costs are in runes, Limitr's base currency. One rune is one US dollar unless you change it. The
exchange converts any credit into any other through runes. It adds currencies
(euro: { value: 1.14, currency: 'usd' }), gives abstract credits like an "AI credit" their value (a credit with
no price, plus a pair), and can set a credit's price in another credit. See
Discrete and abstract credits.
Units
A credit with stof_units keeps its meters in those units, and limits and calls can use any unit of the same kind:
policy: {
credits: {
storage: { label: 'Storage', stof_units: 'MB' }
compute: { label: 'Compute', stof_units: 's' }
}
plans: {
free: {
default: true
entitlements: {
storage: { limit: { credit: 'storage', value: 1GB } } // 1000 MB
jobs: { limit: { credit: 'compute', value: 2hr } } // 7200 s
}
}
}
}
allow(id, 'storage', '250MB') and allow(id, 'storage', '0.25GB') both count 250. Sizes use base 10 (1GB is
1000 MB, GiB is base 2). A credit without stof_units takes plain numbers.
Price
// id is optional, Ex. your payment provider's price ID
price: { amount: 0.00002, id: 'price_123' }
What gets priced. Prices apply to billable units: usage past the limit. Units inside a plan's allowance are
already paid for by the plan, so they show their cost but no price. An entitlement with value: 0 makes every unit
billable (pure usage-based pricing).
Pricing models
| Model | How billable units are priced |
|---|---|
flat | One price per unit (price). |
tiered | Each tier's units at that tier's price, like tax brackets. |
volume | The tier the total falls in sets one price for every unit. |
stairstep | One flat fee for the tier the total falls in. |
Tiers are listed lowest first, each with up_to (the end of the tier, in billable units) and a price. The last tier
has no up_to. Validation sorts them and checks there's exactly one open-ended tier at the end.
image: {
label: 'Image'
pricing_model: 'tiered'
tiers: [
{ up_to: 100, price: { amount: 0.04 } }, // the first 100 billable images
{ up_to: 1000, price: { amount: 0.03 } }, // 101 to 1,000
{ price: { amount: 0.02 } } // everything past 1,000
]
}
The engine prices each call from where the customer already is this period, so the same call costs less once a customer is deep into the tiers. Here are the three models side by side, with 150 billable images bought in two calls:
import { Limitr } from '@formata/limitr';
// The first 100 units at 4 cents, the rest at 2 cents.
const tiers = `[{ up_to: 100, price: { amount: 0.04 } }, { price: { amount: 0.02 } }]`;
// $5 for up to 100 units, $8 past that.
const steps = `[{ up_to: 100, price: { amount: 5 } }, { price: { amount: 8 } }]`;
const limitr = await Limitr.new(`
policy: {
credits: {
tiered: { pricing_model: 'tiered', tiers: ${tiers} }
volume: { pricing_model: 'volume', tiers: ${tiers} }
stairstep: { pricing_model: 'stairstep', tiers: ${steps} }
}
plans: {
usage: {
default: true
entitlements: {
tiered: { limit: { credit: 'tiered', mode: 'soft', value: 0 } }
volume: { limit: { credit: 'volume', mode: 'soft', value: 0 } }
stairstep: { limit: { credit: 'stairstep', mode: 'soft', value: 0 } }
}
}
}
}`);
await limitr.ensureCustomer('ada');
limitr.addHandler('bill', (name, value) => {
if (name !== 'meter-overage') return;
const event = JSON.parse(value as string);
console.log(`${event.entitlement}: ${event.meter.diff} images for $${event.overage_price}`);
});
for (const model of ['tiered', 'volume', 'stairstep']) {
await limitr.allow('ada', model, 80);
await limitr.allow('ada', model, 70);
}
It prints:
tiered: 80 images for $3.2
tiered: 70 images for $1.8
volume: 80 images for $3.2
volume: 70 images for $-0.2
stairstep: 80 images for $5
stairstep: 70 images for $3
- Tiered: the second call finishes the first tier (20 at $0.04) and starts the second (50 at $0.02): $1.80.
- Volume: at 150 the total is in the $0.02 tier, so all 150 are worth $3.00. The first call was already worth
$3.20, so the second is worth -$0.20. Volume pricing can lower the price of earlier units, so bill it from the
period's total (the sum of
overage_price, or the meter at the end of the period), not call by call. - Stairstep: the first call lands in the $5 step; the second moves the total into the $8 step, so it's worth the $3 difference.
Reading prices and costs
| Call | Returns |
|---|---|
credit(id) | The credit record. |
creditFor(customer, entitlement) | The credit an entitlement is metered in, for that customer. |
creditExchange(in, out = 'rune', value = 1) | Converts between credits and currencies, Ex. the rune price of 1,000 tokens. |
customerMarginSnapshot(customer) | Revenue, cost, and margin this period. See Cost functions and margin. |