> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blnkfinance.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Building an AI Billing System

> Learn how to implement usage-based billing for AI products with token tracking, cost calculation, and prepaid/postpaid payment models.

export const CtaCallout = props => {
  const {title, buttonLabel, href, trackingEvent, buttonTarget, rel = "noopener noreferrer", children} = props;
  const handleCtaClick = () => {
    if (typeof window === "undefined" || !trackingEvent) {
      return;
    }
    try {
      window.dispatchEvent(new CustomEvent("blnk:docs-cta", {
        detail: {
          name: trackingEvent,
          href
        }
      }));
    } catch {}
    try {
      window.posthog?.capture?.(trackingEvent, {
        href
      });
    } catch {}
    const gaPayload = {
      cta_href: href
    };
    try {
      window.gtag?.("event", trackingEvent, gaPayload);
    } catch {}
    try {
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: trackingEvent,
        ...gaPayload
      });
    } catch {}
  };
  const isExternal = typeof href === "string" && (/^https?:\/\//i).test(href);
  const target = buttonTarget ?? (isExternal ? "_blank" : undefined);
  const linkRel = isExternal ? rel : undefined;
  return <section className="cta-callout not-prose relative my-8 w-full min-w-0 overflow-hidden rounded-xl border border-zinc-200 p-5 dark:border-white/10">
      <div className="cta-callout-noise" aria-hidden="true" />
      <div className="cta-callout-layout">
        {title ? <div className="cta-callout-title-row">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" width="14" height="14" className="cta-callout-icon shrink-0 text-zinc-800 dark:text-zinc-200" aria-hidden="true">
              <g fill="none" fillRule="nonzero">
                <path d="M28 0v28H0V0h28ZM14.691833333333335 27.134333333333334l-0.012833333333333334 0.0023333333333333335 -0.08283333333333333 0.04083333333333334 -0.023333333333333334 0.004666666666666667 -0.016333333333333335 -0.004666666666666667 -0.08283333333333333 -0.04083333333333334c-0.011666666666666667 -0.004666666666666667 -0.022166666666666668 -0.0011666666666666668 -0.028000000000000004 0.005833333333333334l-0.004666666666666667 0.011666666666666667 -0.019833333333333335 0.49933333333333335 0.005833333333333334 0.023333333333333334 0.011666666666666667 0.015166666666666667 0.12133333333333333 0.08633333333333333 0.0175 0.004666666666666667 0.014000000000000002 -0.004666666666666667 0.12133333333333333 -0.08633333333333333 0.014000000000000002 -0.018666666666666668 0.004666666666666667 -0.019833333333333335 -0.019833333333333335 -0.4981666666666667c-0.0023333333333333335 -0.011666666666666667 -0.0105 -0.019833333333333335 -0.019833333333333335 -0.021Zm0.3091666666666667 -0.13183333333333336 -0.015166666666666667 0.0023333333333333335 -0.21583333333333335 0.1085 -0.011666666666666667 0.011666666666666667 -0.0035000000000000005 0.012833333333333334 0.021 0.5016666666666667 0.005833333333333334 0.014000000000000002 0.009333333333333334 0.008166666666666668 0.23450000000000004 0.1085c0.014000000000000002 0.004666666666666667 0.026833333333333334 0 0.03383333333333334 -0.009333333333333334l0.004666666666666667 -0.016333333333333335 -0.03966666666666667 -0.7163333333333334c-0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.023333333333333334 -0.023333333333333334 -0.025666666666666667Zm-0.8341666666666667 0.0023333333333333335a0.026833333333333334 0.026833333333334334 0 0 0 -0.0315 0.007000000000000001l-0.007000000000000001 0.016333333333333335 -0.03966666666666667 0.7163333333333334c0 0.014000000000000002 0.008166666666666668 0.023333333333333334 0.019833333333333335 0.028000000000000004l0.0175 -0.0023333333333333335 0.23450000000000004 -0.1085 0.011666666666666667 -0.009333333333333334 0.004666666666666667 -0.012833333333333334 0.019833333333333335 -0.5016666666666667 -0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.011666666666666667 -0.21466666666666667 -0.10733333333333334Z" strokeWidth="1.1667" />
                <path fill="currentColor" d="M14 2.916666666666667A1.75 1.75 0 0 1 15.750000000000002 4.666666666666667v6.302333333333334L21.207666666666668 7.816666666666667a1.75 1.75 0 0 1 1.75 3.031L17.5 14l5.457666666666667 3.151166666666667a1.75 1.75 0 0 1 -1.75 3.031l-5.457666666666667 -3.1500000000000004V23.333333333333336a1.75 1.75 0 0 1 -3.5 0v-6.302333333333334L6.792333333333334 20.183333333333337a1.75 1.75 0 1 1 -1.75 -3.031L10.5 14 5.042333333333334 10.848833333333333a1.75 1.75 0 0 1 1.75 -3.031l5.457666666666667 3.1500000000000004V4.666666666666667A1.75 1.75 0 0 1 14 2.916666666666667Z" strokeWidth="1.1667" />
              </g>
            </svg>
            <p className="cta-callout-title min-w-0 font-semibold text-zinc-800 dark:text-zinc-200">
              {title}
            </p>
          </div> : null}
        <div className={`cta-callout-body text-sm leading-normal text-zinc-800 dark:text-zinc-200${title ? " cta-callout-body--indented" : ""}`}>
          {children}
        </div>
        <a href={href} target={target} rel={linkRel} onClick={handleCtaClick} data-docs-cta={trackingEvent || undefined} className="cta-callout-button inline-flex items-center justify-center gap-1 rounded-full bg-white px-3 py-1.5 text-sm font-semibold transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 dark:bg-white dark:hover:bg-zinc-200">
          {buttonLabel}
          <span className="cta-callout-button-arrow" aria-hidden="true">
            →
          </span>
        </a>
      </div>
    </section>;
};

This tutorial provides a step-by-step guide to building an AI billing system using Blnk Finance. You'll learn how to track token usage, calculate costs, and manage both prepaid and postpaid billing models.

***

## How AI billing works

Each usage event has two components that must be tracked together:

* **Tokens**: The amount consumed, typically measured and reported by your AI provider
* **Dollars**: The monetary value of that consumption based on your pricing model

When a customer uses your AI product, the following happens:

1. **Usage tracking**: The AI provider reports token consumption (input and output tokens)
2. **Cost calculation**: The token count is converted to USD based on your pricing model
3. **Atomic recording**: Both tokens and dollars are recorded together in a single transaction
4. **Balance management**: For prepaid, funds are deducted from the customer wallet. For postpaid, balances move into overdraft
5. **Invoice generation**: At the end of the billing period, generate invoices from accumulated usage

***

## Prerequisites

Before starting, ensure you have:

1. A running Blnk Core instance (e.g. at `http://localhost:5001`).
2. [An API key](/advanced/secure-blnk) for Blnk (replace `YOUR_API_KEY` in the code examples). Required for authenticated requests.
3. Optionally, you can connect your Blnk Core to your [Blnk Cloud](https://cloud.blnkfinance.com/?utm_source=blnk_docs\&utm_medium=documentation\&utm_campaign=tutorials%2Fmore%2Fai-billing) workspace to view your ledger data.

***

## 1: Create customer wallet

Each customer needs a wallet to track their usage and balance. Each wallet will be represented with [a ledger](/ledgers/introduction.mdx) and a balance.

<Steps titleSize="h3">
  <Step title="Create a ledger">
    [Create a ledger](/reference/create-ledger) for customer wallets. This groups all customer balances in one place:

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X POST "http://localhost:5001/ledgers" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "Customers Ledger"
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.Ledgers.create({
        name: 'Customers Ledger',
      });
      ```

      ```go Go wrap theme={"system"}
      ledger, resp, err := client.Ledger.Create(blnkgo.CreateLedgerRequest{
        Name: "Customers Ledger",
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk ledgers create
      ```
    </CodeGroup>

    <Info>
      Save the `ledger_id` from the response. You'll need it to create balances for customer wallets.
    </Info>

    <img src="https://mintcdn.com/blnk/TZC-WDEljP4tkd4b/images/tutorials/ai-billing/create-ledger.png?fit=max&auto=format&n=TZC-WDEljP4tkd4b&q=85&s=dfd231266b71b5876cfb4772f8594125" className="rounded-xl" alt="Create a ledger" width="3388" height="1964" data-path="images/tutorials/ai-billing/create-ledger.png" />
  </Step>

  <Step title="Create customer identity">
    [Create a customer identity](/reference/create-identity) to link all balances to the same customer:

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X POST "http://localhost:5001/identities" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "first_name": "Xavier",
          "last_name": "Woods"
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.Identity.create({
        first_name: 'Xavier',
        last_name: 'Woods',
      });
      ```

      ```go Go wrap theme={"system"}
      identity, resp, err := client.Identity.Create(blnkgo.Identity{
        FirstName: "Xavier",
        LastName:  "Woods",
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk identities create
      ```
    </CodeGroup>

    <Info>
      Save the `identity_id` from the response. You'll use this ID to link the customer wallet to this identity.
    </Info>
  </Step>

  <Step title="Create customer wallet balance">
    [Create a balance](/reference/create-balance) for the customer wallet:

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X POST "http://localhost:5001/balances" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "ledger_id": "<ledger-id>",
          "identity_id": "<identity-id>",
          "currency": "USD",
          "meta_data": { "name": "Xavier Woods" }
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.LedgerBalances.create({
        ledger_id: '<ledger-id>',
        identity_id: '<identity-id>',
        currency: 'USD',
        meta_data: { name: 'Xavier Woods' },
      });
      ```

      ```go Go wrap theme={"system"}
      balance, resp, err := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
        LedgerID:   "<ledger-id>",
        IdentityID: "<identity-id>",
        Currency:   "USD",
        MetaData: map[string]interface{}{
          "name": "Xavier Woods",
        },
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk balances create
      ```
    </CodeGroup>

    <Info>
      Save the `balance_id` from the response. You'll need it for wallet funding and usage transactions.
    </Info>

    <img src="https://mintcdn.com/blnk/TZC-WDEljP4tkd4b/images/tutorials/ai-billing/create-balance.png?fit=max&auto=format&n=TZC-WDEljP4tkd4b&q=85&s=5ddeb3be47c32068704edc0b515e61d1" className="rounded-xl" alt="Create customer wallet balance" width="1695" height="979" data-path="images/tutorials/ai-billing/create-balance.png" />
  </Step>
</Steps>

***

## 2: Wallet top-up

Customers can fund their wallets before using your AI product. This is required for prepaid billing models.

<Steps titleSize="h3">
  <Step title="Money movement map">
    When a customer funds their wallet, money moves from the external world to the customer's wallet:

    <img src="https://mintcdn.com/blnk/jKiGmb7nTD9y-R5a/images/tutorials/ai-billing/wallet-topup-map.png?fit=max&auto=format&n=jKiGmb7nTD9y-R5a&q=85&s=13972cb3f5c91cd24322e99e3167ab34" className="rounded-xl" alt="Money movement map for funding a customer's wallet" width="1080" height="471" data-path="images/tutorials/ai-billing/wallet-topup-map.png" />

    [Explore the map yourself here](https://map.blnkfinance.xyz/HAeq2mnPxI)

    `@World-USD` is an [internal balance](/balances/internal-balances) that represents money entering and leaving your system. Internal balances use the `@` prefix and don't require a balance ID.
  </Step>

  <Step title="Fund the customer wallet">
    [Create a transaction](/reference/create-transaction) to add funds to the customer's wallet:

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X POST "http://localhost:5001/transactions" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "precise_amount": 12000,
          "precision": 100,
          "currency": "USD",
          "reference": "ai-billing_ref-001",
          "source": "@World-USD",
          "destination": "<customer-balance-id>",
          "description": "Wallet funding",
          "allow_overdraft": true
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.Transactions.create({
        precise_amount: 12000,
        precision: 100,
        currency: 'USD',
        reference: 'ai-billing_ref-001',
        source: '@World-USD',
        destination: '<customer-balance-id>',
        description: 'Wallet funding',
        allow_overdraft: true,
      });
      ```

      ```go Go wrap theme={"system"}
      transaction, resp, err := client.Transaction.Create(blnkgo.CreateTransactionRequest{
        ParentTransaction: blnkgo.ParentTransaction{
          PreciseAmount: 12000,
          Precision:   100,
          Currency:    "USD",
          Reference:   "ai-billing_ref-001",
          Source:      "@World-USD",
          Destination: "<customer-balance-id>",
          Description: "Wallet funding",
        },
        AllowOverdraft: true,
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk transactions create
      ```
    </CodeGroup>

    <img src="https://mintcdn.com/blnk/TZC-WDEljP4tkd4b/images/tutorials/ai-billing/fund-transaction.png?fit=max&auto=format&n=TZC-WDEljP4tkd4b&q=85&s=b2d028af297fd28940215e06dc412bda" className="rounded-xl" alt="Create a transaction to add funds to the customer's wallet" width="1695" height="981" data-path="images/tutorials/ai-billing/fund-transaction.png" />

    <Note>
      Blnk enforces [double-entry accounting](/guides/double-entry) through the source and destination fields. The source represents where funds are deducted from, while the destination represents where they are credited to.
    </Note>
  </Step>
</Steps>

***

## 3: Track usage and billing

To record usage in your ledger, you need to know **how many tokens** your AI model consumed and **what that usage is worth in USD**. Both components are recorded together atomically.

<Steps titleSize="h3">
  <Step title="Money movement map">
    When a customer uses your AI product, two things happen simultaneously:

    <img src="https://mintcdn.com/blnk/aZhTjw2yAR_BF0cV/images/tutorials/ai-billing/token-usage-map.png?fit=max&auto=format&n=aZhTjw2yAR_BF0cV&q=85&s=79ce734ec10f45e1c6df7e502a956015" className="rounded-xl" alt="Money movememt map for a usage-billing AI billing workflow" width="1298" height="609" data-path="images/tutorials/ai-billing/token-usage-map.png" />

    [Explore the map yourself here](https://map.blnkfinance.xyz/AO5n3YG0ZV)

    * Tokens are moved from the customer's token allocation to your system's token pool
    * The corresponding USD value is debited from the customer's wallet and credited to your revenue account

    Both actions occur together in a single atomic write, ensuring every token record has a matching customer debit transaction.
  </Step>

  <Step title="Connect to your LLM provider">
    Connect to your AI provider. For this example, we'll use the OpenAI TypeScript SDK:

    ```typescript theme={"system"}
    import OpenAI from "openai";

    export const openai = new OpenAI({ 
      apiKey: process.env.OPENAI_API_KEY! 
    });
    ```
  </Step>

  <Step title="Capture token usage">
    Make a completion request and extract token usage after each event:

    ```typescript theme={"system"}
    export async function runCompletion(model: string, prompt: string) {
      const response = await openai.chat.completions.create({
        model,
        messages: [{ role: "user", content: prompt }],
      });

      const inputTokens = response.usage?.prompt_tokens ?? 0;
      const outputTokens = response.usage?.completion_tokens ?? 0;

      return { 
        inputTokens, 
        outputTokens, 
        totalTokens: inputTokens + outputTokens 
      };
    }
    ```
  </Step>

  <Step title="Calculate USD value">
    Each model has its own rate for input and output tokens. Calculate the cost:

    ```typescript expandable theme={"system"}
    const MODEL_RATES = {
      "gpt-4o-mini": { inputPer1K: 0.15, outputPer1K: 0.60 },
      "gpt-4o": { inputPer1K: 0.30, outputPer1K: 1.20 },
    };

    export function calculateUSD(
      model: string, 
      inputTokens: number, 
      outputTokens: number
    ) {
      const rate = MODEL_RATES[model];
      if (!rate) throw new Error(`No pricing configured for model: ${model}`);

      const inputCost = (inputTokens / 1000) * rate.inputPer1K;
      const outputCost = (outputTokens / 1000) * rate.outputPer1K;

      return inputCost + outputCost;
    }
    ```
  </Step>

  <Step title="Record usage atomically">
    Record both token usage and dollar cost together using the [bulk transactions](/reference/bulk-transactions) endpoint with `atomic: true`:

    <CodeGroup>
      ```bash cURL wrap expandable theme={"system"}
      curl -X POST "http://localhost:5001/transactions/bulk" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "atomic": true,
          "run_async": false,
          "transactions": [
            {
              "precise_amount": 2290,
              "precision": 1,
              "currency": "TOKENS",
              "reference": "ref_ai-billing_001_tokens",
              "source": "@Token-Wallet",
              "destination": "@System-Tokens",
              "description": "Token leg",
              "allow_overdraft": true
            },
            {
              "precise_amount": 602500,
              "precision": 100,
              "currency": "USD",
              "reference": "ref_ai-billing_001_usd",
              "source": "<customer-balance-id>",
              "destination": "@Revenue",
              "description": "USD leg",
              "allow_overdraft": false
            }
          ]
        }'
      ```

      ```typescript TypeScript wrap expandable theme={"system"}
      const response = await blnk.Transactions.createBulk({
        atomic: true,
        run_async: false,
        transactions: [
          {
            precise_amount: 2290,
            precision: 1,
            currency: 'TOKENS',
            reference: 'ref_ai-billing_001_tokens',
            source: '@Token-Wallet',
            destination: '@System-Tokens',
            description: 'Token leg',
            allow_overdraft: true,
          },
          {
            precise_amount: 602500,
            precision: 100,
            currency: 'USD',
            reference: 'ref_ai-billing_001_usd',
            source: '<customer-balance-id>',
            destination: '@Revenue',
            description: 'USD leg',
            allow_overdraft: false,
          },
        ],
      });
      ```

      ```go Go wrap theme={"system"}
      ```

      ```bash Blnk CLI wrap theme={"system"}
      ```
    </CodeGroup>

    <img src="https://mintcdn.com/blnk/TZC-WDEljP4tkd4b/images/tutorials/ai-billing/record-usage.png?fit=max&auto=format&n=TZC-WDEljP4tkd4b&q=85&s=e3530b21d3bbd89b06bf20f932c21743" className="rounded-xl" alt="Record usage atomically" width="1698" height="978" data-path="images/tutorials/ai-billing/record-usage.png" />

    <Note>
      The response includes a `batch_id` that links both legs of the transaction in your ledger.
    </Note>

    <Tip>
      You can use `meta_data` to attach extra details to each transaction, such as the model name, prompt type, or customer ID.
    </Tip>
  </Step>
</Steps>

***

## 4: Prepaid vs postpaid billing

Your system can support both prepaid and postpaid billing models.

<Steps titleSize="h3">
  <Step title="Prepaid billing">
    With prepaid billing, customers fund their wallet before using your product:

    * The customer must have sufficient balance before usage
    * The corresponding dollar cost is deducted from the customer wallet
    * If the wallet balance becomes insufficient or reaches zero, the transaction fails
    * The customer must top up their wallet to continue using the product

    In the bulk transaction from step 3, set `allow_overdraft: false` on the USD leg to enforce prepaid behavior.
  </Step>

  <Step title="Postpaid billing">
    With postpaid billing, customers don't need to fund their wallet in advance:

    * Usage accumulates with the balance moving into overdraft (using `allow_overdraft: true` on the USD leg)
    * The overdraft balance represents how much the customer owes
    * At the end of the billing period, retrieve the overdraft amount to generate an invoice
    * Once payment is made, the balance resets to zero and the next cycle begins.
  </Step>
</Steps>

***

## Conclusion

You now have a fully functional AI billing system built with Blnk Finance. This system can:

* Create and manage customer wallets for tracking usage per customer
* Fund customer wallets for prepaid billing models
* Track token usage and calculate costs atomically
* Support both prepaid and postpaid billing models
* Generate accurate invoices from accumulated usage

***

<CtaCallout title="Need help with your use case?" href="https://blnkfinance.com/contact/us?utm_source=blnk_docs&utm_medium=documentation&utm_campaign=home%2Finstall" buttonLabel="Speak with us" trackingEvent="clicked_pro_support">
  Get dedicated support for architecture reviews, integration planning, ledger workflows, and production deployment.
</CtaCallout>
