BeInfi Blog
Tutorial: Automated Billing for AI with Vercel AI SDK in 5 Minutes
Back to blog
Tutorial Vercel AI SDK TypeScript Billing Code

Tutorial: Automated Billing for AI with Vercel AI SDK in 5 Minutes

I
Infi Pulse Tech Team
| | 6 min read

In this tutorial, we’ll go from zero to working automated billing in under 5 minutes. You’ll need: a project with Vercel AI SDK and an Infinitum Pulse account.

Prerequisites

  • Node.js 18+
  • A project using the ai package (Vercel AI SDK)
  • Infinitum Pulse account (free during beta)

Step 1: Install the SDK

npm install @beinfi/pulse-sdk

The package exports two things:

  • @beinfi/pulse-sdk — main client for tracking and customer management
  • @beinfi/pulse-sdk/ai — middleware for Vercel AI SDK

Step 2: Create Product and Meters

In the Pulse dashboard, create:

  1. Product: “My AI Agent”
  2. Meter 1: input_tokens — Display: “Input Tokens” — Unit: “tokens” — Price: $0.003/1K
  3. Meter 2: output_tokens — Display: “Output Tokens” — Unit: “tokens” — Price: $0.008/1K

Note the Product ID and API Key (starts with sk_live_ or sk_test_).

Step 3: Set Up Environment Variables

# .env
PULSE_API_KEY=sk_test_xxxxxxxxxxxxx
PULSE_PRODUCT_ID=prod_xxxxxxxxxxxxx
OPENAI_API_KEY=sk-xxxxxxxxxxxxx

Step 4: Add the Middleware

This is where the magic happens. The middleware intercepts all model calls and tracks tokens automatically.

// lib/ai.ts
import { Pulse } from '@beinfi/pulse-sdk'
import { pulseMiddleware } from '@beinfi/pulse-sdk/ai'
import { wrapLanguageModel } from 'ai'
import { openai } from '@ai-sdk/openai'

const pulse = new Pulse(process.env.PULSE_API_KEY!)

export function createBilledModel(customerId: string) {
  return wrapLanguageModel({
    model: openai('gpt-4o'),
    middleware: pulseMiddleware({
      pulse,
      customerId,
      meters: {
        input: 'input_tokens',
        output: 'output_tokens',
      },
    }),
  })
}

Step 5: Use the Model in Your App

With generateText (single call)

import { generateText } from 'ai'
import { createBilledModel } from '@/lib/ai'

export async function POST(req: Request) {
  const { prompt, userId } = await req.json()

  const model = createBilledModel(userId)

  const { text } = await generateText({
    model,
    prompt,
  })

  return Response.json({ text })
}

With streamText (streaming)

import { streamText } from 'ai'
import { createBilledModel } from '@/lib/ai'

export async function POST(req: Request) {
  const { messages, userId } = await req.json()

  const model = createBilledModel(userId)

  const result = streamText({
    model,
    messages,
  })

  // Tokens are tracked when the stream finishes
  return result.toDataStreamResponse()
}

With generateObject (structured output)

import { generateObject } from 'ai'
import { createBilledModel } from '@/lib/ai'
import { z } from 'zod'

const model = createBilledModel(userId)

const { object } = await generateObject({
  model,
  schema: z.object({
    summary: z.string(),
    sentiment: z.enum(['positive', 'negative', 'neutral']),
    topics: z.array(z.string()),
  }),
  prompt: `Analyze this review: "${reviewText}"`,
})

// Tokens tracked automatically, including the
// extra tokens from schema enforcement

Step 6: Register Customers

When a user signs up in your app, register them as a customer:

// lib/pulse.ts
import { Pulse } from '@beinfi/pulse-sdk'

const pulse = new Pulse(process.env.PULSE_API_KEY!)

export async function registerCustomer(user: {
  id: string
  name: string
  email: string
}) {
  await pulse.metering.createCustomer(process.env.PULSE_PRODUCT_ID!, {
    externalId: user.id,
    name: user.name,
    email: user.email,
    metadata: {
      plan: 'free',
      source: 'signup',
    },
  })
}

Step 7: Query Usage (Optional)

If you want to show consumption in your dashboard:

// api/usage/route.ts
import { Pulse } from '@beinfi/pulse-sdk'

const pulse = new Pulse(process.env.PULSE_API_KEY!)

export async function GET(req: Request) {
  const url = new URL(req.url)
  const userId = url.searchParams.get('userId')!

  const usage = await pulse.metering.getUsage({
    customerId: userId,
  })

  return Response.json(usage)
}

Response:

{
  "data": [
    {
      "meterId": "input_tokens",
      "meterName": "Input Tokens",
      "totalValue": "1523400",
      "count": 342
    },
    {
      "meterId": "output_tokens",
      "meterName": "Output Tokens",
      "totalValue": "487200",
      "count": 342
    }
  ]
}

Step 8: Done! What Happens Now?

With everything set up, the automated flow is:

  1. Day to day: Each model call tracks tokens via middleware
  2. End of month: Infinitum aggregates usage per customer
  3. Invoice generated: Detailed line items (input tokens, output tokens)
  4. Payment link: Automatically sent to customer
  5. Payment: Customer pays in USDC/USDT via link
  6. You receive: Funds in your wallet, instant settlement

Verifying It Works

Local test

// test-billing.ts
import { createBilledModel } from './lib/ai'
import { generateText } from 'ai'

async function test() {
  const model = createBilledModel('test_user_123')

  const { text, usage } = await generateText({
    model,
    prompt: 'Explain quantum computing in 2 sentences.',
  })

  console.log('Response:', text)
  console.log('Tokens:', usage)
  console.log('Check your Pulse dashboard for the tracked event!')
}

test()

In the dashboard

After running the test, go to the Pulse dashboard and you’ll see:

  • The registered metering event
  • Input and output tokens separated
  • Associated customer
  • Value calculated based on defined prices

Multi-Model Billing

If your agent uses different models, set up separate middlewares:

import { Pulse } from '@beinfi/pulse-sdk'
import { pulseMiddleware } from '@beinfi/pulse-sdk/ai'
import { wrapLanguageModel } from 'ai'
import { openai } from '@ai-sdk/openai'
import { anthropic } from '@ai-sdk/anthropic'

const pulse = new Pulse(process.env.PULSE_API_KEY!)

// GPT-4o — more expensive
export const gpt4o = (customerId: string) =>
  wrapLanguageModel({
    model: openai('gpt-4o'),
    middleware: pulseMiddleware({
      pulse,
      customerId,
      meters: {
        input: 'gpt4o_input',   // $0.005/1K
        output: 'gpt4o_output', // $0.015/1K
      },
    }),
  })

// GPT-4o-mini — cheaper
export const gpt4oMini = (customerId: string) =>
  wrapLanguageModel({
    model: openai('gpt-4o-mini'),
    middleware: pulseMiddleware({
      pulse,
      customerId,
      meters: {
        input: 'gpt4o_mini_input',   // $0.00015/1K
        output: 'gpt4o_mini_output', // $0.0006/1K
      },
    }),
  })

// Claude Sonnet
export const claude = (customerId: string) =>
  wrapLanguageModel({
    model: anthropic('claude-sonnet-4-5-20250929'),
    middleware: pulseMiddleware({
      pulse,
      customerId,
      meters: {
        input: 'claude_input',   // $0.003/1K
        output: 'claude_output', // $0.015/1K
      },
    }),
  })

Session-Based Tracking

For agents that make multiple calls per “session” (e.g., agent loops):

import { Pulse } from '@beinfi/pulse-sdk'

const pulse = new Pulse(process.env.PULSE_API_KEY!)

async function runAgent(userId: string, task: string) {
  // Creates a session that accumulates events
  const session = pulse.metering.session(userId)

  // Agent loop (multiple calls)
  for (const step of agentPlan) {
    const result = await executeStep(step)
    session.track('input_tokens', result.inputTokens)
    session.track('output_tokens', result.outputTokens)
    session.track('tool_calls', result.toolCalls)
  }

  // Sends everything at once as a batch
  await session.end()
}

Conclusion

In under 5 minutes you have:

  • Automatic tracking of tokens per user
  • Billing that runs on its own at the end of the month
  • Payment via crypto with instant settlement
  • Zero impact on your AI latency

The complete code is available on our GitHub. Any questions, join our Discord.