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. Both Plans carry the same Entitlements, so a customer's access doesn't depend on which one they bought.

┌─ Product ──────────────────────────────────────────────────┐
│                                                            │
│  Plan: Starter ($29/month, recurring)                      │
│    Token Line Item  image_tokens, qty 1,000, reset         │
│    Entitlements     generate_images, export_hd             │
│                                                            │
│  Plan: 1,000 Token Top-Up ($29, one-off, perpetual)        │
│    Token Line Item  image_tokens, qty 1,000                │
│    Entitlements     generate_images, export_hd             │
│                                                            │
└────────────────────────────────────────────────────────────┘

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 any Entitlements that will be used to gate features in your application depending on their Subscription.

As an example, if your application was an AI image generation tool, you may offer a "Starter" Plan, with generate_images and export_hd as your Entitlements.

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.

FieldValueNotes
Tokenimage_tokensThe 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 Quantity1000The 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 BehaviourResetOnly 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.

One-off Token Line Items always use accumulate behaviour because there is no renewal to reset against.

If you want the Plan to also enable feature access, give the Plan the same generate_images and export_hd Entitlements as the Starter Plan, then tick "Create a perpetual subscription". That checkbox only appears once every Line Item on the Plan is one-off, Entitlements are only ever granted through a Subscription, so without it a customer who buys a top-up receives the tokens but no access to features.

Because both Plans grant the same image_tokens slug, a customer holding 200 Starter tokens who then purchases a top-up ends up with 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.

Purchases of this Plan produce a Receipt for the payment, and because it is perpetual, a Subscription that never renews alongside it. The Subscription is what carries the Entitlements; the Receipt is the record of the one-off charge.

Generate a Checkout link for the Starter Plan and redirect the customer to it.

ParameterRequiredDescription
planIdYesThe Plan being purchased.
ownerYesThe 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.
granteeNoThe 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.
intervalYesThe billing Interval. Set to null for one-off purchases such as the top-up Plan.
intervalCountYesThe 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.
currencyNoThree-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.
successUrlNoWhere the customer is redirected to after paying. Required if this is not set in the Plan's Product's settings.
cancelUrlNoWhere 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.url

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

ParameterRequiredDescription
tokenYesThe Token slug to deduct from, this was set on the Plan.
ownerYesThe Owner whose balance is being spent.
amountYesTokens 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. Perform an Entitlement check

Entitlements allow you to gate features in your application, so a Grantee can only access something if it's included on a Plan they have subscribed to.

import { Salable } from '@salable/sdk';
const salable = new Salable('secret-api-key');
 
const { data } = await salable.api.entitlements.check.get({
    queryParameters: {
        granteeId: 'user_alice'
    }
});
 
const hasAccess = data.entitlements.find(e => e.value === 'generate_images');
 
if (hasAccess) {
    // Show the generation UI
}

Your application can now hide the generation UI from any Grantee whose Plan doesn't grant generate_images.

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_tokens returns a balance of 1,000.
  • Calling consume with amount: 1 returns 204, and the balance drops to 999.
  • Replaying that consume call with the same Idempotency-Key leaves the balance at 999 rather than 998.
  • Consuming more tokens than remain returns 422 with "Insufficient token balance".
  • Buying the top-up Plan adds 1,000 tokens on top of the remainder rather than replacing it, and records shows the Starter record with an expiresAt date alongside the top-up record with null.
  • Buying only the top-up Plan still returns generate_images from the Entitlement check, confirming the perpetual Subscription is granting Entitlements.

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.