Productivity

usage-based-billing

dodopayments/skills · updated Apr 8, 2026

$npx skills add https://github.com/dodopayments/skills --skill usage-based-billing
summary

Reference: docs.dodopayments.com/features/usage-based-billing

skill.md

Dodo Payments Usage-Based Billing

Reference: docs.dodopayments.com/features/usage-based-billing

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:

Aggregation Use Case Example
Count Total events API calls, image generations
Sum Add values Tokens used, bytes transferred
Max Highest value Peak concurrent users
Last Most recent Current 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

general reviews

Ratings

4.554 reviews
  • Ganesh Mohane· Dec 24, 2024

    usage-based-billing is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.

  • Arjun Gupta· Dec 24, 2024

    I recommend usage-based-billing for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.

  • Chinedu Khanna· Dec 24, 2024

    usage-based-billing reduced setup friction for our internal harness; good balance of opinion and flexibility.

  • Aisha Diallo· Dec 24, 2024

    Useful defaults in usage-based-billing — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.

  • Hassan Zhang· Dec 16, 2024

    usage-based-billing is among the better-maintained entries we tried; worth keeping pinned for repeat workflows.

  • Anika Lopez· Nov 27, 2024

    Keeps context tight: usage-based-billing is the kind of skill you can hand to a new teammate without a long onboarding doc.

  • Sakshi Patil· Nov 15, 2024

    usage-based-billing fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.

  • Hassan Bansal· Nov 15, 2024

    Registry listing for usage-based-billing matched our evaluation — installs cleanly and behaves as described in the markdown.

  • Chinedu Malhotra· Nov 15, 2024

    We added usage-based-billing from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.

  • Yash Thakker· Nov 7, 2024

    Keeps context tight: usage-based-billing is the kind of skill you can hand to a new teammate without a long onboarding doc.

showing 1-10 of 54

1 / 6