Claude
Skills
Sign in
Back

usage-based-billing

Included with Lifetime
$97 forever

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

General

What this skill does


# 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:
```json
{
  "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

```typescript
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

```typescript
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):

```typescript
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

```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

```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:
```typescript
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

```typescript
// 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

```typescript
// 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()

Related in General