@drip-sdk/node 1.0.10 → 1.1.1

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.
package/README.md CHANGED
@@ -1,702 +1,264 @@
1
- # @drip-sdk/node
1
+ # Drip SDK (Node.js)
2
2
 
3
- The official Node.js SDK for **Drip** - Usage-based billing for AI agents.
3
+ Drip is a lightweight SDK for **usage tracking and execution logging** in systems where cost is tied to computation — AI agents, APIs, background jobs, and infra workloads.
4
4
 
5
- Drip enables real-time, per-request billing using USDC on blockchain. Perfect for AI APIs, compute platforms, and any service with variable usage patterns.
5
+ This **Core SDK** is designed for pilots: it records *what ran* and *how much it used*, without handling billing or balances.
6
6
 
7
- [![npm version](https://badge.fury.io/js/@drip-sdk/node.svg)](https://www.npmjs.com/package/@drip-sdk/node)
7
+ **One line to start tracking:** `await drip.trackUsage({ customerId, meter, quantity })`
8
+
9
+ [![npm version](https://img.shields.io/npm/v/%40drip-sdk%2Fnode.svg)](https://www.npmjs.com/package/@drip-sdk/node)
8
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
11
 
10
- ## Installation
12
+ ---
11
13
 
12
- ```bash
13
- npm install @drip-sdk/node
14
- ```
14
+ ## 60-Second Quickstart (Core SDK)
15
15
 
16
- ```bash
17
- yarn add @drip-sdk/node
18
- ```
16
+ ### 1. Install
19
17
 
20
18
  ```bash
21
- pnpm add @drip-sdk/node
19
+ npm install @drip-sdk/node
22
20
  ```
23
21
 
24
- ## Quick Start
25
-
26
- ### One-Liner Integration (Recommended)
27
-
28
- The fastest way to add billing to your API:
22
+ ### 2. Set your API key
29
23
 
30
- #### Next.js App Router
31
-
32
- ```typescript
33
- // app/api/generate/route.ts
34
- import { withDrip } from '@drip-sdk/node/next';
24
+ ```bash
25
+ # Secret key — full API access (server-side only, never expose publicly)
26
+ export DRIP_API_KEY=sk_test_...
35
27
 
36
- export const POST = withDrip({
37
- meter: 'api_calls',
38
- quantity: 1,
39
- }, async (req, { charge, customerId }) => {
40
- // Your handler - payment already verified!
41
- console.log(`Charged ${charge.charge.amountUsdc} USDC to ${customerId}`);
42
- return Response.json({ result: 'success' });
43
- });
28
+ # Or public key — read/write access for usage, customers, billing (safe for client-side)
29
+ export DRIP_API_KEY=pk_test_...
44
30
  ```
45
31
 
46
- #### Express
32
+ ### 3. Track usage (one line)
47
33
 
48
34
  ```typescript
49
- import express from 'express';
50
- import { dripMiddleware } from '@drip-sdk/node/express';
51
-
52
- const app = express();
35
+ import { drip } from '@drip-sdk/node';
53
36
 
54
- app.use('/api/paid', dripMiddleware({
55
- meter: 'api_calls',
56
- quantity: 1,
57
- }));
58
-
59
- app.post('/api/paid/generate', (req, res) => {
60
- console.log(`Charged: ${req.drip.charge.charge.amountUsdc} USDC`);
61
- res.json({ success: true });
62
- });
37
+ // Track usage - that's it
38
+ await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });
63
39
  ```
64
40
 
65
- ### Manual Integration
41
+ The `drip` singleton reads `DRIP_API_KEY` from your environment automatically.
66
42
 
67
- For more control, use the SDK directly:
43
+ ### Alternative: Explicit Configuration
68
44
 
69
45
  ```typescript
70
46
  import { Drip } from '@drip-sdk/node';
71
47
 
72
- // Initialize the client
73
- const drip = new Drip({
74
- apiKey: process.env.DRIP_API_KEY!,
75
- });
76
-
77
- // Create a customer
78
- const customer = await drip.createCustomer({
79
- onchainAddress: '0x1234567890abcdef...',
80
- externalCustomerId: 'user_123',
81
- });
48
+ // Auto-reads DRIP_API_KEY from environment
49
+ const drip = new Drip();
82
50
 
83
- // Record usage and charge
84
- const result = await drip.charge({
85
- customerId: customer.id,
86
- meter: 'api_calls',
87
- quantity: 100,
88
- });
51
+ // Or pass config explicitly with a secret key (full access)
52
+ const drip = new Drip({ apiKey: 'sk_test_...' });
89
53
 
90
- console.log(`Charged ${result.charge.amountUsdc} USDC`);
91
- console.log(`TX: ${result.charge.txHash}`);
54
+ // Or with a public key (safe for client-side, limited scope)
55
+ const drip = new Drip({ apiKey: 'pk_test_...' });
92
56
  ```
93
57
 
94
- ## Configuration
58
+ ### Full Example
95
59
 
96
60
  ```typescript
97
- const drip = new Drip({
98
- // Required: Your Drip API key
99
- apiKey: 'drip_live_abc123...',
100
-
101
- // Optional: API base URL (for staging/development)
102
- baseUrl: 'https://api.drip.dev/v1',
103
-
104
- // Optional: Request timeout in milliseconds (default: 30000)
105
- timeout: 30000,
106
- });
107
- ```
108
-
109
- ## API Reference
61
+ import { drip } from '@drip-sdk/node';
62
+
63
+ async function main() {
64
+ // Verify connectivity
65
+ await drip.ping();
66
+
67
+ // Record usage
68
+ await drip.trackUsage({
69
+ customerId: 'customer_123',
70
+ meter: 'llm_tokens',
71
+ quantity: 842,
72
+ metadata: { model: 'gpt-4o-mini' },
73
+ });
110
74
 
111
- ### Customer Management
75
+ // Record an execution lifecycle
76
+ await drip.recordRun({
77
+ customerId: 'customer_123',
78
+ workflow: 'research-agent',
79
+ events: [
80
+ { eventType: 'llm.call', model: 'gpt-4', inputTokens: 500, outputTokens: 1200 },
81
+ { eventType: 'tool.call', name: 'web-search', duration: 1500 },
82
+ ],
83
+ status: 'COMPLETED',
84
+ });
112
85
 
113
- #### Create a Customer
86
+ console.log('Usage + run recorded');
87
+ }
114
88
 
115
- ```typescript
116
- const customer = await drip.createCustomer({
117
- onchainAddress: '0x1234567890abcdef...',
118
- externalCustomerId: 'user_123', // Your internal user ID
119
- metadata: { plan: 'pro' },
120
- });
89
+ main();
121
90
  ```
122
91
 
123
- #### Get a Customer
92
+ **Expected result:**
93
+ - No errors
94
+ - Events appear in your Drip dashboard within seconds
124
95
 
125
- ```typescript
126
- const customer = await drip.getCustomer('cust_abc123');
127
- ```
96
+ ---
128
97
 
129
- #### List Customers
98
+ ## Core Concepts (2-minute mental model)
130
99
 
131
- ```typescript
132
- // List all customers
133
- const { data: customers } = await drip.listCustomers();
100
+ | Concept | Description |
101
+ |---------|-------------|
102
+ | `customerId` | The end user, API key, or account you're attributing usage to |
103
+ | `meter` | What you're measuring (tokens, requests, seconds, rows, etc.) |
104
+ | `quantity` | Numeric usage for that meter |
105
+ | `run` | A single execution or request lifecycle (success / failure / duration) |
134
106
 
135
- // With filters
136
- const { data: activeCustomers } = await drip.listCustomers({
137
- status: 'ACTIVE',
138
- limit: 50,
139
- });
140
- ```
107
+ **Status values:** `PENDING` | `RUNNING` | `COMPLETED` | `FAILED`
141
108
 
142
- #### Get Customer Balance
109
+ **Event schema:** Payloads are schema-flexible. Drip stores events as structured JSON and does not enforce a fixed event taxonomy.
143
110
 
144
- ```typescript
145
- const balance = await drip.getBalance('cust_abc123');
146
- console.log(`Balance: ${balance.balanceUSDC} USDC`);
147
- ```
111
+ Drip is append-only and idempotent-friendly. You can safely retry events.
148
112
 
149
- ### Meters (Usage Types)
113
+ ---
150
114
 
151
- #### List Available Meters
115
+ ## Idempotency Keys
152
116
 
153
- Discover what meter names are valid for charging. Meters are defined by your pricing plans.
154
-
155
- ```typescript
156
- const { data: meters } = await drip.listMeters();
117
+ Every mutating SDK method (`charge`, `trackUsage`, `emitEvent`) accepts an optional `idempotencyKey` parameter. The server uses this key to deduplicate requests — if two requests share the same key, only the first is processed.
157
118
 
158
- console.log('Available meters:');
159
- for (const meter of meters) {
160
- console.log(` ${meter.meter}: $${meter.unitPriceUsd}/unit`);
161
- }
162
- // Output:
163
- // api_calls: $0.001/unit
164
- // tokens: $0.00001/unit
165
- // compute_seconds: $0.01/unit
166
- ```
119
+ `recordRun` generates idempotency keys internally for its batch events (using `externalRunId` when provided, otherwise deterministic keys).
167
120
 
168
- ### Charging & Usage
121
+ ### Auto-generated keys (default)
169
122
 
170
- #### Record Usage and Charge
123
+ When you omit `idempotencyKey`, the SDK generates one automatically. The auto key is:
171
124
 
172
- ```typescript
173
- const result = await drip.charge({
174
- customerId: 'cust_abc123',
175
- meter: 'api_calls',
176
- quantity: 100,
177
- idempotencyKey: 'req_unique_123', // Prevents duplicate charges
178
- metadata: { endpoint: '/v1/chat' },
179
- });
125
+ - **Unique per call** — two separate calls with identical parameters produce different keys (a monotonic counter ensures this).
126
+ - **Stable across retries** — the key is generated once and reused for all retry attempts of that call, so network retries are safely deduplicated.
127
+ - **Deterministic** — no randomness; keys are reproducible for debugging.
180
128
 
181
- if (result.success) {
182
- console.log(`Charge ID: ${result.charge.id}`);
183
- console.log(`Amount: ${result.charge.amountUsdc} USDC`);
184
- console.log(`TX Hash: ${result.charge.txHash}`);
185
- }
186
- ```
129
+ This means you get **free retry safety** with zero configuration.
187
130
 
188
- #### Get Charge Details
131
+ > **Note:** `wrapApiCall` generates a time-based key when no explicit `idempotencyKey` is provided. Pass your own key if you need deterministic deduplication with `wrapApiCall`.
189
132
 
190
- ```typescript
191
- const charge = await drip.getCharge('chg_abc123');
192
- console.log(`Status: ${charge.status}`);
193
- ```
133
+ ### When to pass explicit keys
194
134
 
195
- #### List Charges
135
+ Pass your own `idempotencyKey` when you need **application-level deduplication** — e.g., to guarantee that a specific business operation is billed exactly once, even across process restarts:
196
136
 
197
137
  ```typescript
198
- // List all charges
199
- const { data: charges } = await drip.listCharges();
200
-
201
- // Filter by customer and status
202
- const { data: customerCharges } = await drip.listCharges({
203
- customerId: 'cust_abc123',
204
- status: 'CONFIRMED',
205
- limit: 50,
138
+ await drip.charge({
139
+ customerId: 'cust_123',
140
+ meter: 'api_calls',
141
+ quantity: 1,
142
+ idempotencyKey: `order_${orderId}_charge`, // your business-level key
206
143
  });
207
144
  ```
208
145
 
209
- #### Check Charge Status
146
+ Common patterns:
147
+ - `order_${orderId}` — one charge per order
148
+ - `run_${runId}_step_${stepIndex}` — one charge per pipeline step
149
+ - `invoice_${invoiceId}` — one charge per invoice
210
150
 
211
- ```typescript
212
- const status = await drip.getChargeStatus('chg_abc123');
213
- if (status.status === 'CONFIRMED') {
214
- console.log('Charge confirmed on-chain!');
215
- }
216
- ```
151
+ ### StreamMeter
217
152
 
218
- ### Streaming Meter
153
+ `StreamMeter` also auto-generates idempotency keys per flush. If you provide an `idempotencyKey` in the options, each flush appends a counter (`_flush_0`, `_flush_1`, etc.) to keep multi-flush scenarios safe.
219
154
 
220
- For LLM token streaming and other high-frequency metering scenarios, use the streaming meter to accumulate usage locally and charge once at the end:
155
+ ---
221
156
 
222
- ```typescript
223
- import { Drip } from '@drip-sdk/node';
157
+ ## API Key Types
224
158
 
225
- const drip = new Drip({ apiKey: process.env.DRIP_API_KEY! });
159
+ Drip issues two key types per API key pair. Each has different access scopes:
226
160
 
227
- // Create a stream meter for a customer
228
- const meter = drip.createStreamMeter({
229
- customerId: 'cust_abc123',
230
- meter: 'tokens',
231
- });
161
+ | Key Type | Prefix | Access | Use In |
162
+ |----------|--------|--------|--------|
163
+ | **Secret Key** | `sk_live_` / `sk_test_` | Full API access (all endpoints) | Server-side only |
164
+ | **Public Key** | `pk_live_` / `pk_test_` | Usage tracking, customers, billing, analytics, sessions | Client-side safe |
232
165
 
233
- // Stream from your LLM provider
234
- const stream = await openai.chat.completions.create({
235
- model: 'gpt-4',
236
- messages: [{ role: 'user', content: 'Hello!' }],
237
- stream: true,
238
- });
166
+ ### What public keys **can** access
167
+ - Usage tracking (`trackUsage`, `recordRun`, `startRun`, `emitEvent`, etc.)
168
+ - Customer management (`createCustomer`, `getCustomer`, `listCustomers`)
169
+ - Billing & charges (`charge`, `getBalance`, `listCharges`, etc.)
170
+ - Pricing plans, sessions, analytics, usage caps, refunds
239
171
 
240
- // Accumulate tokens as they stream
241
- for await (const chunk of stream) {
242
- const tokens = chunk.usage?.completion_tokens ?? 1;
243
- meter.add(tokens); // Accumulates locally, no API call
244
- yield chunk;
245
- }
246
-
247
- // Single charge at end of stream
248
- const result = await meter.flush();
249
- console.log(`Charged ${result.charge.amountUsdc} USDC for ${result.quantity} tokens`);
250
- ```
172
+ ### What public keys **cannot** access (secret key required)
173
+ - Webhook management (`createWebhook`, `listWebhooks`, `deleteWebhook`, etc.)
174
+ - API key management (create, rotate, revoke keys)
175
+ - Feature flag management
251
176
 
252
- #### Stream Meter Options
177
+ The SDK detects your key type automatically and will throw a `DripError` with code `PUBLIC_KEY_NOT_ALLOWED` (HTTP 403) if you attempt a secret-key-only operation with a public key.
253
178
 
254
179
  ```typescript
255
- const meter = drip.createStreamMeter({
256
- customerId: 'cust_abc123',
257
- meter: 'tokens',
180
+ const drip = new Drip({ apiKey: 'pk_test_...' });
181
+ console.log(drip.keyType); // 'public'
258
182
 
259
- // Optional: Custom idempotency key
260
- idempotencyKey: 'stream_req_123',
183
+ // This works fine
184
+ await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });
261
185
 
262
- // Optional: Metadata attached to the charge
263
- metadata: { model: 'gpt-4', endpoint: '/v1/chat' },
264
-
265
- // Optional: Auto-flush when threshold reached
266
- flushThreshold: 10000, // Flush every 10k tokens
267
-
268
- // Optional: Callback on each add
269
- onAdd: (quantity, total) => console.log(`Added ${quantity}, total: ${total}`),
270
- });
186
+ // This throws DripError(403, 'PUBLIC_KEY_NOT_ALLOWED')
187
+ await drip.createWebhook({ url: '...', events: ['charge.succeeded'] });
271
188
  ```
272
189
 
273
- #### Handling Partial Failures
190
+ ---
274
191
 
275
- If the stream fails mid-way, you can still charge for what was delivered:
276
-
277
- ```typescript
278
- const meter = drip.createStreamMeter({
279
- customerId: 'cust_abc123',
280
- meter: 'tokens',
281
- });
282
-
283
- try {
284
- for await (const chunk of stream) {
285
- meter.add(chunk.tokens);
286
- yield chunk;
287
- }
288
- await meter.flush();
289
- } catch (error) {
290
- // Charge for tokens delivered before failure
291
- if (meter.total > 0) {
292
- await meter.flush();
293
- }
294
- throw error;
295
- }
296
- ```
192
+ ## SDK Variants
297
193
 
298
- #### Multiple Meters in One Request
194
+ | Variant | Description |
195
+ |---------|-------------|
196
+ | **Core SDK** (recommended for pilots) | Usage tracking + execution logging only |
197
+ | **Full SDK** | Includes billing, balances, and workflows (for later stages) |
299
198
 
300
- ```typescript
301
- const tokenMeter = drip.createStreamMeter({ customerId, meter: 'tokens' });
302
- const toolMeter = drip.createStreamMeter({ customerId, meter: 'tool_calls' });
303
-
304
- for await (const chunk of agentStream) {
305
- if (chunk.type === 'token') {
306
- tokenMeter.add(1);
307
- } else if (chunk.type === 'tool_call') {
308
- toolMeter.add(1);
309
- }
310
- yield chunk;
311
- }
199
+ ---
312
200
 
313
- // Flush all meters
314
- await Promise.all([tokenMeter.flush(), toolMeter.flush()]);
315
- ```
201
+ ## Core SDK Methods
316
202
 
317
- ### Run Tracking (Simplified API)
203
+ | Method | Description |
204
+ |--------|-------------|
205
+ | `ping()` | Verify API connection |
206
+ | `createCustomer(params)` | Create a customer |
207
+ | `getCustomer(customerId)` | Get customer details |
208
+ | `listCustomers(options)` | List all customers |
209
+ | `trackUsage(params)` | Record metered usage |
210
+ | `recordRun(params)` | Log complete agent run (simplified) |
211
+ | `startRun(params)` | Start execution trace |
212
+ | `emitEvent(params)` | Log event within run |
213
+ | `emitEventsBatch(params)` | Batch log events |
214
+ | `endRun(runId, params)` | Complete execution trace |
215
+ | `getRunTimeline(runId)` | Get execution timeline |
318
216
 
319
- Track agent executions with a single API call instead of multiple separate calls.
217
+ ---
320
218
 
321
- #### Record a Complete Run
219
+ ## Who This Is For
322
220
 
323
- The `recordRun()` method combines workflow creation, run tracking, event emission, and completion into one call:
221
+ - AI agents (token metering, tool calls, execution traces)
222
+ - API companies (per-request billing, endpoint attribution)
223
+ - RPC providers (multi-chain call tracking)
224
+ - Cloud/infra (compute seconds, storage, bandwidth)
324
225
 
325
- ```typescript
326
- // Before: 4+ separate API calls
327
- const workflow = await drip.createWorkflow({ name: 'My Agent', slug: 'my_agent' });
328
- const run = await drip.startRun({ customerId, workflowId: workflow.id });
329
- await drip.emitEvent({ runId: run.id, eventType: 'step1', ... });
330
- await drip.emitEvent({ runId: run.id, eventType: 'step2', ... });
331
- await drip.endRun(run.id, { status: 'COMPLETED' });
332
-
333
- // After: 1 call with recordRun()
334
- const result = await drip.recordRun({
335
- customerId: 'cust_123',
336
- workflow: 'my_agent', // Auto-creates workflow if it doesn't exist
337
- events: [
338
- { eventType: 'agent.start', description: 'Started processing' },
339
- { eventType: 'tool.ocr', quantity: 3, units: 'pages', costUnits: 0.15 },
340
- { eventType: 'tool.validate', quantity: 1, costUnits: 0.05 },
341
- { eventType: 'agent.complete', description: 'Finished successfully' },
342
- ],
343
- status: 'COMPLETED',
344
- });
226
+ ---
345
227
 
346
- console.log(result.summary);
347
- // Output: "✓ My Agent: 4 events recorded (250ms)"
348
- ```
228
+ ## Full SDK (Billing, Webhooks, Integrations)
349
229
 
350
- #### Record a Failed Run
230
+ For billing, webhooks, middleware, and advanced features:
351
231
 
352
232
  ```typescript
353
- const result = await drip.recordRun({
354
- customerId: 'cust_123',
355
- workflow: 'prescription_intake',
356
- events: [
357
- { eventType: 'agent.start', description: 'Started processing' },
358
- { eventType: 'error', description: 'OCR failed: image too blurry' },
359
- ],
360
- status: 'FAILED',
361
- errorMessage: 'OCR processing failed',
362
- errorCode: 'OCR_QUALITY_ERROR',
363
- });
364
-
365
- console.log(result.summary);
366
- // Output: "✗ Prescription Intake: 2 events recorded (150ms)"
367
- ```
368
-
369
- ### Webhooks
370
-
371
- #### Create a Webhook
372
-
373
- ```typescript
374
- const webhook = await drip.createWebhook({
375
- url: 'https://api.yourapp.com/webhooks/drip',
376
- events: ['charge.succeeded', 'charge.failed', 'customer.balance.low'],
377
- description: 'Main webhook endpoint',
378
- });
379
-
380
- // IMPORTANT: Save the secret securely!
381
- console.log(`Webhook secret: ${webhook.secret}`);
382
- ```
383
-
384
- #### List Webhooks
385
-
386
- ```typescript
387
- const { data: webhooks } = await drip.listWebhooks();
388
- webhooks.forEach((wh) => {
389
- console.log(`${wh.url}: ${wh.stats?.successfulDeliveries} successful`);
390
- });
391
- ```
392
-
393
- #### Delete a Webhook
394
-
395
- ```typescript
396
- await drip.deleteWebhook('wh_abc123');
397
- ```
398
-
399
- #### Verify Webhook Signatures
400
-
401
- ```typescript
402
- import express from 'express';
403
233
  import { Drip } from '@drip-sdk/node';
404
-
405
- const app = express();
406
-
407
- app.post(
408
- '/webhooks/drip',
409
- express.raw({ type: 'application/json' }),
410
- (req, res) => {
411
- const isValid = Drip.verifyWebhookSignature(
412
- req.body.toString(),
413
- req.headers['x-drip-signature'] as string,
414
- process.env.DRIP_WEBHOOK_SECRET!,
415
- );
416
-
417
- if (!isValid) {
418
- return res.status(401).send('Invalid signature');
419
- }
420
-
421
- const event = JSON.parse(req.body.toString());
422
-
423
- switch (event.type) {
424
- case 'charge.succeeded':
425
- console.log('Charge succeeded:', event.data.charge_id);
426
- break;
427
- case 'charge.failed':
428
- console.log('Charge failed:', event.data.failure_reason);
429
- break;
430
- case 'customer.balance.low':
431
- console.log('Low balance alert for:', event.data.customer_id);
432
- break;
433
- }
434
-
435
- res.status(200).send('OK');
436
- },
437
- );
438
234
  ```
439
235
 
440
- ### Available Webhook Events
441
-
442
- | Event | Description |
443
- | ---------------------------- | ------------------------------------ |
444
- | `charge.succeeded` | Charge confirmed on-chain |
445
- | `charge.failed` | Charge failed |
446
- | `customer.balance.low` | Customer balance below threshold |
447
- | `customer.deposit.confirmed` | Deposit confirmed on-chain |
448
- | `customer.withdraw.confirmed`| Withdrawal confirmed |
449
- | `customer.usage_cap.reached` | Usage cap hit |
450
- | `customer.created` | New customer created |
451
- | `usage.recorded` | Usage event recorded |
452
- | `transaction.created` | Transaction initiated |
453
- | `transaction.confirmed` | Transaction confirmed on-chain |
454
- | `transaction.failed` | Transaction failed |
455
-
456
- ## TypeScript Usage
236
+ See **[FULL_SDK.md](./FULL_SDK.md)** for complete documentation.
457
237
 
458
- The SDK is written in TypeScript and includes full type definitions.
459
-
460
- ```typescript
461
- import {
462
- Drip,
463
- DripConfig,
464
- DripError,
465
- Customer,
466
- Charge,
467
- ChargeResult,
468
- ChargeStatus,
469
- Webhook,
470
- WebhookEventType,
471
- StreamMeter,
472
- StreamMeterOptions,
473
- } from '@drip-sdk/node';
474
-
475
- // All types are available for use
476
- const config: DripConfig = {
477
- apiKey: process.env.DRIP_API_KEY!,
478
- };
479
-
480
- const drip = new Drip(config);
481
-
482
- // Type-safe responses
483
- const customer: Customer = await drip.getCustomer('cust_abc123');
484
- const result: ChargeResult = await drip.charge({
485
- customerId: customer.id,
486
- meter: 'api_calls',
487
- quantity: 100,
488
- });
489
- ```
238
+ ---
490
239
 
491
240
  ## Error Handling
492
241
 
493
- The SDK throws `DripError` for API errors:
494
-
495
242
  ```typescript
496
243
  import { Drip, DripError } from '@drip-sdk/node';
497
244
 
498
245
  try {
499
- const result = await drip.charge({
500
- customerId: 'cust_abc123',
501
- meter: 'api_calls',
502
- quantity: 100,
503
- });
246
+ await drip.trackUsage({ ... });
504
247
  } catch (error) {
505
248
  if (error instanceof DripError) {
506
- console.error(`API Error: ${error.message}`);
507
- console.error(`Status Code: ${error.statusCode}`);
508
- console.error(`Error Code: ${error.code}`);
509
-
510
- switch (error.code) {
511
- case 'INSUFFICIENT_BALANCE':
512
- // Handle low balance
513
- break;
514
- case 'CUSTOMER_NOT_FOUND':
515
- // Handle missing customer
516
- break;
517
- case 'RATE_LIMITED':
518
- // Handle rate limiting
519
- break;
520
- }
249
+ console.error(`Error: ${error.message} (${error.code})`);
521
250
  }
522
251
  }
523
252
  ```
524
253
 
525
- ### Common Error Codes
526
-
527
- | Code | Description |
528
- | ---------------------- | ---------------------------------------- |
529
- | `INSUFFICIENT_BALANCE` | Customer doesn't have enough balance |
530
- | `CUSTOMER_NOT_FOUND` | Customer ID doesn't exist |
531
- | `DUPLICATE_CUSTOMER` | Customer already exists |
532
- | `INVALID_API_KEY` | API key is invalid or revoked |
533
- | `RATE_LIMITED` | Too many requests |
534
- | `TIMEOUT` | Request timed out |
535
-
536
- ## Idempotency
537
-
538
- Use idempotency keys to safely retry requests:
539
-
540
- ```typescript
541
- const result = await drip.charge({
542
- customerId: 'cust_abc123',
543
- meter: 'api_calls',
544
- quantity: 100,
545
- idempotencyKey: `req_${requestId}`, // Unique per request
546
- });
547
-
548
- // Retrying with the same key returns the original result
549
- const retry = await drip.charge({
550
- customerId: 'cust_abc123',
551
- meter: 'api_calls',
552
- quantity: 100,
553
- idempotencyKey: `req_${requestId}`, // Same key = same result
554
- });
555
- ```
556
-
557
- ## CommonJS Usage
558
-
559
- The SDK supports both ESM and CommonJS:
560
-
561
- ```javascript
562
- // ESM
563
- import { Drip } from '@drip-sdk/node';
564
-
565
- // CommonJS
566
- const { Drip } = require('@drip-sdk/node');
567
- ```
254
+ ---
568
255
 
569
256
  ## Requirements
570
257
 
571
258
  - Node.js 18.0.0 or higher
572
- - Native `fetch` support (included in Node.js 18+)
573
-
574
- ## Middleware Reference (withDrip)
575
-
576
- ### Configuration Options
577
-
578
- | Option | Type | Default | Description |
579
- |--------|------|---------|-------------|
580
- | `meter` | `string` | **required** | Usage meter to charge (must match pricing plan) |
581
- | `quantity` | `number \| (req) => number` | **required** | Quantity to charge (static or dynamic) |
582
- | `apiKey` | `string` | `DRIP_API_KEY` | Drip API key |
583
- | `baseUrl` | `string` | `DRIP_API_URL` | Drip API base URL |
584
- | `customerResolver` | `'header' \| 'query' \| function` | `'header'` | How to identify customers |
585
- | `skipInDevelopment` | `boolean` | `false` | Skip charging in dev mode |
586
- | `metadata` | `object \| function` | `undefined` | Custom metadata for charges |
587
- | `onCharge` | `function` | `undefined` | Callback after successful charge |
588
- | `onError` | `function` | `undefined` | Custom error handler |
589
-
590
- ### How It Works
591
-
592
- ```
593
- ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
594
- │ Your API │ │ withDrip │ │ Drip Backend │
595
- │ (Next/Express)│───▶│ Middleware │───▶│ API │
596
- └─────────────────┘ └──────────────────┘ └─────────────────┘
597
- │ │ │
598
- ▼ ▼ ▼
599
- 1. Request 2. Resolve 3. Check balance
600
- arrives customer & charge
601
- │ │ │
602
- ▼ ▼ ▼
603
- 6. Response 5. Pass to 4. Return result
604
- returned handler or 402
605
- ```
606
-
607
- ### x402 Payment Flow
608
-
609
- When a customer has insufficient balance, the middleware returns `402 Payment Required`:
610
-
611
- ```
612
- HTTP/1.1 402 Payment Required
613
- X-Payment-Required: true
614
- X-Payment-Amount: 0.01
615
- X-Payment-Recipient: 0x...
616
- X-Payment-Usage-Id: 0x...
617
- X-Payment-Expires: 1704110400
618
- X-Payment-Nonce: abc123
619
-
620
- {
621
- "error": "Payment required",
622
- "code": "PAYMENT_REQUIRED",
623
- "paymentRequest": { ... },
624
- "instructions": {
625
- "step1": "Sign the payment request with your session key using EIP-712",
626
- "step2": "Retry the request with X-Payment-* headers"
627
- }
628
- }
629
- ```
630
-
631
- ### Advanced Usage
632
-
633
- #### Dynamic Quantity
634
-
635
- ```typescript
636
- export const POST = withDrip({
637
- meter: 'tokens',
638
- quantity: async (req) => {
639
- const body = await req.json();
640
- return body.maxTokens ?? 100;
641
- },
642
- }, handler);
643
- ```
644
-
645
- #### Custom Customer Resolution
646
-
647
- ```typescript
648
- export const POST = withDrip({
649
- meter: 'api_calls',
650
- quantity: 1,
651
- customerResolver: (req) => {
652
- const token = req.headers.get('authorization')?.split(' ')[1];
653
- return decodeJWT(token).customerId;
654
- },
655
- }, handler);
656
- ```
657
-
658
- #### Factory Pattern
659
-
660
- ```typescript
661
- // lib/drip.ts
662
- import { createWithDrip } from '@drip-sdk/node/next';
663
-
664
- export const withDrip = createWithDrip({
665
- apiKey: process.env.DRIP_API_KEY,
666
- baseUrl: process.env.DRIP_API_URL,
667
- });
668
-
669
- // app/api/generate/route.ts
670
- import { withDrip } from '@/lib/drip';
671
- export const POST = withDrip({ meter: 'api_calls', quantity: 1 }, handler);
672
- ```
673
-
674
- ### What's Included vs. Missing
675
-
676
- | Feature | Status | Description |
677
- |---------|--------|-------------|
678
- | Next.js App Router | ✅ | `withDrip` wrapper |
679
- | Express Middleware | ✅ | `dripMiddleware` |
680
- | x402 Payment Flow | ✅ | Automatic 402 handling |
681
- | Dynamic Quantity | ✅ | Function-based pricing |
682
- | Customer Resolution | ✅ | Header, query, or custom |
683
- | Idempotency | ✅ | Built-in or custom keys |
684
- | Dev Mode Skip | ✅ | Skip in development |
685
- | Metadata | ✅ | Attach to charges |
686
- | TypeScript | ✅ | Full type definitions |
687
- | Streaming Meter | ✅ | For LLM token streams |
688
-
689
- ## Contributing
690
-
691
- Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
692
-
693
- ## License
694
-
695
- MIT - see [LICENSE](./LICENSE)
696
259
 
697
260
  ## Links
698
261
 
699
- - [GitHub Repository](https://github.com/MichaelLevin5908/drip-sdk)
700
- - [Issue Tracker](https://github.com/MichaelLevin5908/drip-sdk/issues)
701
- - [npm Package](https://www.npmjs.com/package/@drip-sdk/node)
702
- - [Documentation](https://docs.drip.dev)
262
+ - [Full SDK Documentation](./FULL_SDK.md)
263
+ - [API Documentation](https://drippay.dev/api-reference)
264
+ - [npm](https://www.npmjs.com/package/@drip-sdk/node)