Token billing without building the infrastructure

Sell prepaid tokens, granted at purchase. Salable burns the balance down as your customers use it, lets them buy a top-up when they run out, and resets or rolls it over each cycle. Salable handles the whole flow, so your application never has to react to a Stripe webhook. All you do is create the checkout, check entitlements and call consume.

You define the token

A token in Salable is a unit you define: an image generation, a report, an API call, or any other action in your application. Create the token once, add it to any plan that includes the feature, and set the quantity and price. Below, the same image_generations token is priced five different ways.

Monthly reset

$29 /month

1,000 generations

≈ $0.03 a generation

Resets at the start of each cycle.

Accumulating

$99 /month

5,000 generations

≈ $0.02 a generation

Accumulates unused tokens, each batch expiring after 90 days.

One-off top-up

$20 one-off

1,000 generations

$0.02 a generation

Joins the balance they already have and never expires.

Free allowance

$0 /month

50 generations

nothing, until they buy

Enough to build against before anyone pays.

Bought once

$499 one-off

100,000 generations

≈ $0.005 a generation

Bought once, drawn down for as long as it lasts.

Example figures. You set the price and the quantity.

What you don't build

From the first purchase onwards, Salable manages token accruals, balance records, consumption, reset or accumulation, subscription renewals and top-ups.

Gate features in your application

Salable uses grantees to represent who receives access: a user, a workspace, a project, or any other identifier in your application. When a customer buys a plan, the grantees named at checkout get access to its entitlements. Perform entitlement checks against the grantee in your code to gate features.

Grant tokens at purchase

The token quantity you configure on the plan’s line item becomes the balance granted to the plan owner. Nothing to reconcile on your side: consume tokens as they’re used.

Keep each tenant’s balance separate

A user can belong to several tenants, each with its own balance. The owner in Salable is the tenant the purchase belongs to. Scope each consume request to the owner, and the right balance is spent. Tokens never cross tenants.

Read the balance in one request

A single request returns the owner’s total balance for a token, aggregated across every purchase. Each purchase is recorded separately, with its source and expiry date.

Top-ups join one balance

Sell a one-off plan for the same token and it adds to the existing balance: two separate 500-token purchases leave the owner with a single balance of 1,000. Salable always spends the soonest-expiring records first, preserving longer-lived tokens for later.

Never record the same consumption twice

Consume tokens as owners use them. Send an Idempotency-Key with each call: retry within 24 hours, and Salable replays the original response instead of recording the usage twice.

Reset or roll over the balance

Choose whether unused tokens roll over to the next cycle or reset, with optional expiry days per batch.

Sell it, check entitlements, consume

Create the checkout, check entitlements and consume. Read the balance and sell a top-up when an owner runs low.

  1. 1. Checkout
    const response = await fetch('https://salable.app/api/checkout', {
        method: 'POST',
        headers: {
            Authorization: 'Bearer YOUR_SECRET_KEY',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            planId: 'plan_01KHRQ9T3W4WFDARCVX3QG70EB',
            owner: 'org_acme',
            grantee: 'user_jo',
            interval: 'month',
            intervalCount: 1,
            currency: 'USD',
            successUrl: 'https://yourapp.com/welcome',
            cancelUrl: 'https://yourapp.com/pricing'
        })
    });
    
    const { data } = await response.json();
  2. 2. Check entitlements
    const response = await fetch('https://salable.app/api/entitlements/check?granteeId=user_jo&owner=org_acme', {
        headers: {
            Authorization: 'Bearer YOUR_SECRET_KEY'
        }
    });
    
    const { data } = await response.json();
    const hasAccess = data.entitlements.some(e => e.type === 'token' && e.value === 'image_generations');
  3. 3. Consume
    const response = await fetch('https://salable.app/api/tokens/consume', {
        method: 'POST',
        headers: {
            Authorization: 'Bearer YOUR_SECRET_KEY',
            'Content-Type': 'application/json',
            'Idempotency-Key': '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
        },
        body: JSON.stringify({
            token: 'image_generations',
            owner: 'org_acme',
            amount: 10
        })
    });
  4. Balance
    const response = await fetch('https://salable.app/api/tokens/balance?owner=org_acme&token=image_generations', {
        headers: {
            Authorization: 'Bearer YOUR_SECRET_KEY'
        }
    });
    
    const { data } = await response.json();
    const { balance, records } = data[0];
  5. Top-up
    const response = await fetch('https://salable.app/api/checkout', {
        method: 'POST',
        headers: {
            Authorization: 'Bearer YOUR_SECRET_KEY',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            planId: 'plan_01KM5C5J4W8G2R7N9XQ6DPVTBA',
            owner: 'org_acme',
            grantee: 'user_jo',
            interval: null,
            intervalCount: null,
            currency: 'USD',
            successUrl: 'https://yourapp.com/tokens/added',
            cancelUrl: 'https://yourapp.com/tokens'
        })
    });
    
    const { data } = await response.json();

Define the token. Sell the plan. Track the usage.