@dodopayments/opencode-plugin 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,680 @@
1
+ ---
2
+ name: usage-based-billing
3
+ description: Guide for implementing usage-based billing with Dodo Payments - meters, events, pricing per unit, and metered subscriptions.
4
+ ---
5
+
6
+ # Dodo Payments Usage-Based Billing
7
+
8
+ **Reference: [docs.dodopayments.com/features/usage-based-billing](https://docs.dodopayments.com/features/usage-based-billing/introduction)**
9
+
10
+ Charge customers for what they actually use—API calls, storage, AI tokens, or any metric you define.
11
+
12
+ ---
13
+
14
+ ## Overview
15
+
16
+ Usage-based billing is perfect for:
17
+ - **APIs**: Charge per request or operation
18
+ - **AI Services**: Bill per token, generation, or inference
19
+ - **Infrastructure**: Charge for compute, storage, bandwidth
20
+ - **SaaS**: Metered features alongside subscriptions
21
+
22
+ ---
23
+
24
+ ## Core Concepts
25
+
26
+ ### Events
27
+ Usage actions sent from your application:
28
+ ```json
29
+ {
30
+ "event_id": "evt_unique_123",
31
+ "customer_id": "cus_abc123",
32
+ "event_name": "api.call",
33
+ "timestamp": "2025-01-21T10:30:00Z",
34
+ "metadata": { "endpoint": "/v1/users", "tokens": 150 }
35
+ }
36
+ ```
37
+
38
+ ### Meters
39
+ Aggregate events into billable quantities:
40
+ | Aggregation | Use Case | Example |
41
+ |-------------|----------|---------|
42
+ | **Count** | Total events | API calls, image generations |
43
+ | **Sum** | Add values | Tokens used, bytes transferred |
44
+ | **Max** | Highest value | Peak concurrent users |
45
+ | **Last** | Most recent | Current storage used |
46
+
47
+ ### Products with Usage Pricing
48
+ - Price per unit (e.g., $0.001 per API call)
49
+ - Free threshold (e.g., 1,000 free calls)
50
+ - Automatic billing each cycle
51
+
52
+ **Billing Example**: 2,500 calls - 1,000 free = 1,500 × $0.02 = $30.00
53
+
54
+ ---
55
+
56
+ ## Quick Start
57
+
58
+ ### 1. Create a Meter
59
+
60
+ In Dashboard → Meters → Create Meter:
61
+
62
+ 1. **Name**: "API Requests"
63
+ 2. **Event Name**: `api.call` (exact match, case-sensitive)
64
+ 3. **Aggregation**: Count
65
+ 4. **Unit**: "calls"
66
+
67
+ ### 2. Create Usage-Based Product
68
+
69
+ In Dashboard → Products → Create Product:
70
+
71
+ 1. Select **Usage-Based** type
72
+ 2. Connect your meter
73
+ 3. Set pricing:
74
+ - **Price Per Unit**: $0.001
75
+ - **Free Threshold**: 1000
76
+
77
+ ### 3. Send Events
78
+
79
+ ```typescript
80
+ import DodoPayments from 'dodopayments';
81
+
82
+ const client = new DodoPayments({
83
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY,
84
+ });
85
+
86
+ await client.usageEvents.ingest({
87
+ events: [{
88
+ event_id: `api_${Date.now()}_${Math.random()}`,
89
+ customer_id: 'cus_abc123',
90
+ event_name: 'api.call',
91
+ timestamp: new Date().toISOString(),
92
+ metadata: {
93
+ endpoint: '/v1/users',
94
+ method: 'GET',
95
+ }
96
+ }]
97
+ });
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Implementation Examples
103
+
104
+ ### TypeScript/Node.js
105
+
106
+ ```typescript
107
+ import DodoPayments from 'dodopayments';
108
+
109
+ const client = new DodoPayments({
110
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
111
+ });
112
+
113
+ // Track single event
114
+ async function trackUsage(
115
+ customerId: string,
116
+ eventName: string,
117
+ metadata: Record<string, string>
118
+ ) {
119
+ await client.usageEvents.ingest({
120
+ events: [{
121
+ event_id: `${eventName}_${Date.now()}_${crypto.randomUUID()}`,
122
+ customer_id: customerId,
123
+ event_name: eventName,
124
+ timestamp: new Date().toISOString(),
125
+ metadata,
126
+ }]
127
+ });
128
+ }
129
+
130
+ // Track API call
131
+ await trackUsage('cus_abc123', 'api.call', {
132
+ endpoint: '/v1/generate',
133
+ method: 'POST',
134
+ });
135
+
136
+ // Track token usage (for Sum aggregation)
137
+ await trackUsage('cus_abc123', 'token.usage', {
138
+ tokens: '1500',
139
+ model: 'gpt-4',
140
+ });
141
+ ```
142
+
143
+ ### Batch Event Ingestion
144
+
145
+ Send multiple events efficiently (max 1000 per request):
146
+
147
+ ```typescript
148
+ async function trackBatchUsage(
149
+ events: Array<{
150
+ customerId: string;
151
+ eventName: string;
152
+ metadata: Record<string, string>;
153
+ timestamp?: string;
154
+ }>
155
+ ) {
156
+ const formattedEvents = events.map((e, i) => ({
157
+ event_id: `batch_${Date.now()}_${i}_${crypto.randomUUID()}`,
158
+ customer_id: e.customerId,
159
+ event_name: e.eventName,
160
+ timestamp: e.timestamp || new Date().toISOString(),
161
+ metadata: e.metadata,
162
+ }));
163
+
164
+ await client.usageEvents.ingest({ events: formattedEvents });
165
+ }
166
+
167
+ // Batch track multiple API calls
168
+ await trackBatchUsage([
169
+ { customerId: 'cus_abc', eventName: 'api.call', metadata: { endpoint: '/v1/users' } },
170
+ { customerId: 'cus_abc', eventName: 'api.call', metadata: { endpoint: '/v1/orders' } },
171
+ { customerId: 'cus_xyz', eventName: 'api.call', metadata: { endpoint: '/v1/products' } },
172
+ ]);
173
+ ```
174
+
175
+ ### Python
176
+
177
+ ```python
178
+ from dodopayments import DodoPayments
179
+ import uuid
180
+ from datetime import datetime
181
+
182
+ client = DodoPayments(bearer_token=os.environ["DODO_PAYMENTS_API_KEY"])
183
+
184
+ def track_usage(customer_id: str, event_name: str, metadata: dict):
185
+ client.usage_events.ingest(events=[{
186
+ "event_id": f"{event_name}_{datetime.now().timestamp()}_{uuid.uuid4()}",
187
+ "customer_id": customer_id,
188
+ "event_name": event_name,
189
+ "timestamp": datetime.now().isoformat(),
190
+ "metadata": metadata
191
+ }])
192
+
193
+ # Track AI token usage
194
+ track_usage("cus_abc123", "ai.tokens", {
195
+ "tokens": "2500",
196
+ "model": "claude-3",
197
+ "operation": "completion"
198
+ })
199
+
200
+ # Track image generation
201
+ track_usage("cus_abc123", "image.generated", {
202
+ "size": "1024x1024",
203
+ "model": "dall-e-3"
204
+ })
205
+ ```
206
+
207
+ ### Go
208
+
209
+ ```go
210
+ package main
211
+
212
+ import (
213
+ "context"
214
+ "fmt"
215
+ "os"
216
+ "time"
217
+
218
+ "github.com/dodopayments/dodopayments-go"
219
+ "github.com/google/uuid"
220
+ )
221
+
222
+ func main() {
223
+ client := dodopayments.NewClient(
224
+ option.WithBearerToken(os.Getenv("DODO_PAYMENTS_API_KEY")),
225
+ )
226
+
227
+ ctx := context.Background()
228
+
229
+ _, err := client.UsageEvents.Ingest(ctx, &dodopayments.UsageEventIngestParams{
230
+ Events: []dodopayments.UsageEvent{{
231
+ EventID: fmt.Sprintf("api_%d_%s", time.Now().Unix(), uuid.New().String()),
232
+ CustomerID: "cus_abc123",
233
+ EventName: "api.call",
234
+ Timestamp: time.Now().Format(time.RFC3339),
235
+ Metadata: map[string]string{
236
+ "endpoint": "/v1/users",
237
+ "method": "GET",
238
+ },
239
+ }},
240
+ })
241
+
242
+ if err != nil {
243
+ panic(err)
244
+ }
245
+ }
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Meter Configuration
251
+
252
+ ### Aggregation Types
253
+
254
+ #### Count (API Calls, Requests)
255
+ ```
256
+ Meter: API Requests
257
+ Event Name: api.call
258
+ Aggregation: Count
259
+ Unit: calls
260
+ ```
261
+
262
+ #### Sum (Tokens, Bytes)
263
+ ```
264
+ Meter: Token Usage
265
+ Event Name: token.usage
266
+ Aggregation: Sum
267
+ Over Property: tokens
268
+ Unit: tokens
269
+ ```
270
+
271
+ Events must include the property in metadata:
272
+ ```typescript
273
+ await client.usageEvents.ingest({
274
+ events: [{
275
+ event_id: 'token_123',
276
+ customer_id: 'cus_abc',
277
+ event_name: 'token.usage',
278
+ metadata: { tokens: '1500' } // This value gets summed
279
+ }]
280
+ });
281
+ ```
282
+
283
+ #### Max (Peak Concurrent Users)
284
+ ```
285
+ Meter: Peak Users
286
+ Event Name: concurrent.users
287
+ Aggregation: Max
288
+ Over Property: count
289
+ Unit: users
290
+ ```
291
+
292
+ #### Last (Current Storage)
293
+ ```
294
+ Meter: Storage Used
295
+ Event Name: storage.snapshot
296
+ Aggregation: Last
297
+ Over Property: bytes
298
+ Unit: GB
299
+ ```
300
+
301
+ ### Event Filtering
302
+
303
+ Filter which events count toward the meter:
304
+
305
+ ```
306
+ Filter Logic: AND
307
+ Conditions:
308
+ - Property: tier, Equals: "premium"
309
+ - Property: status, Equals: "success"
310
+ ```
311
+
312
+ Only events matching ALL conditions are counted.
313
+
314
+ ---
315
+
316
+ ## Common Use Cases
317
+
318
+ ### AI Token Billing
319
+
320
+ ```typescript
321
+ // Meter: AI Tokens (Sum aggregation over "tokens" property)
322
+
323
+ async function trackAIUsage(
324
+ customerId: string,
325
+ promptTokens: number,
326
+ completionTokens: number,
327
+ model: string
328
+ ) {
329
+ const totalTokens = promptTokens + completionTokens;
330
+
331
+ await client.usageEvents.ingest({
332
+ events: [{
333
+ event_id: `ai_${Date.now()}_${crypto.randomUUID()}`,
334
+ customer_id: customerId,
335
+ event_name: 'ai.tokens',
336
+ timestamp: new Date().toISOString(),
337
+ metadata: {
338
+ tokens: totalTokens.toString(),
339
+ prompt_tokens: promptTokens.toString(),
340
+ completion_tokens: completionTokens.toString(),
341
+ model,
342
+ }
343
+ }]
344
+ });
345
+ }
346
+
347
+ // After AI completion
348
+ await trackAIUsage('cus_abc', 500, 1200, 'gpt-4');
349
+ ```
350
+
351
+ ### Image Generation
352
+
353
+ ```typescript
354
+ // Meter: Images Generated (Count aggregation)
355
+
356
+ async function trackImageGeneration(
357
+ customerId: string,
358
+ imageSize: string,
359
+ model: string
360
+ ) {
361
+ await client.usageEvents.ingest({
362
+ events: [{
363
+ event_id: `img_${Date.now()}_${crypto.randomUUID()}`,
364
+ customer_id: customerId,
365
+ event_name: 'image.generated',
366
+ timestamp: new Date().toISOString(),
367
+ metadata: {
368
+ size: imageSize,
369
+ model,
370
+ }
371
+ }]
372
+ });
373
+ }
374
+ ```
375
+
376
+ ### API Rate Tracking
377
+
378
+ ```typescript
379
+ // Middleware for Express/Next.js
380
+
381
+ async function trackAPIUsage(
382
+ req: Request,
383
+ customerId: string
384
+ ) {
385
+ await client.usageEvents.ingest({
386
+ events: [{
387
+ event_id: `api_${Date.now()}_${crypto.randomUUID()}`,
388
+ customer_id: customerId,
389
+ event_name: 'api.call',
390
+ timestamp: new Date().toISOString(),
391
+ metadata: {
392
+ endpoint: req.url,
393
+ method: req.method,
394
+ user_agent: req.headers['user-agent'] || 'unknown',
395
+ }
396
+ }]
397
+ });
398
+ }
399
+ ```
400
+
401
+ ### Storage Billing
402
+
403
+ ```typescript
404
+ // Meter: Storage (Last aggregation - snapshot of current usage)
405
+
406
+ async function updateStorageUsage(customerId: string, bytesUsed: number) {
407
+ await client.usageEvents.ingest({
408
+ events: [{
409
+ event_id: `storage_${Date.now()}_${customerId}`,
410
+ customer_id: customerId,
411
+ event_name: 'storage.snapshot',
412
+ timestamp: new Date().toISOString(),
413
+ metadata: {
414
+ bytes: bytesUsed.toString(),
415
+ gb: (bytesUsed / 1024 / 1024 / 1024).toFixed(2),
416
+ }
417
+ }]
418
+ });
419
+ }
420
+
421
+ // Call periodically or after storage changes
422
+ await updateStorageUsage('cus_abc', 5368709120); // 5GB
423
+ ```
424
+
425
+ ---
426
+
427
+ ## Next.js Integration
428
+
429
+ ### API Route for Usage Tracking
430
+
431
+ ```typescript
432
+ // app/api/track-usage/route.ts
433
+ import { NextRequest, NextResponse } from 'next/server';
434
+ import DodoPayments from 'dodopayments';
435
+
436
+ const client = new DodoPayments({
437
+ bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
438
+ });
439
+
440
+ export async function POST(req: NextRequest) {
441
+ const { customerId, eventName, metadata } = await req.json();
442
+
443
+ try {
444
+ await client.usageEvents.ingest({
445
+ events: [{
446
+ event_id: `${eventName}_${Date.now()}_${crypto.randomUUID()}`,
447
+ customer_id: customerId,
448
+ event_name: eventName,
449
+ timestamp: new Date().toISOString(),
450
+ metadata,
451
+ }]
452
+ });
453
+
454
+ return NextResponse.json({ success: true });
455
+ } catch (error: any) {
456
+ return NextResponse.json(
457
+ { error: error.message },
458
+ { status: 500 }
459
+ );
460
+ }
461
+ }
462
+ ```
463
+
464
+ ### Usage Tracking Hook
465
+
466
+ ```typescript
467
+ // hooks/useUsageTracking.ts
468
+ import { useCallback } from 'react';
469
+
470
+ export function useUsageTracking(customerId: string) {
471
+ const trackUsage = useCallback(async (
472
+ eventName: string,
473
+ metadata: Record<string, string>
474
+ ) => {
475
+ await fetch('/api/track-usage', {
476
+ method: 'POST',
477
+ headers: { 'Content-Type': 'application/json' },
478
+ body: JSON.stringify({ customerId, eventName, metadata }),
479
+ });
480
+ }, [customerId]);
481
+
482
+ return { trackUsage };
483
+ }
484
+
485
+ // Usage in component
486
+ function AIChat() {
487
+ const { trackUsage } = useUsageTracking('cus_abc123');
488
+
489
+ const handleGenerate = async () => {
490
+ const result = await generateAIResponse(prompt);
491
+
492
+ // Track token usage
493
+ await trackUsage('ai.tokens', {
494
+ tokens: result.totalTokens.toString(),
495
+ model: 'gpt-4',
496
+ });
497
+ };
498
+ }
499
+ ```
500
+
501
+ ---
502
+
503
+ ## Hybrid Billing
504
+
505
+ Combine usage-based with subscriptions:
506
+
507
+ ### Subscription + Usage Overage
508
+
509
+ ```typescript
510
+ // 1. Customer subscribes to plan with included usage
511
+ // Product: Pro Plan - $49/month + $0.01/call after 10,000 free
512
+
513
+ // 2. Track all usage events
514
+ await trackUsage('cus_abc', 'api.call', { endpoint: '/v1/generate' });
515
+
516
+ // 3. Dodo automatically:
517
+ // - Applies free threshold (10,000 calls)
518
+ // - Charges overage at $0.01/call
519
+ // - Combines with subscription fee
520
+ ```
521
+
522
+ ### Multiple Meters per Product
523
+
524
+ Attach up to 10 meters to a single product:
525
+
526
+ ```
527
+ Product: AI Platform
528
+ ├── Meter: API Calls ($0.001/call, 1000 free)
529
+ ├── Meter: Token Usage ($0.01/1000 tokens)
530
+ ├── Meter: Image Generations ($0.05/image, 10 free)
531
+ └── Meter: Storage ($0.10/GB)
532
+ ```
533
+
534
+ ### Credit-Based Meter Billing
535
+
536
+ Link meters to credit entitlements so usage events deduct from a customer's credit balance instead of charging per-unit:
537
+
538
+ 1. Create a credit entitlement (Dashboard → Products → Credits)
539
+ 2. Create a usage-based product with a meter
540
+ 3. On the meter, toggle **Bill usage in Credits**
541
+ 4. Select the credit entitlement and set **Meter units per credit**
542
+
543
+ ```typescript
544
+ // Meter: AI Tokens (Sum aggregation over "tokens")
545
+ // Credit: "AI Credits" with 10,000 credits/cycle
546
+ // Meter units per credit: 1000 (1,000 tokens = 1 credit)
547
+
548
+ // Usage events deduct credits automatically
549
+ await client.usageEvents.ingest({
550
+ events: [{
551
+ event_id: `ai_${Date.now()}_${crypto.randomUUID()}`,
552
+ customer_id: 'cus_abc123',
553
+ event_name: 'ai.tokens',
554
+ timestamp: new Date().toISOString(),
555
+ metadata: { tokens: '1500', model: 'gpt-4' }
556
+ }]
557
+ });
558
+
559
+ // Check remaining credit balance
560
+ const balance = await client.creditEntitlements.balances.get(
561
+ 'cent_ai_credits',
562
+ 'cus_abc123'
563
+ );
564
+ console.log(`Credits remaining: ${balance.available_balance}`);
565
+ ```
566
+
567
+ Credit deduction runs via a background worker every minute using FIFO ordering (oldest grants consumed first). When credits run out:
568
+ - **Overage disabled**: Usage is blocked
569
+ - **Overage enabled**: Usage continues and overage is tracked per your configured behavior (forgive, bill, or carry deficit)
570
+ ---
571
+
572
+ ## Best Practices
573
+
574
+ ### 1. Unique Event IDs
575
+ Always generate unique IDs for idempotency:
576
+ ```typescript
577
+ const eventId = `${eventName}_${Date.now()}_${crypto.randomUUID()}`;
578
+ ```
579
+
580
+ ### 2. Batch Events
581
+ For high-volume, batch events (max 1000/request):
582
+ ```typescript
583
+ // Queue events and send in batches
584
+ const eventQueue: Event[] = [];
585
+
586
+ function queueEvent(event: Event) {
587
+ eventQueue.push(event);
588
+ if (eventQueue.length >= 100) {
589
+ flushEvents();
590
+ }
591
+ }
592
+
593
+ async function flushEvents() {
594
+ if (eventQueue.length === 0) return;
595
+ const batch = eventQueue.splice(0, 1000);
596
+ await client.usageEvents.ingest({ events: batch });
597
+ }
598
+
599
+ // Flush periodically
600
+ setInterval(flushEvents, 5000);
601
+ ```
602
+
603
+ ### 3. Handle Failures Gracefully
604
+ Implement retry logic for event ingestion:
605
+ ```typescript
606
+ async function trackWithRetry(event: Event, retries = 3) {
607
+ for (let i = 0; i < retries; i++) {
608
+ try {
609
+ await client.usageEvents.ingest({ events: [event] });
610
+ return;
611
+ } catch (error) {
612
+ if (i === retries - 1) throw error;
613
+ await sleep(1000 * Math.pow(2, i)); // Exponential backoff
614
+ }
615
+ }
616
+ }
617
+ ```
618
+
619
+ ### 4. Include Relevant Metadata
620
+ Add context for debugging and filtering:
621
+ ```typescript
622
+ metadata: {
623
+ endpoint: '/v1/generate',
624
+ method: 'POST',
625
+ model: 'gpt-4',
626
+ user_id: 'internal_user_123',
627
+ request_id: requestId,
628
+ }
629
+ ```
630
+
631
+ ### 5. Monitor in Dashboard
632
+ Check Meters dashboard for:
633
+ - Event volume and trends
634
+ - Usage per customer
635
+ - Aggregated quantities
636
+
637
+ ---
638
+
639
+ ## Pricing Examples
640
+
641
+ ### API Service
642
+ ```
643
+ Meter: API Calls (Count)
644
+ Price: $0.001 per call
645
+ Free Threshold: 1,000 calls/month
646
+
647
+ Customer uses 15,000 calls:
648
+ (15,000 - 1,000) × $0.001 = $14.00
649
+ ```
650
+
651
+ ### AI Token Service
652
+ ```
653
+ Meter: Tokens (Sum over "tokens")
654
+ Price: $0.00001 per token ($0.01 per 1,000)
655
+ Free Threshold: 10,000 tokens
656
+
657
+ Customer uses 50,000 tokens:
658
+ (50,000 - 10,000) × $0.00001 = $0.40
659
+ ```
660
+
661
+ ### Image Generation
662
+ ```
663
+ Meter: Images (Count)
664
+ Price: $0.05 per image
665
+ Free Threshold: 10 images
666
+
667
+ Customer generates 100 images:
668
+ (100 - 10) × $0.05 = $4.50
669
+ ```
670
+
671
+ ---
672
+
673
+ ## Resources
674
+
675
+ - [Usage-Based Billing Guide](https://docs.dodopayments.com/features/usage-based-billing/introduction)
676
+ - [Meters Documentation](https://docs.dodopayments.com/features/usage-based-billing/meters)
677
+ - [Event Ingestion](https://docs.dodopayments.com/features/usage-based-billing/event-ingestion)
678
+ - [AI Chat App Tutorial](https://docs.dodopayments.com/developer-resources/build-an-ai-chat-app-with-usage-based-billing)
679
+ - [Hybrid Billing Models](https://docs.dodopayments.com/features/hybrid-billing)
680
+ - [Credit-Based Billing](https://docs.dodopayments.com/features/credit-based-billing)