@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 +154 -592
- package/dist/core.cjs +3 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +762 -0
- package/dist/core.d.ts +760 -0
- package/dist/core.js +3 -0
- package/dist/core.js.map +1 -0
- package/dist/express.cjs +2 -2
- package/dist/express.cjs.map +1 -1
- package/dist/express.d.cts +3 -3
- package/dist/express.d.ts +3 -3
- package/dist/express.js +2 -2
- package/dist/express.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +746 -54
- package/dist/index.d.ts +746 -54
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/langchain.cjs +3 -0
- package/dist/langchain.cjs.map +1 -0
- package/dist/langchain.d.cts +290 -0
- package/dist/langchain.d.ts +290 -0
- package/dist/langchain.js +3 -0
- package/dist/langchain.js.map +1 -0
- package/dist/middleware.cjs +2 -2
- package/dist/middleware.cjs.map +1 -1
- package/dist/middleware.d.cts +3 -3
- package/dist/middleware.d.ts +3 -3
- package/dist/middleware.js +2 -2
- package/dist/middleware.js.map +1 -1
- package/dist/next.cjs +2 -2
- package/dist/next.cjs.map +1 -1
- package/dist/next.d.cts +3 -3
- package/dist/next.d.ts +3 -3
- package/dist/next.js +2 -2
- package/dist/next.js.map +1 -1
- package/dist/{types-D8mMON4v.d.ts → types-B2qwDadD.d.ts} +1 -1
- package/dist/{types-92iVqLtE.d.cts → types-Bo8SiUdl.d.cts} +1 -1
- package/package.json +23 -1
package/README.md
CHANGED
|
@@ -1,702 +1,264 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Drip SDK (Node.js)
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
+
**One line to start tracking:** `await drip.trackUsage({ customerId, meter, quantity })`
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@drip-sdk/node)
|
|
8
10
|
[](https://opensource.org/licenses/MIT)
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
---
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
npm install @drip-sdk/node
|
|
14
|
-
```
|
|
14
|
+
## 60-Second Quickstart (Core SDK)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
yarn add @drip-sdk/node
|
|
18
|
-
```
|
|
16
|
+
### 1. Install
|
|
19
17
|
|
|
20
18
|
```bash
|
|
21
|
-
|
|
19
|
+
npm install @drip-sdk/node
|
|
22
20
|
```
|
|
23
21
|
|
|
24
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
32
|
+
### 3. Track usage (one line)
|
|
47
33
|
|
|
48
34
|
```typescript
|
|
49
|
-
import
|
|
50
|
-
import { dripMiddleware } from '@drip-sdk/node/express';
|
|
51
|
-
|
|
52
|
-
const app = express();
|
|
35
|
+
import { drip } from '@drip-sdk/node';
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
41
|
+
The `drip` singleton reads `DRIP_API_KEY` from your environment automatically.
|
|
66
42
|
|
|
67
|
-
|
|
43
|
+
### Alternative: Explicit Configuration
|
|
68
44
|
|
|
69
45
|
```typescript
|
|
70
46
|
import { Drip } from '@drip-sdk/node';
|
|
71
47
|
|
|
72
|
-
//
|
|
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
|
-
//
|
|
84
|
-
const
|
|
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
|
-
|
|
91
|
-
|
|
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
|
-
|
|
58
|
+
### Full Example
|
|
95
59
|
|
|
96
60
|
```typescript
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
+
console.log('Usage + run recorded');
|
|
87
|
+
}
|
|
114
88
|
|
|
115
|
-
|
|
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
|
-
|
|
92
|
+
**Expected result:**
|
|
93
|
+
- No errors
|
|
94
|
+
- Events appear in your Drip dashboard within seconds
|
|
124
95
|
|
|
125
|
-
|
|
126
|
-
const customer = await drip.getCustomer('cust_abc123');
|
|
127
|
-
```
|
|
96
|
+
---
|
|
128
97
|
|
|
129
|
-
|
|
98
|
+
## Core Concepts (2-minute mental model)
|
|
130
99
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
+
**Event schema:** Payloads are schema-flexible. Drip stores events as structured JSON and does not enforce a fixed event taxonomy.
|
|
143
110
|
|
|
144
|
-
|
|
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
|
-
|
|
113
|
+
---
|
|
150
114
|
|
|
151
|
-
|
|
115
|
+
## Idempotency Keys
|
|
152
116
|
|
|
153
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
121
|
+
### Auto-generated keys (default)
|
|
169
122
|
|
|
170
|
-
|
|
123
|
+
When you omit `idempotencyKey`, the SDK generates one automatically. The auto key is:
|
|
171
124
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
const charge = await drip.getCharge('chg_abc123');
|
|
192
|
-
console.log(`Status: ${charge.status}`);
|
|
193
|
-
```
|
|
133
|
+
### When to pass explicit keys
|
|
194
134
|
|
|
195
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
+
---
|
|
221
156
|
|
|
222
|
-
|
|
223
|
-
import { Drip } from '@drip-sdk/node';
|
|
157
|
+
## API Key Types
|
|
224
158
|
|
|
225
|
-
|
|
159
|
+
Drip issues two key types per API key pair. Each has different access scopes:
|
|
226
160
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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
|
|
256
|
-
|
|
257
|
-
meter: 'tokens',
|
|
180
|
+
const drip = new Drip({ apiKey: 'pk_test_...' });
|
|
181
|
+
console.log(drip.keyType); // 'public'
|
|
258
182
|
|
|
259
|
-
|
|
260
|
-
|
|
183
|
+
// This works fine
|
|
184
|
+
await drip.trackUsage({ customerId: 'cust_123', meter: 'api_calls', quantity: 1 });
|
|
261
185
|
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
190
|
+
---
|
|
274
191
|
|
|
275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
314
|
-
await Promise.all([tokenMeter.flush(), toolMeter.flush()]);
|
|
315
|
-
```
|
|
201
|
+
## Core SDK Methods
|
|
316
202
|
|
|
317
|
-
|
|
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
|
-
|
|
217
|
+
---
|
|
320
218
|
|
|
321
|
-
|
|
219
|
+
## Who This Is For
|
|
322
220
|
|
|
323
|
-
|
|
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
|
-
|
|
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
|
-
|
|
347
|
-
// Output: "✓ My Agent: 4 events recorded (250ms)"
|
|
348
|
-
```
|
|
228
|
+
## Full SDK (Billing, Webhooks, Integrations)
|
|
349
229
|
|
|
350
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(`
|
|
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
|
-
|
|
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
|
-
- [
|
|
700
|
-
- [
|
|
701
|
-
- [npm
|
|
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)
|