Token-Based Billing Quick Start
Selling a fixed bundle of tokens turns unpredictable usage into predictable revenue, which is why token-based pricing has become popular with AI products. A customer can only ever consume tokens they have paid for, and anything they leave unspent is yours to keep or roll over as you choose. Building that yourself means holding a balance per customer, decrementing correctly when requests are sent concurrently, expiring or carrying over the remainder at the end of each cycle, and granting more when someone tops up. Salable does all of it for you: a Token Line Item grants the balance on purchase, one consume call deducts from the balance atomically, and the Reset Behaviour you pick decides what tokens carry over into the next cycle.
What you'll build
By the end of this guide you'll have a Product with a Starter Plan at $29/month granting 1,000 image tokens per
cycle, a one-off 1,000 Token Top-Up Plan at $29 for customers who run out mid-cycle, and an application that deducts
tokens per generation and refuses the request when the balance reaches zero. Unused Starter tokens reset at the
start of each cycle, while topped-up tokens stack on top of the remaining balance. An owner-scoped Entitlement check
automatically returns the image_tokens Token slug for access checks.
┌─ Product ──────────────────────────────────────────────────┐
│ │
│ Plan: Starter ($29/month, recurring) │
│ Token Line Item image_tokens, qty 1,000, reset │
│ Entitlement export_hd │
│ │
│ Plan: 1,000 Token Top-Up ($29, one-off) │
│ Token Line Item image_tokens, qty 1,000 │
│ Perpetual Subscription optional for standalone access │
│ │
└────────────────────────────────────────────────────────────┘
1. Create and configure your Product
Before you can start implementing Salable into your codebase, you need to sign up for an account.
In the Salable dashboard, create a Product and a Plan called "Starter"; this is what users will subscribe to. Add Entitlements only for features that aren't already represented by a Meter or Token.
For an AI image generation tool, add export_hd as an Entitlement. The image_tokens Token slug added in the next step will control access to image generation, so you don't create a separate image_tokens Entitlement for it.
Tip To return Meter and Token slugs as entries in an entitlement check, pass the
ownerquery param in the request. When gating features with these entries, check both thetypeandvalue, a separate Entitlement is then not required to gate these features.
2. Add the Token Line Item
To grant tokens on your "Starter" Plan, create a Line Item with a Pricing Type of "Token" and set values from the table below. This Line Item is what tells the system to create tokens on purchase.
| Field | Value | Notes |
|---|---|---|
| Token | image_tokens | The Line Item needs to be linked to a Token in the system. Type image_tokens into the input, this will be your Token slug. Tokens are organisation-scoped and reusable across Plans. |
| Token Quantity | 1000 | The amount of tokens which will be granted to the customer, per unit per cycle. You can allow a customer to buy multiple units of tokens e.g. 2 units purchased receives 2,000 tokens. |
| Reset Behaviour | Reset | Only appears on recurring Line Items, where it defaults to Reset. Reset clears the remaining balance at the start of each cycle and issues a fresh 1,000. |
| Token Expiry Days | (not shown) | Only appears when Reset Behaviour is Accumulate, or on a one-off Line Item. Under Reset the balance already expires with the billing cycle. |
Set the Price to $29 with a monthly Interval. This Price is the per-unit cost, if the customer wanted 2,000 tokens, they would pay $58 for two units.
3. Create a one-off top-up Plan
Customers who exhaust their allowance mid-cycle need a way to buy more without upgrading, which is what a Plan with a one-off Line Item gives them.
Create a second Plan on your Product named "Token Top-Up". Add a one-off
Token Line Item using the same image_tokens slug with a Token Quantity of 1,000, priced at $29.
Note One-off Token Line Items always use accumulate behaviour because there is no renewal to reset against.
Because both Plans grant tokens for the same image_tokens slug, a customer holding 200 Starter tokens who then purchases a
top-up will have 1,200 available, spread across two Token Records with different expiry dates. Salable spends whichever record expires soonest first, and leaves records with no expiry until last.
Tip The customer's valid Starter Subscription continues to grant access to
image_tokensandexport_hdafter a top-up. The top-up Plan only needs a perpetual Subscription for standalone access or access after the Starter Subscription ends. In that case, check "Create a perpetual subscription" and add theexport_hdEntitlement if top-up buyers should receive it.
Purchases of this Plan produce a Receipt and a Token Record and optionally a Subscription if "Create a perpetual subscription" was checked.
Important Entitlement checks only grant Entitlements, Token slugs, and Meter slugs through a valid Subscription. The top-up tokens can be spent without their own Subscription while the Starter Subscription is valid, but they don't grant access by themselves.
4. Generate a Stripe Checkout link
Generate a Checkout link for the Starter Plan and redirect the customer to it.
| Parameter | Required | Description |
|---|---|---|
planId | Yes | The Plan being purchased. |
owner | Yes | The Owner is the user's tenant like a team ID or organisation ID. For single-user applications use the user ID. Token balances are tracked per Owner. |
grantee | No | The Grantee receiving access. Accepts a Grantee's ID within your system like a user ID or a Group ID in Salable. If omitted, Salable creates an empty Group for the Owner. |
interval | Yes | The billing Interval. Set to null for one-off purchases such as the top-up Plan. |
intervalCount | Yes | The number of Intervals for each cycle, so 1 with month cycles monthly. Set to null for one-off purchases such as the top-up Plan. |
currency | No | Three-letter ISO 4217 code. Falls back to geolocation when omitted. For geolocation to work all the Line Items on the Plan must have the same default currency. |
successUrl | No | Where the customer is redirected to after paying. Required if this is not set in the Plan's Product's settings. |
cancelUrl | No | Where the customer returns if they abandon checkout. Required if this is not set in the Plan's Product's settings. |
import { Salable } from '@salable/sdk';
const salable = new Salable('secret-api-key');
const { data } = await salable.api.checkout.post({
planId: 'plan_01KM4XQ8ZT7BN2VF9GDR3HC5EW',
owner: 'company_acme',
grantee: 'user_alice',
interval: 'month',
intervalCount: 1,
currency: 'USD',
successUrl: 'https://your-app.com/success',
cancelUrl: 'https://your-app.com/pricing'
});
// Redirect user to data.urlTo sell the top-up Plan, call the same endpoint with planId set to the top-up Plan's ID, and set interval and
intervalCount to null. Setting these values to null is how you create a one-off purchase in the checkout.
Once the customer pays for the Starter Plan, Salable creates the Subscription
and a Token Record holding 1,000 image_tokens for company_acme, and fires a tokens.accrued webhook.
Checkouts in Test Mode work without touching a real card. Pay with Stripe's test card
4242 4242 4242 4242, any future expiry date, and any three-digit CVC.
5. Consume tokens
Deduct from the balance whenever the customer consumes tokens, such as when they generate an image.
| Parameter | Required | Description |
|---|---|---|
token | Yes | The Token slug to deduct from, this was set on the Plan. |
owner | Yes | The Owner whose balance is being spent. |
amount | Yes | Tokens to deduct. Must be an integer of at least 1. |
Send an Idempotency-Key header, as the request shows below. If the same request is sent again within 24 hours,
Salable replays the original response and returns an Idempotent-Replayed: true header instead of deducting a second
time, which is what stops a client retry from charging the customer twice for the same action.
import { Salable } from '@salable/sdk';
const salable = new Salable('secret-api-key');
await salable.api.tokens.consume.post(
{
token: 'image_tokens',
owner: 'company_acme',
amount: 1
},
{
headers: {
'Idempotency-Key': '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
}
}
);A successful consume request returns 204 No Content and sends a tokens.consumed webhook event. When the balance is too low it returns 422 with the title
"Insufficient token balance", this is the signal to block the action and point the customer at the top-up Plan from
Step 3.
Consumption is atomic, so concurrent requests can't drive the balance below zero. When an Owner holds several Token
Records for the same Token, Salable spends the records expiring soonest first and leaves records with no expiry until
last, deducting across multiple records in a single call where needed. A record that reaches zero is marked consumed
and drops out of later balance checks.
6. Check access to the Token
Pass the Owner when checking access so Salable includes the relevant Token slugs. Check both type and value, rather than creating a separate Entitlement to represent image_tokens.
import { Salable } from '@salable/sdk';
const salable = new Salable('secret-api-key');
const { data } = await salable.api.entitlements.check.get({
queryParameters: {
granteeId: 'user_alice',
owner: 'company_acme'
}
});
const hasAccess = data.entitlements.some(entry => entry.type === 'token' && entry.value === 'image_tokens');
if (hasAccess) {
// Show the generation UI
}Your application can now hide the generation UI when the Owner's active Plan doesn't include image_tokens. A Meter and Token can share the same slug, so keep the type === 'token' check.
7. Get the token balance
To show customers how many tokens they have left, query the balance endpoint. You can filter by token
slug and source which will either be Receipt Item ID (ri_...) or Subscription Plan Line Item ID (spli_...) when you want the tokens from one purchase in isolation.
import { Salable } from '@salable/sdk';
const salable = new Salable('secret-api-key');
const { data } = await salable.api.tokens.balance.get({
queryParameters: {
owner: 'company_acme',
token: 'image_tokens'
}
});
const { balance, records } = data[0];
// balance === 750
// records === [{ recordId: 'tkr_01KM4YB3QN5WE7RX2FHT9DVC64', remaining: 750, expiresAt: '2026-09-01T00:00:00Z' }]The aggregate balance is what you put in the UI. The records breakdown is what you use to warn customers that 200 of their tokens expire next week, which the aggregate alone can't tell them.
8. React to token webhooks
Webhooks let you act on balance changes without polling.
Salable sends tokens.accrued when tokens are granted, whether from a new Subscription, a renewal, or a top-up Receipt. Use it to refresh a cached balance or email a customer that their new allowance has landed. tokens.consumed fires on every deduction, which is useful for usage analytics and for warning customers as they approach zero. tokens.expired fires when a Token Record hits its expiry and the remaining balance is invalidated, either at the end of a cycle under reset or when the expiry days is reached under accumulate.
Treat tokens.accrued as the signal to invalidate any balance you cache, since it is the only one of the three events
that increases what a customer has available.
9. Test it
Work through these to confirm the model end to end:
- After checkout completes,
GET /api/tokens/balance?owner=company_acme&token=image_tokensreturns a balance of 1,000. - Calling consume with
amount: 1returns204, and the balance drops to 999. - Replaying that consume call with the same
Idempotency-Keyleaves the balance at 999 rather than 998. - Consuming more tokens than remain returns
422with "Insufficient token balance". - Buying the top-up Plan adds 1,000 tokens on top of the remainder rather than replacing it, and
recordsshows the Starter record with anexpiresAtdate alongside the top-up record withnull. - An owner-scoped Entitlement check returns
image_tokenswithtype: 'token'after the Starter checkout.
10. Next steps
- Token Billing Reset versus accumulate in depth, expiry rules, and how Token Records are drawn down.
- Understanding Entitlements Entitlement patterns, signature verification, and caching.
- Cart & Checkout Sell a Plan and a top-up together in a single transaction using a Cart.
- Webhooks Verify signatures and handle retries for the token events above.
- Subscriptions & Billing How renewals, cancellations, and proration affect the balances you grant.