Token Billing

Token billing lets you grant customers a balance of consumable tokens after purchase. Unlike metered billing, which charges after consumption, token billing pre-allocates a fixed number of tokens that customers can consume as they use your product. When the balance runs out, the customer can either wait for their allowance to reset or purchase additional tokens.

AI products often use the token-based pricing model for subscription tiers that include a fixed allowance each cycle, and usage-based pricing for pay-as-you-go subscriptions. Tokens suit customers that want a cap they spend down per subscription cycle. Features that call a model, like image generation or coding assistants, are typical examples, where each request carries a cost that consumes the balance. API products use the same model to cost operations on a single scale, so a basic lookup might spend one token where a more advanced feature spends ten.

How Token Billing Works

You define a Token with a unique slug like api_tokens or image_generations. Tokens are organisation-scoped and reusable across Plans, and across Line Items on the same Plan. When you add a Token Line Item to a Plan, you specify how many tokens to grant after purchase and its Reset Behaviour.

When a customer purchases a Plan with a Token Line Item, Salable creates a Token Record with the allocated balance (Token Quantity * quantity purchased). Your application then consumes tokens as the customer uses your product. At any point, you can also check the remaining balance.

Subscription Created
  |-- Token Record created (balance: 1,000)
  |-- Token Record Event: accrued +1,000
  |
  |-- Customer uses product --> POST /api/tokens/consume (amount: 50)
  |      |-- Balance: 950
  |      |-- Token Record Event: consumed -50
  |
  |-- Customer uses product --> POST /api/tokens/consume (amount: 200)
  |      |-- Balance: 750
  |      |-- Token Record Event: consumed -200
  |
Billing Period Ends (Reset Behaviour)
  |-- Current Token Record marked expired (balance: 750)
  |-- Token Record Event:  expired -750
  |-- New Token Record created (balance: 1,000)
  |-- Token Record Event: accrued +1,000

Setting Up Token Billing

Adding Token Line Items to Plans

In the Product editor, navigate to your Plan and add a Line Item. Set the Pricing Type to Token.

Configure the Token Line Item with these properties:

FieldRequiredDescription
TokenYesIdentifies which Token to grant, chosen from existing ones or create a new one. Use lowercase snake_case for the slug, like api_tokens or image_generations. You can reuse the same Token across Plans, and across Line Items on the same Plan, to grant the same type of token at different quantities.
Token QuantityYesThe number of tokens granted per unit per billing cycle. The field is pre-filled with 1. If the quantity purchased is 5 and the Token Quantity is 1000, the customer gets 5,000 tokens.
Reset BehaviourRecurring onlyControls what happens at the start of each billing cycle, defaulting to Reset. Choose Reset to clear remaining tokens and issue a fresh allocation, or Accumulate to add new tokens on top of any remaining balance. One-off Token Line Items automatically use the accumulate behaviour as they do not renew, so the field is hidden for them.
Token Expiry DaysNoHow many days after accrual tokens expire. Only appears when Reset Behaviour is Accumulate or the Line Item is one-off, since under Reset the balance already expires with the billing cycle. If not set, accumulated tokens never expire. Setting it to 90 means tokens expire 90 days after they're purchased, regardless of the billing cycle.

A single Plan can contain multiple Token Line Items. For example, a Pro Plan might include 10,000 API tokens and 500 image generations as separate Token Line Items.

Important A Plan whose Line Items are all one-off produces a Receipt rather than a Subscription, and Entitlements are only ever granted through a Subscription. If a one-off Plan should unlock features as well as grant tokens, tick "Create a perpetual subscription" on the Plan. That creates a Subscription which never renews alongside the Receipt, so the Plan's Entitlements resolve in an Entitlement check. Without the perpetual Subscription, the customer receives the tokens but no access.

Configuring Token Pricing

Tokens aren't paid for individually; the customer is charged for the defined amount multiplied by the quantity purchased. For example, a Token Line Item granting 1,000 API tokens might be priced at $29/month. The customer pays $29 and receives 1,000 tokens to consume throughout the month.

Configure Prices with your desired Intervals and Currencies, just as you would for flat rate or per-seat Line Items.

Consuming Tokens

API: Consume Tokens

When a customer performs an action that should deduct from their token balance, call the consume endpoint.

Endpoint: POST /api/tokens/consume

Parameters:

ParameterRequiredDescription
tokenYesThe Token slug to consume from.
ownerYesThe user's tenant, such as a team ID or organisation ID. For single-user applications, use the user ID.
amountYesNumber of tokens to deduct. Must be an integer of at least 1.

Headers:

HeaderRequiredDescription
Idempotency-KeyNoUnique key to prevent duplicate consumption. Subsequent requests within 24 hours of a successful request with the same key and request parameters will replay the previous response and return an Idempotent-Replayed: true header. Reusing a key with different parameters, or on a different endpoint, returns 409 instead, as does a second request arriving while the first is still in flight.

Request Body:

{
    "token": "api_tokens",
    "owner": "company_acme",
    "amount": 1
}

Response:

Returns 204 No Content on success and triggers a tokens.consumed webhook.

Token consumption is atomic. When an Owner holds more than one active Token Record for the same Token, Salable consumes the record with the soonest expiry first, so a balance that is about to be lost is spent before one that will still be there next cycle.

A Token Record with an expiresAt of null never expires, which happens whenever tokens accumulate without Token Expiry Days set, including one-off purchases left with no expiry. These records are consumed last, after every dated record has been drawn down. If a single consume request spans multiple records, each is deducted independently and its own event is created.

After a Token Record's balance reaches zero, its status will be changed to consumed and no longer used in future balance checks.

Implementation Example

// Deduct one token per generation, before doing the work
app.post('/api/generate-image', async (req, res) => {
    const consumeResponse = await fetch('https://salable.app/api/tokens/consume', {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${process.env.SALABLE_API_KEY}`,
            'Content-Type': 'application/json',
            'Idempotency-Key': req.headers['x-request-id']
        },
        body: JSON.stringify({
            token: 'api_tokens',
            owner: 'company_acme',
            amount: 1
        })
    });
 
    if (consumeResponse.status === 422) {
        return res.status(422).json({ error: 'Out of tokens' });
    }
 
    if (consumeResponse.status !== 204) {
        return res.status(500).json({ error: 'Unable to consume tokens' });
    }
 
    const result = await generateImage(req.body.prompt);
    res.json(result);
});

Checking Token Balances

API: Get Token Balance

Query the current token balance for an Owner to display remaining tokens in your UI or to check before performing an action.

Endpoint: GET /api/tokens/balance

Query Parameters:

ParameterRequiredDescription
ownerYesThe Owner value to check.
tokenNoToken slug to filter by. If omitted, returns balances for every Token the Owner holds.
sourceNoA Receipt Item ID (ri_...) or Subscription Plan Line Item ID (spli_...), to return only the Token Records created by that one purchase.

Example Request:

GET /api/tokens/balance?owner=company_acme&token=api_tokens

Example Response:

{
    "type": "list",
    "data": [
        {
            "token": "api_tokens",
            "balance": 750,
            "records": [
                {
                    "recordId": "tkr_01KM4YB3QN5WE7RX2FHT9DVC64",
                    "remaining": 500,
                    "expiresAt": "2026-10-01T00:00:00Z",
                    "source": {
                        "type": "subscription_plan",
                        "id": "sp_01KM4Y9V5N3P8H2D6XQ7WCJZRA",
                        "itemId": "spli_01KM4YAG8T6R2F9N7VQ3XCPJWE"
                    }
                },
                {
                    "recordId": "tkr_01KM5C7XJP2QD8NVA3RTB6HW9F",
                    "remaining": 250,
                    "expiresAt": null,
                    "source": {
                        "type": "receipt",
                        "id": "rcpt_01KM5C5J4W8G2R7N9XQ6DPVTBA",
                        "itemId": "ri_01KM5C6P7H3T9V2X8NQ4WBRJFA"
                    }
                }
            ]
        }
    ]
}

The response includes the aggregate balance across all active Token Records, plus a per-record breakdown showing remaining tokens and expiry dates. An Owner with no active records returns 200 with an empty data array rather than an error.

An Owner value that doesn't exist returns 404. A source that isn't a valid Receipt Item or Subscription Plan Line Item returns 400, as does a missing owner or any parameter that fails validation.

Listing Tokens

API: List Tokens

Retrieve the Tokens configured in your organisation.

Endpoint: GET /api/tokens

Query Parameters:

ParameterRequiredDescription
searchNoFilter Tokens by slug.
beforeNoReturn records before this cursor for backwards pagination. Use the previousCursor from a previous response.
afterNoReturn records after this cursor for forward pagination. Use the nextCursor from a previous response.
limitNoThe number of records returned in a response. Value between 1 and 100. Defaults to 25.

Example Response:

{
    "type": "list",
    "data": [
        {
            "id": "tk_01KM4WZ8VN3JQ6TX2PDR5CB7HA",
            "slug": "api_tokens",
            "name": "api_tokens",
            "organisation": "org_01KM4V2HTX9BE5QN7GDS3JF6WC",
            "createdAt": "2026-08-01T00:00:00Z",
            "updatedAt": "2026-08-01T00:00:00Z"
        }
    ],
    "nextCursor": null,
    "previousCursor": null,
    "hasMore": false
}

An organisation with no Tokens returns 200 with an empty data array. Passing both before and after, or a cursor that cannot be decoded, returns 400, as does a limit outside 1 to 100 or any other parameter that fails validation.

Reset vs Accumulate

The Reset Behaviour you choose determines how token balances behave across billing cycles. The right choice depends on your business model.

Reset Behaviour

With reset, the token balance starts fresh at the beginning of each billing cycle. Any unused tokens from the previous cycle are lost. This creates a "use it or lose it" dynamic that encourages consistent usage and makes revenue predictable.

The Token Record's expiry is set to match the Subscription's next billing date. When the period ends, the old Token Record is marked as expired and a new one is created with a fresh allocation.

Reset is the right choice when you want customers to use their allocation each period, when your costs are time-bound (like monthly compute quotas), or when you want to avoid indefinite liability from unused tokens.

Accumulate Behaviour

With accumulate, new tokens are added on top of any remaining balance from previous cycles. For example, a customer who receives 1,000 tokens monthly but only uses 800 will have 1,200 tokens available in their second month.

Optionally, if you configure the Token Expiry Days property, each batch of tokens will have its own expiry independent of the billing cycle. For example, Tokens granted in January with a 90-day expiry will expire in April, even if the customer is still subscribed. If you don't set Token Expiry Days, accumulated tokens never expire.

Accumulate works well for scenarios where customers have variable usage patterns, where you want to reward loyalty by letting tokens build up, or where the nature of the work is bursty (heavy usage some months, light usage others).

Webhook Events

Salable sends webhook events for token events.

tokens.accrued is sent when tokens are granted. For example, when a Subscription is created or renewed or a Receipt is created.

tokens.consumed is sent when tokens are consumed.

tokens.expired is sent when a Token Record reaches its expiry date and the remaining balance is invalidated.

If you cache balances, treat tokens.accrued as the signal to invalidate, since it is the only one of the three that increases what a customer has available. The other two only ever reduce a balance, so a stale cache errs towards showing less than the customer has rather than more.

Next steps

Now that you understand how token billing works, these guides cover what sits around it:

  • Token-Based Billing Quick Start Build a working token Plan end to end, from creating the Product through to consuming a balance in your application.
  • Products & Pricing Configure Products, Plans, and Line Items, including how Token Line Items combine with other pricing types.
  • Subscriptions & Billing How Subscriptions provision tokens, and what renewals and cancellations do to a balance.
  • Metered Usage Charge after consumption rather than before, for pay-as-you-go pricing alongside your token tiers.
  • Webhooks Verify signatures and handle retries for the token events above.