@drip-sdk/node 1.0.2 → 1.0.3

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,603 +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();
53
-
54
- app.use('/api/paid', dripMiddleware({
55
- meter: 'api_calls',
56
- quantity: 1,
57
- }));
35
+ import { drip } from '@drip-sdk/node';
58
36
 
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
- });
48
+ // Auto-reads DRIP_API_KEY from environment
49
+ const drip = new Drip();
76
50
 
77
- // Create a customer
78
- const customer = await drip.createCustomer({
79
- onchainAddress: '0x1234567890abcdef...',
80
- externalCustomerId: 'user_123',
81
- });
51
+ // Or pass config explicitly with a secret key (full access)
52
+ const drip = new Drip({ apiKey: 'sk_test_...' });
82
53
 
83
- // Record usage and charge
84
- const result = await drip.charge({
85
- customerId: customer.id,
86
- meter: 'api_calls',
87
- quantity: 100,
88
- });
89
-
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...',
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
+ });
100
74
 
101
- // Optional: API base URL (for staging/development)
102
- baseUrl: 'https://api.drip.dev/v1',
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
+ });
103
85
 
104
- // Optional: Request timeout in milliseconds (default: 30000)
105
- timeout: 30000,
106
- });
86
+ console.log('Usage + run recorded');
87
+ }
88
+
89
+ main();
107
90
  ```
108
91
 
109
- ## API Reference
92
+ **Expected result:**
93
+ - No errors
94
+ - Events appear in your Drip dashboard within seconds
110
95
 
111
- ### Customer Management
96
+ ---
112
97
 
113
- #### Create a Customer
98
+ ## Core Concepts (2-minute mental model)
114
99
 
115
- ```typescript
116
- const customer = await drip.createCustomer({
117
- onchainAddress: '0x1234567890abcdef...',
118
- externalCustomerId: 'user_123', // Your internal user ID
119
- metadata: { plan: 'pro' },
120
- });
121
- ```
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) |
122
106
 
123
- #### Get a Customer
107
+ **Status values:** `PENDING` | `RUNNING` | `COMPLETED` | `FAILED`
124
108
 
125
- ```typescript
126
- const customer = await drip.getCustomer('cust_abc123');
127
- ```
109
+ **Event schema:** Payloads are schema-flexible. Drip stores events as structured JSON and does not enforce a fixed event taxonomy.
128
110
 
129
- #### List Customers
111
+ Drip is append-only and idempotent-friendly. You can safely retry events.
130
112
 
131
- ```typescript
132
- // List all customers
133
- const { data: customers } = await drip.listCustomers();
113
+ ---
134
114
 
135
- // With filters
136
- const { data: activeCustomers } = await drip.listCustomers({
137
- status: 'ACTIVE',
138
- limit: 50,
139
- });
140
- ```
115
+ ## Idempotency Keys
141
116
 
142
- #### Get Customer Balance
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.
143
118
 
144
- ```typescript
145
- const balance = await drip.getBalance('cust_abc123');
146
- console.log(`Balance: ${balance.balanceUSDC} USDC`);
147
- ```
119
+ `recordRun` generates idempotency keys internally for its batch events (using `externalRunId` when provided, otherwise deterministic keys).
148
120
 
149
- ### Meters (Usage Types)
121
+ ### Auto-generated keys (default)
150
122
 
151
- #### List Available Meters
123
+ When you omit `idempotencyKey`, the SDK generates one automatically. The auto key is:
152
124
 
153
- Discover what meter names are valid for charging. Meters are defined by your pricing plans.
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.
154
128
 
155
- ```typescript
156
- const { data: meters } = await drip.listMeters();
129
+ This means you get **free retry safety** with zero configuration.
157
130
 
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
- ```
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`.
167
132
 
168
- ### Charging & Usage
133
+ ### When to pass explicit keys
169
134
 
170
- #### Record Usage and Charge
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:
171
136
 
172
137
  ```typescript
173
- const result = await drip.charge({
174
- customerId: 'cust_abc123',
138
+ await drip.charge({
139
+ customerId: 'cust_123',
175
140
  meter: 'api_calls',
176
- quantity: 100,
177
- idempotencyKey: 'req_unique_123', // Prevents duplicate charges
178
- metadata: { endpoint: '/v1/chat' },
141
+ quantity: 1,
142
+ idempotencyKey: `order_${orderId}_charge`, // your business-level key
179
143
  });
180
-
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
144
  ```
187
145
 
188
- #### Get Charge Details
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
189
150
 
190
- ```typescript
191
- const charge = await drip.getCharge('chg_abc123');
192
- console.log(`Status: ${charge.status}`);
193
- ```
151
+ ### StreamMeter
194
152
 
195
- #### List Charges
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.
196
154
 
197
- ```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,
206
- });
207
- ```
155
+ ---
208
156
 
209
- #### Check Charge Status
157
+ ## API Key Types
210
158
 
211
- ```typescript
212
- const status = await drip.getChargeStatus('chg_abc123');
213
- if (status.status === 'CONFIRMED') {
214
- console.log('Charge confirmed on-chain!');
215
- }
216
- ```
159
+ Drip issues two key types per API key pair. Each has different access scopes:
217
160
 
218
- ### Run Tracking (Simplified API)
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 |
219
165
 
220
- Track agent executions with a single API call instead of multiple separate calls.
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
221
171
 
222
- #### Record a Complete Run
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
223
176
 
224
- The `recordRun()` method combines workflow creation, run tracking, event emission, and completion into one call:
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.
225
178
 
226
179
  ```typescript
227
- // Before: 4+ separate API calls
228
- const workflow = await drip.createWorkflow({ name: 'My Agent', slug: 'my_agent' });
229
- const run = await drip.startRun({ customerId, workflowId: workflow.id });
230
- await drip.emitEvent({ runId: run.id, eventType: 'step1', ... });
231
- await drip.emitEvent({ runId: run.id, eventType: 'step2', ... });
232
- await drip.endRun(run.id, { status: 'COMPLETED' });
233
-
234
- // After: 1 call with recordRun()
235
- const result = await drip.recordRun({
236
- customerId: 'cust_123',
237
- workflow: 'my_agent', // Auto-creates workflow if it doesn't exist
238
- events: [
239
- { eventType: 'agent.start', description: 'Started processing' },
240
- { eventType: 'tool.ocr', quantity: 3, units: 'pages', costUnits: 0.15 },
241
- { eventType: 'tool.validate', quantity: 1, costUnits: 0.05 },
242
- { eventType: 'agent.complete', description: 'Finished successfully' },
243
- ],
244
- status: 'COMPLETED',
245
- });
180
+ const drip = new Drip({ apiKey: 'pk_test_...' });
181
+ console.log(drip.keyType); // 'public'
182
+
183
+ // This works fine
184
+ await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });
246
185
 
247
- console.log(result.summary);
248
- // Output: "✓ My Agent: 4 events recorded (250ms)"
186
+ // This throws DripError(403, 'PUBLIC_KEY_NOT_ALLOWED')
187
+ await drip.createWebhook({ url: '...', events: ['charge.succeeded'] });
249
188
  ```
250
189
 
251
- #### Record a Failed Run
190
+ ---
252
191
 
253
- ```typescript
254
- const result = await drip.recordRun({
255
- customerId: 'cust_123',
256
- workflow: 'prescription_intake',
257
- events: [
258
- { eventType: 'agent.start', description: 'Started processing' },
259
- { eventType: 'error', description: 'OCR failed: image too blurry' },
260
- ],
261
- status: 'FAILED',
262
- errorMessage: 'OCR processing failed',
263
- errorCode: 'OCR_QUALITY_ERROR',
264
- });
192
+ ## SDK Variants
265
193
 
266
- console.log(result.summary);
267
- // Output: "✗ Prescription Intake: 2 events recorded (150ms)"
268
- ```
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) |
269
198
 
270
- ### Webhooks
199
+ ---
271
200
 
272
- #### Create a Webhook
201
+ ## Core SDK Methods
273
202
 
274
- ```typescript
275
- const webhook = await drip.createWebhook({
276
- url: 'https://api.yourapp.com/webhooks/drip',
277
- events: ['charge.succeeded', 'charge.failed', 'customer.balance.low'],
278
- description: 'Main webhook endpoint',
279
- });
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 |
280
216
 
281
- // IMPORTANT: Save the secret securely!
282
- console.log(`Webhook secret: ${webhook.secret}`);
283
- ```
217
+ ---
284
218
 
285
- #### List Webhooks
219
+ ## Who This Is For
286
220
 
287
- ```typescript
288
- const { data: webhooks } = await drip.listWebhooks();
289
- webhooks.forEach((wh) => {
290
- console.log(`${wh.url}: ${wh.stats?.successfulDeliveries} successful`);
291
- });
292
- ```
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)
293
225
 
294
- #### Delete a Webhook
226
+ ---
295
227
 
296
- ```typescript
297
- await drip.deleteWebhook('wh_abc123');
298
- ```
228
+ ## Full SDK (Billing, Webhooks, Integrations)
299
229
 
300
- #### Verify Webhook Signatures
230
+ For billing, webhooks, middleware, and advanced features:
301
231
 
302
232
  ```typescript
303
- import express from 'express';
304
233
  import { Drip } from '@drip-sdk/node';
305
-
306
- const app = express();
307
-
308
- app.post(
309
- '/webhooks/drip',
310
- express.raw({ type: 'application/json' }),
311
- (req, res) => {
312
- const isValid = Drip.verifyWebhookSignature(
313
- req.body.toString(),
314
- req.headers['x-drip-signature'] as string,
315
- process.env.DRIP_WEBHOOK_SECRET!,
316
- );
317
-
318
- if (!isValid) {
319
- return res.status(401).send('Invalid signature');
320
- }
321
-
322
- const event = JSON.parse(req.body.toString());
323
-
324
- switch (event.type) {
325
- case 'charge.succeeded':
326
- console.log('Charge succeeded:', event.data.charge_id);
327
- break;
328
- case 'charge.failed':
329
- console.log('Charge failed:', event.data.failure_reason);
330
- break;
331
- case 'customer.balance.low':
332
- console.log('Low balance alert for:', event.data.customer_id);
333
- break;
334
- }
335
-
336
- res.status(200).send('OK');
337
- },
338
- );
339
234
  ```
340
235
 
341
- ### Available Webhook Events
342
-
343
- | Event | Description |
344
- | ---------------------------- | ------------------------------------ |
345
- | `charge.succeeded` | Charge confirmed on-chain |
346
- | `charge.failed` | Charge failed |
347
- | `customer.balance.low` | Customer balance below threshold |
348
- | `customer.deposit.confirmed` | Deposit confirmed on-chain |
349
- | `customer.withdraw.confirmed`| Withdrawal confirmed |
350
- | `customer.usage_cap.reached` | Usage cap hit |
351
- | `customer.created` | New customer created |
352
- | `usage.recorded` | Usage event recorded |
353
- | `transaction.created` | Transaction initiated |
354
- | `transaction.confirmed` | Transaction confirmed on-chain |
355
- | `transaction.failed` | Transaction failed |
356
-
357
- ## TypeScript Usage
236
+ See **[FULL_SDK.md](./FULL_SDK.md)** for complete documentation.
358
237
 
359
- The SDK is written in TypeScript and includes full type definitions.
360
-
361
- ```typescript
362
- import {
363
- Drip,
364
- DripConfig,
365
- DripError,
366
- Customer,
367
- Charge,
368
- ChargeResult,
369
- ChargeStatus,
370
- Webhook,
371
- WebhookEventType,
372
- } from '@drip-sdk/node';
373
-
374
- // All types are available for use
375
- const config: DripConfig = {
376
- apiKey: process.env.DRIP_API_KEY!,
377
- };
378
-
379
- const drip = new Drip(config);
380
-
381
- // Type-safe responses
382
- const customer: Customer = await drip.getCustomer('cust_abc123');
383
- const result: ChargeResult = await drip.charge({
384
- customerId: customer.id,
385
- meter: 'api_calls',
386
- quantity: 100,
387
- });
388
- ```
238
+ ---
389
239
 
390
240
  ## Error Handling
391
241
 
392
- The SDK throws `DripError` for API errors:
393
-
394
242
  ```typescript
395
243
  import { Drip, DripError } from '@drip-sdk/node';
396
244
 
397
245
  try {
398
- const result = await drip.charge({
399
- customerId: 'cust_abc123',
400
- meter: 'api_calls',
401
- quantity: 100,
402
- });
246
+ await drip.trackUsage({ ... });
403
247
  } catch (error) {
404
248
  if (error instanceof DripError) {
405
- console.error(`API Error: ${error.message}`);
406
- console.error(`Status Code: ${error.statusCode}`);
407
- console.error(`Error Code: ${error.code}`);
408
-
409
- switch (error.code) {
410
- case 'INSUFFICIENT_BALANCE':
411
- // Handle low balance
412
- break;
413
- case 'CUSTOMER_NOT_FOUND':
414
- // Handle missing customer
415
- break;
416
- case 'RATE_LIMITED':
417
- // Handle rate limiting
418
- break;
419
- }
249
+ console.error(`Error: ${error.message} (${error.code})`);
420
250
  }
421
251
  }
422
252
  ```
423
253
 
424
- ### Common Error Codes
425
-
426
- | Code | Description |
427
- | ---------------------- | ---------------------------------------- |
428
- | `INSUFFICIENT_BALANCE` | Customer doesn't have enough balance |
429
- | `CUSTOMER_NOT_FOUND` | Customer ID doesn't exist |
430
- | `DUPLICATE_CUSTOMER` | Customer already exists |
431
- | `INVALID_API_KEY` | API key is invalid or revoked |
432
- | `RATE_LIMITED` | Too many requests |
433
- | `TIMEOUT` | Request timed out |
434
-
435
- ## Idempotency
436
-
437
- Use idempotency keys to safely retry requests:
438
-
439
- ```typescript
440
- const result = await drip.charge({
441
- customerId: 'cust_abc123',
442
- meter: 'api_calls',
443
- quantity: 100,
444
- idempotencyKey: `req_${requestId}`, // Unique per request
445
- });
446
-
447
- // Retrying with the same key returns the original result
448
- const retry = await drip.charge({
449
- customerId: 'cust_abc123',
450
- meter: 'api_calls',
451
- quantity: 100,
452
- idempotencyKey: `req_${requestId}`, // Same key = same result
453
- });
454
- ```
455
-
456
- ## CommonJS Usage
457
-
458
- The SDK supports both ESM and CommonJS:
459
-
460
- ```javascript
461
- // ESM
462
- import { Drip } from '@drip-sdk/node';
463
-
464
- // CommonJS
465
- const { Drip } = require('@drip-sdk/node');
466
- ```
254
+ ---
467
255
 
468
256
  ## Requirements
469
257
 
470
258
  - Node.js 18.0.0 or higher
471
- - Native `fetch` support (included in Node.js 18+)
472
-
473
- ## Middleware Reference (withDrip)
474
-
475
- ### Configuration Options
476
-
477
- | Option | Type | Default | Description |
478
- |--------|------|---------|-------------|
479
- | `meter` | `string` | **required** | Usage meter to charge (must match pricing plan) |
480
- | `quantity` | `number \| (req) => number` | **required** | Quantity to charge (static or dynamic) |
481
- | `apiKey` | `string` | `DRIP_API_KEY` | Drip API key |
482
- | `baseUrl` | `string` | `DRIP_API_URL` | Drip API base URL |
483
- | `customerResolver` | `'header' \| 'query' \| function` | `'header'` | How to identify customers |
484
- | `skipInDevelopment` | `boolean` | `false` | Skip charging in dev mode |
485
- | `metadata` | `object \| function` | `undefined` | Custom metadata for charges |
486
- | `onCharge` | `function` | `undefined` | Callback after successful charge |
487
- | `onError` | `function` | `undefined` | Custom error handler |
488
-
489
- ### How It Works
490
-
491
- ```
492
- ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
493
- │ Your API │ │ withDrip │ │ Drip Backend │
494
- │ (Next/Express)│───▶│ Middleware │───▶│ API │
495
- └─────────────────┘ └──────────────────┘ └─────────────────┘
496
- │ │ │
497
- ▼ ▼ ▼
498
- 1. Request 2. Resolve 3. Check balance
499
- arrives customer & charge
500
- │ │ │
501
- ▼ ▼ ▼
502
- 6. Response 5. Pass to 4. Return result
503
- returned handler or 402
504
- ```
505
-
506
- ### x402 Payment Flow
507
-
508
- When a customer has insufficient balance, the middleware returns `402 Payment Required`:
509
-
510
- ```
511
- HTTP/1.1 402 Payment Required
512
- X-Payment-Required: true
513
- X-Payment-Amount: 0.01
514
- X-Payment-Recipient: 0x...
515
- X-Payment-Usage-Id: 0x...
516
- X-Payment-Expires: 1704110400
517
- X-Payment-Nonce: abc123
518
-
519
- {
520
- "error": "Payment required",
521
- "code": "PAYMENT_REQUIRED",
522
- "paymentRequest": { ... },
523
- "instructions": {
524
- "step1": "Sign the payment request with your session key using EIP-712",
525
- "step2": "Retry the request with X-Payment-* headers"
526
- }
527
- }
528
- ```
529
-
530
- ### Advanced Usage
531
-
532
- #### Dynamic Quantity
533
-
534
- ```typescript
535
- export const POST = withDrip({
536
- meter: 'tokens',
537
- quantity: async (req) => {
538
- const body = await req.json();
539
- return body.maxTokens ?? 100;
540
- },
541
- }, handler);
542
- ```
543
-
544
- #### Custom Customer Resolution
545
-
546
- ```typescript
547
- export const POST = withDrip({
548
- meter: 'api_calls',
549
- quantity: 1,
550
- customerResolver: (req) => {
551
- const token = req.headers.get('authorization')?.split(' ')[1];
552
- return decodeJWT(token).customerId;
553
- },
554
- }, handler);
555
- ```
556
-
557
- #### Factory Pattern
558
-
559
- ```typescript
560
- // lib/drip.ts
561
- import { createWithDrip } from '@drip-sdk/node/next';
562
-
563
- export const withDrip = createWithDrip({
564
- apiKey: process.env.DRIP_API_KEY,
565
- baseUrl: process.env.DRIP_API_URL,
566
- });
567
-
568
- // app/api/generate/route.ts
569
- import { withDrip } from '@/lib/drip';
570
- export const POST = withDrip({ meter: 'api_calls', quantity: 1 }, handler);
571
- ```
572
-
573
- ### What's Included vs. Missing
574
-
575
- | Feature | Status | Description |
576
- |---------|--------|-------------|
577
- | Next.js App Router | ✅ | `withDrip` wrapper |
578
- | Express Middleware | ✅ | `dripMiddleware` |
579
- | x402 Payment Flow | ✅ | Automatic 402 handling |
580
- | Dynamic Quantity | ✅ | Function-based pricing |
581
- | Customer Resolution | ✅ | Header, query, or custom |
582
- | Idempotency | ✅ | Built-in or custom keys |
583
- | Dev Mode Skip | ✅ | Skip in development |
584
- | Metadata | ✅ | Attach to charges |
585
- | TypeScript | ✅ | Full type definitions |
586
- | Fastify Adapter | ❌ | Coming soon |
587
- | Rate Limiting | ❌ | Planned |
588
- | Balance Caching | ❌ | Planned |
589
-
590
- ## Contributing
591
-
592
- Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
593
-
594
- ## License
595
-
596
- MIT - see [LICENSE](./LICENSE)
597
259
 
598
260
  ## Links
599
261
 
600
- - [GitHub Repository](https://github.com/MichaelLevin5908/drip-sdk)
601
- - [Issue Tracker](https://github.com/MichaelLevin5908/drip-sdk/issues)
602
- - [npm Package](https://www.npmjs.com/package/@drip-sdk/node)
603
- - [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)