Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
dodopayments avatar

Usage Based Billing

  • 1 installs
  • 4 repo stars
  • Updated August 1, 2026
  • dodopayments/dodo-agent-plugin

Guide for implementing usage-based billing with Dodo Payments, including meters, events, per-unit pricing, and metered subscriptions.

About

Explains metered billing in Dodo Payments using meters, usage events, and per-unit pricing for things like AI tokens or image generation. A developer uses it when charging customers by consumption rather than a flat fee.

  • Covers meters, usage events, and per-unit pricing
  • Examples for tracking AI token and image-generation usage

Usage Based Billing by the numbers

  • 1 all-time installs (skills.sh)
  • Ranked #3,830 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dodopayments/dodo-agent-plugin --skill usage-based-billing

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs1
repo stars4
Last updatedAugust 1, 2026
Repositorydodopayments/dodo-agent-plugin

What it does

Guide for implementing usage-based billing with Dodo Payments, including meters, events, per-unit pricing, and metered subscriptions.

Files

SKILL.mdMarkdownGitHub ↗

Dodo Payments Usage-Based Billing

Reference: [docs.dodopayments.com/features/usage-based-billing](https://docs.dodopayments.com/features/usage-based-billing/introduction)

Charge customers for what they actually use—API calls, storage, AI tokens, or any metric you define.

---

Overview

Usage-based billing is perfect for:

  • APIs: Charge per request or operation
  • AI Services: Bill per token, generation, or inference
  • Infrastructure: Charge for compute, storage, bandwidth
  • SaaS: Metered features alongside subscriptions

---

Core Concepts

Events

Usage actions sent from your application:

{
  "event_id": "evt_unique_123",
  "customer_id": "cus_abc123",
  "event_name": "api.call",
  "timestamp": "2025-01-21T10:30:00Z",
  "metadata": { "endpoint": "/v1/users", "tokens": 150 }
}

Meters

Aggregate events into billable quantities:

AggregationUse CaseExample
CountTotal eventsAPI calls, image generations
SumAdd valuesTokens used, bytes transferred
MaxHighest valuePeak concurrent users
LastMost recentCurrent storage used

Products with Usage Pricing

  • Price per unit (e.g., $0.001 per API call)
  • Free threshold (e.g., 1,000 free calls)
  • Automatic billing each cycle

Billing Example: 2,500 calls - 1,000 free = 1,500 × $0.02 = $30.00

---

Quick Start

1. Create a Meter

In Dashboard → Meters → Create Meter:

1. Name: "API Requests" 2. Event Name: api.call (exact match, case-sensitive) 3. Aggregation: Count 4. Unit: "calls"

2. Create Usage-Based Product

In Dashboard → Products → Create Product:

1. Select Usage-Based type 2. Connect your meter 3. Set pricing:

  • Price Per Unit: $0.001
  • Free Threshold: 1000

3. Send Events

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
});

await client.usageEvents.ingest({
  events: [{
    event_id: `api_${Date.now()}_${Math.random()}`,
    customer_id: 'cus_abc123',
    event_name: 'api.call',
    timestamp: new Date().toISOString(),
    metadata: {
      endpoint: '/v1/users',
      method: 'GET',
    }
  }]
});

---

Implementation Examples

TypeScript/Node.js

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});

// Track single event
async function trackUsage(
  customerId: string,
  eventName: string,
  metadata: Record<string, string>
) {
  await client.usageEvents.ingest({
    events: [{
      event_id: `${eventName}_${Date.now()}_${crypto.randomUUID()}`,
      customer_id: customerId,
      event_name: eventName,
      timestamp: new Date().toISOString(),
      metadata,
    }]
  });
}

// Track API call
await trackUsage('cus_abc123', 'api.call', {
  endpoint: '/v1/generate',
  method: 'POST',
});

// Track token usage (for Sum aggregation)
await trackUsage('cus_abc123', 'token.usage', {
  tokens: '1500',
  model: 'gpt-4',
});

Batch Event Ingestion

Send multiple events efficiently (max 1000 per request):

async function trackBatchUsage(
  events: Array<{
    customerId: string;
    eventName: string;
    metadata: Record<string, string>;
    timestamp?: string;
  }>
) {
  const formattedEvents = events.map((e, i) => ({
    event_id: `batch_${Date.now()}_${i}_${crypto.randomUUID()}`,
    customer_id: e.customerId,
    event_name: e.eventName,
    timestamp: e.timestamp || new Date().toISOString(),
    metadata: e.metadata,
  }));

  await client.usageEvents.ingest({ events: formattedEvents });
}

// Batch track multiple API calls
await trackBatchUsage([
  { customerId: 'cus_abc', eventName: 'api.call', metadata: { endpoint: '/v1/users' } },
  { customerId: 'cus_abc', eventName: 'api.call', metadata: { endpoint: '/v1/orders' } },
  { customerId: 'cus_xyz', eventName: 'api.call', metadata: { endpoint: '/v1/products' } },
]);

Python

from dodopayments import DodoPayments
import uuid
from datetime import datetime

client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])

def track_usage(customer_id: str, event_name: str, metadata: dict):
    client.usage_events.ingest(events=[{
        "event_id": f"{event_name}_{datetime.now().timestamp()}_{uuid.uuid4()}",
        "customer_id": customer_id,
        "event_name": event_name,
        "timestamp": datetime.now().isoformat(),
        "metadata": metadata
    }])

# Track AI token usage
track_usage("cus_abc123", "ai.tokens", {
    "tokens": "2500",
    "model": "claude-3",
    "operation": "completion"
})

# Track image generation
track_usage("cus_abc123", "image.generated", {
    "size": "1024x1024",
    "model": "dall-e-3"
})

Go

package main

import (
    "context"
    "fmt"
    "os"
    "time"

    "github.com/dodopayments/dodopayments-go"
    "github.com/google/uuid"
)

func main() {
    client := dodopayments.NewClient(
        option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
    )

    ctx := context.Background()

    _, err := client.UsageEvents.Ingest(ctx, &dodopayments.UsageEventIngestParams{
        Events: []dodopayments.UsageEvent{{
            EventID:    fmt.Sprintf("api_%d_%s", time.Now().Unix(), uuid.New().String()),
            CustomerID: "cus_abc123",
            EventName:  "api.call",
            Timestamp:  time.Now().Format(time.RFC3339),
            Metadata: map[string]string{
                "endpoint": "/v1/users",
                "method":   "GET",
            },
        }},
    })

    if err != nil {
        panic(err)
    }
}

---

Meter Configuration

Aggregation Types

Count (API Calls, Requests)
Meter: API Requests
Event Name: api.call
Aggregation: Count
Unit: calls
Sum (Tokens, Bytes)
Meter: Token Usage
Event Name: token.usage
Aggregation: Sum
Over Property: tokens
Unit: tokens

Events must include the property in metadata:

await client.usageEvents.ingest({
  events: [{
    event_id: 'token_123',
    customer_id: 'cus_abc',
    event_name: 'token.usage',
    metadata: { tokens: '1500' } // This value gets summed
  }]
});
Max (Peak Concurrent Users)
Meter: Peak Users
Event Name: concurrent.users
Aggregation: Max
Over Property: count
Unit: users
Last (Current Storage)
Meter: Storage Used
Event Name: storage.snapshot
Aggregation: Last
Over Property: bytes
Unit: GB

Event Filtering

Filter which events count toward the meter:

Filter Logic: AND
Conditions:
  - Property: tier, Equals: "premium"
  - Property: status, Equals: "success"

Only events matching ALL conditions are counted.

---

Common Use Cases

AI Token Billing

// Meter: AI Tokens (Sum aggregation over "tokens" property)

async function trackAIUsage(
  customerId: string,
  promptTokens: number,
  completionTokens: number,
  model: string
) {
  const totalTokens = promptTokens + completionTokens;

  await client.usageEvents.ingest({
    events: [{
      event_id: `ai_${Date.now()}_${crypto.randomUUID()}`,
      customer_id: customerId,
      event_name: 'ai.tokens',
      timestamp: new Date().toISOString(),
      metadata: {
        tokens: totalTokens.toString(),
        prompt_tokens: promptTokens.toString(),
        completion_tokens: completionTokens.toString(),
        model,
      }
    }]
  });
}

// After AI completion
await trackAIUsage('cus_abc', 500, 1200, 'gpt-4');

Image Generation

// Meter: Images Generated (Count aggregation)

async function trackImageGeneration(
  customerId: string,
  imageSize: string,
  model: string
) {
  await client.usageEvents.ingest({
    events: [{
      event_id: `img_${Date.now()}_${crypto.randomUUID()}`,
      customer_id: customerId,
      event_name: 'image.generated',
      timestamp: new Date().toISOString(),
      metadata: {
        size: imageSize,
        model,
      }
    }]
  });
}

API Rate Tracking

// Middleware for Express/Next.js

async function trackAPIUsage(
  req: Request,
  customerId: string
) {
  await client.usageEvents.ingest({
    events: [{
      event_id: `api_${Date.now()}_${crypto.randomUUID()}`,
      customer_id: customerId,
      event_name: 'api.call',
      timestamp: new Date().toISOString(),
      metadata: {
        endpoint: req.url,
        method: req.method,
        user_agent: req.headers['user-agent'] || 'unknown',
      }
    }]
  });
}

Storage Billing

// Meter: Storage (Last aggregation - snapshot of current usage)

async function updateStorageUsage(customerId: string, bytesUsed: number) {
  await client.usageEvents.ingest({
    events: [{
      event_id: `storage_${Date.now()}_${customerId}`,
      customer_id: customerId,
      event_name: 'storage.snapshot',
      timestamp: new Date().toISOString(),
      metadata: {
        bytes: bytesUsed.toString(),
        gb: (bytesUsed / 1024 / 1024 / 1024).toFixed(2),
      }
    }]
  });
}

// Call periodically or after storage changes
await updateStorageUsage('cus_abc', 5368709120); // 5GB

---

Next.js Integration

API Route for Usage Tracking

// app/api/track-usage/route.ts
import { NextRequest, NextResponse } from 'next/server';
import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});

export async function POST(req: NextRequest) {
  const { customerId, eventName, metadata } = await req.json();

  try {
    await client.usageEvents.ingest({
      events: [{
        event_id: `${eventName}_${Date.now()}_${crypto.randomUUID()}`,
        customer_id: customerId,
        event_name: eventName,
        timestamp: new Date().toISOString(),
        metadata,
      }]
    });

    return NextResponse.json({ success: true });
  } catch (error: any) {
    return NextResponse.json(
      { error: error.message },
      { status: 500 }
    );
  }
}

Usage Tracking Hook

// hooks/useUsageTracking.ts
import { useCallback } from 'react';

export function useUsageTracking(customerId: string) {
  const trackUsage = useCallback(async (
    eventName: string,
    metadata: Record<string, string>
  ) => {
    await fetch('/api/track-usage', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ customerId, eventName, metadata }),
    });
  }, [customerId]);

  return { trackUsage };
}

// Usage in component
function AIChat() {
  const { trackUsage } = useUsageTracking('cus_abc123');

  const handleGenerate = async () => {
    const result = await generateAIResponse(prompt);
    
    // Track token usage
    await trackUsage('ai.tokens', {
      tokens: result.totalTokens.toString(),
      model: 'gpt-4',
    });
  };
}

---

Hybrid Billing

Combine usage-based with subscriptions:

Subscription + Usage Overage

// 1. Customer subscribes to plan with included usage
// Product: Pro Plan - $49/month + $0.01/call after 10,000 free

// 2. Track all usage events
await trackUsage('cus_abc', 'api.call', { endpoint: '/v1/generate' });

// 3. Dodo automatically:
//    - Applies free threshold (10,000 calls)
//    - Charges overage at $0.01/call
//    - Combines with subscription fee

Multiple Meters per Product

Attach up to 10 meters to a single product:

Product: AI Platform
├── Meter: API Calls ($0.001/call, 1000 free)
├── Meter: Token Usage ($0.01/1000 tokens)
├── Meter: Image Generations ($0.05/image, 10 free)
└── Meter: Storage ($0.10/GB)

Credit-Based Meter Billing

Link meters to credit entitlements so usage events deduct from a customer's credit balance instead of charging per-unit:

1. Create a credit entitlement (Dashboard → Products → Credits) 2. Create a usage-based product with a meter 3. On the meter, toggle Bill usage in Credits 4. Select the credit entitlement and set Meter units per credit

// Meter: AI Tokens (Sum aggregation over "tokens")
// Credit: "AI Credits" with 10,000 credits/cycle
// Meter units per credit: 1000 (1,000 tokens = 1 credit)

// Usage events deduct credits automatically
await client.usageEvents.ingest({
  events: [{
    event_id: `ai_${Date.now()}_${crypto.randomUUID()}`,
    customer_id: 'cus_abc123',
    event_name: 'ai.tokens',
    timestamp: new Date().toISOString(),
    metadata: { tokens: '1500', model: 'gpt-4' }
  }]
});

// Check remaining credit balance
const balance = await client.creditEntitlements.balances.get(
  'cent_ai_credits',
  'cus_abc123'
);
console.log(`Credits remaining: ${balance.available_balance}`);

Credit deduction runs via a background worker every minute using FIFO ordering (oldest grants consumed first). When credits run out:

  • Overage disabled: Usage is blocked
  • Overage enabled: Usage continues and overage is tracked per your configured behavior (forgive, bill, or carry deficit)

---

Best Practices

1. Unique Event IDs

Always generate unique IDs for idempotency:

const eventId = `${eventName}_${Date.now()}_${crypto.randomUUID()}`;

2. Batch Events

For high-volume, batch events (max 1000/request):

// Queue events and send in batches
const eventQueue: Event[] = [];

function queueEvent(event: Event) {
  eventQueue.push(event);
  if (eventQueue.length >= 100) {
    flushEvents();
  }
}

async function flushEvents() {
  if (eventQueue.length === 0) return;
  const batch = eventQueue.splice(0, 1000);
  await client.usageEvents.ingest({ events: batch });
}

// Flush periodically
setInterval(flushEvents, 5000);

3. Handle Failures Gracefully

Implement retry logic for event ingestion:

async function trackWithRetry(event: Event, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      await client.usageEvents.ingest({ events: [event] });
      return;
    } catch (error) {
      if (i === retries - 1) throw error;
      await sleep(1000 * Math.pow(2, i)); // Exponential backoff
    }
  }
}

4. Include Relevant Metadata

Add context for debugging and filtering:

metadata: {
  endpoint: '/v1/generate',
  method: 'POST',
  model: 'gpt-4',
  user_id: 'internal_user_123',
  request_id: requestId,
}

5. Monitor in Dashboard

Check Meters dashboard for:

  • Event volume and trends
  • Usage per customer
  • Aggregated quantities

---

Pricing Examples

API Service

Meter: API Calls (Count)
Price: $0.001 per call
Free Threshold: 1,000 calls/month

Customer uses 15,000 calls:
(15,000 - 1,000) × $0.001 = $14.00

AI Token Service

Meter: Tokens (Sum over "tokens")
Price: $0.00001 per token ($0.01 per 1,000)
Free Threshold: 10,000 tokens

Customer uses 50,000 tokens:
(50,000 - 10,000) × $0.00001 = $0.40

Image Generation

Meter: Images (Count)
Price: $0.05 per image
Free Threshold: 10 images

Customer generates 100 images:
(100 - 10) × $0.05 = $4.50

---

Resources

Related skills

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.