Skip to content

Credit‐Based Billing: Implementation Guide

aanchal-alt edited this page Apr 6, 2026 · 1 revision

Not every billing model fits neatly into "charge per API call." Sometimes your customers need to prepay for a pool of credits, burn them down over time, and top up when they run low. This is credit-based billing — and it's the dominant model for AI products, token-based APIs, and any service where customers want spending predictability.

This guide covers when to use credits vs. pure usage billing, how to implement credit grants and wallets with Flexprice, and real-world patterns you can steal for your own billing system.

Credits vs. Usage Billing: When to Use Each

Both models charge based on consumption. The difference is when money changes hands and how customers think about spending.

Pure usage billing works best when consumption is relatively predictable, the unit economics are straightforward (e.g., $0.01 per request), and customers are comfortable with variable monthly invoices. It's the simpler model to implement — events come in, metrics get aggregated, invoices go out at end of cycle.

Credit-based billing is the better fit when:

  • Customers want spending caps. Prepaid credits create a natural budget boundary. The customer buys 100,000 credits, and when they're gone, they either top up or get cut off. No surprise invoices.
  • Your pricing unit is abstract. AI tokens, compute units, and "platform credits" don't map cleanly to a dollar amount per raw event. Credits let you define an internal currency that absorbs pricing complexity.
  • You offer promotional or trial access. Grant 10,000 free credits to new signups. When they burn through them, the conversion to paid is a natural upsell — not a hard paywall.
  • Enterprise contracts involve committed spend. The customer commits to $50K/year in credits. They draw down over time, and unused credits either roll over or expire per the contract terms.

In practice, many products use hybrid models — a base subscription plus a credit wallet for variable usage. Flexprice supports both natively.

Core Concepts: Wallets, Grants, and Deductions

Before writing code, here's the data model:

Wallet: A balance container associated with a customer. A customer can have multiple wallets (e.g., one for promotional credits, one for paid credits). Each wallet has its own balance, expiration rules, and priority in the deduction order.

Credit Grant: An operation that adds credits to a wallet. Grants can be:

  • Prepaid — customer purchased them (tied to a payment)
  • Promotional — issued for free (trials, support credits, goodwill)
  • Committed — part of an enterprise contract with minimum spend

Deduction: When usage events are processed, the billing engine deducts credits from the customer's wallet(s) according to a configurable priority order. Promotional credits are typically burned first, then paid credits.

Overage: What happens when the wallet hits zero? Options: block the customer's access, allow continued usage and invoice the overage at a defined rate, or trigger an auto top-up.

Setting Up Credit Grants in Flexprice

Creating a Wallet

First, create a wallet for your customer. This is a one-time setup per customer (or per credit type if you want separate promotional and paid wallets).

curl -X POST https://api.cloud.flexprice.io/v1/wallets \
  -H "Content-Type: application/json" \
  -H "x-api-key: $FLEXPRICE_API_KEY" \
  -d '{
    "customer_id": "cust_8xk29f",
    "currency": "credits",
    "name": "Primary Credit Wallet"
  }'

Issuing a Credit Grant

Add credits to the wallet. You can set expiration dates, grant types, and metadata:

curl:

curl -X POST https://api.cloud.flexprice.io/v1/credit-grants \
  -H "Content-Type: application/json" \
  -H "x-api-key: $FLEXPRICE_API_KEY" \
  -d '{
    "wallet_id": "wal_3kd92x",
    "amount": 50000,
    "grant_type": "prepaid",
    "expires_at": "2026-07-04T00:00:00Z",
    "metadata": {
      "source": "enterprise_contract",
      "contract_id": "contract_2024_q2"
    }
  }'

Python SDK:

client.credit_grants.create(
    wallet_id="wal_3kd92x",
    amount=50000,
    grant_type="prepaid",
    expires_at="2026-07-04T00:00:00Z",
    metadata={"source": "enterprise_contract"},
)

Go SDK:

client.CreditGrants.Create(ctx, &fp.CreditGrantInput{
    WalletID:  "wal_3kd92x",
    Amount:    50000,
    GrantType: "prepaid",
    ExpiresAt: time.Date(2026, 7, 4, 0, 0, 0, 0, time.UTC),
    Metadata:  map[string]string{"source": "enterprise_contract"},
})

Wallet Management: Top-Ups, Expirations, and Rollovers

Real-world credit systems need more than just "add credits, subtract credits." Here's what Flexprice handles out of the box:

Auto Top-Up

Configure a wallet to automatically purchase more credits when the balance drops below a threshold. This prevents service interruption for customers who want uninterrupted access:

{
  "auto_top_up": {
    "enabled": true,
    "threshold": 5000,
    "amount": 25000,
    "payment_method_id": "pm_stripe_abc123"
  }
}

When the balance hits 5,000 credits, Flexprice charges the customer's payment method for 25,000 more credits and adds them to the wallet. No developer intervention required.

Expiration Policies

Each credit grant can have its own expiration date. When credits expire, they're removed from the available balance. Flexprice stacks multiple grants with different expiration dates and deducts from the earliest-expiring credits first (FEFO — First Expiring, First Out).

Rollovers

For subscription-based credit models (e.g., "10,000 credits/month included"), you can configure whether unused credits roll over to the next period or expire at cycle end. Rollover caps prevent unbounded accumulation.

Low Balance Alerts

Trigger webhook notifications when a customer's wallet balance drops below a configurable threshold. Pipe these into your notification system to prompt customers to top up before they hit zero:

{
  "alerts": [
    {"threshold_percent": 20, "webhook_url": "https://your-app.com/hooks/low-credits"},
    {"threshold_percent": 5, "webhook_url": "https://your-app.com/hooks/critical-credits"}
  ]
}

Code: Creating a Credit Grant via API

Here's a complete integration example — a Node.js service that provisions credits when a customer completes a Stripe checkout:

import Flexprice from "@flexprice/sdk";
import Stripe from "stripe";

const fp = new Flexprice({ apiKey: process.env.FLEXPRICE_API_KEY });
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

// Webhook handler for Stripe checkout.session.completed
export async function handleCheckoutComplete(session) {
  const customerId = session.metadata.flexprice_customer_id;
  const creditAmount = parseInt(session.metadata.credit_amount);
  const walletId = session.metadata.wallet_id;

  // Grant credits in Flexprice
  const grant = await fp.creditGrants.create({
    wallet_id: walletId,
    amount: creditAmount,
    grant_type: "prepaid",
    expires_at: getExpirationDate(90), // 90-day expiry
    metadata: {
      stripe_session_id: session.id,
      stripe_payment_intent: session.payment_intent,
    },
  });

  console.log(`Granted ${creditAmount} credits to ${customerId}`, grant);

  // Check wallet balance for confirmation
  const wallet = await fp.wallets.get(walletId);
  console.log(`Current balance: ${wallet.balance} credits`);
}

function getExpirationDate(days) {
  const date = new Date();
  date.setDate(date.getDate() + days);
  return date.toISOString();
}

The key pattern: your payment system handles money, Flexprice handles credit accounting. The Stripe webhook fires, you call the Flexprice API to grant credits, and the billing engine takes it from there — tracking deductions as usage events flow in.

Real-World Patterns

AI Tokens (OpenAI-Style)

Map model inference to credit deductions. Different models cost different credit amounts:

{
  "event_name": "model.inference",
  "external_customer_id": "cust_8xk29f",
  "properties": {
    "model": "gpt-4",
    "input_tokens": 2000,
    "output_tokens": 500,
    "credits": 25
  }
}

Use a Flexprice metered feature with SUM aggregation on the credits property. The wallet deduction happens automatically based on the aggregated usage.

API Calls with Rate Tiers

Charge 1 credit per standard API call, 5 credits per premium endpoint:

def track_api_call(customer_id: str, endpoint: str):
    credits = 5 if endpoint.startswith("/v1/premium/") else 1
    client.events.ingest(
        event_name="api.call",
        external_customer_id=customer_id,
        properties={"credits": credits, "endpoint": endpoint},
    )

Compute Minutes

For long-running jobs, emit periodic heartbeat events:

// Emit every 60 seconds while job is running
ticker := time.NewTicker(60 * time.Second)
for range ticker.C {
    if job.IsComplete() {
        break
    }
    client.Events.Ingest(ctx, &fp.EventInput{
        EventName:          "compute.minute",
        ExternalCustomerID: customerID,
        Properties: map[string]interface{}{
            "credits":  1,
            "job_id":   job.ID,
            "instance": job.InstanceType,
        },
    })
}

Each heartbeat deducts 1 credit from the customer's wallet. When the wallet hits zero, your application checks the entitlement and either queues the job for later or triggers an auto top-up.

How Flexprice Processes Credit Deductions

Under the hood, credit deductions follow the same event pipeline as usage billing. Temporal workflows ensure that credit deductions are durable — if a worker crashes mid-deduction, Temporal replays the workflow to completion. No credits are lost or double-deducted.

The wallet transaction log provides a full audit trail: every grant, deduction, expiration, and top-up is recorded with timestamps and metadata. This is critical for enterprise customers who need to reconcile credit usage against their contracts.

Clone this wiki locally