meterflow 0.2.0 → 0.4.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.
package/README.md CHANGED
@@ -1,10 +1,44 @@
1
1
  # meterflow
2
2
 
3
- Official Node.js SDK for [MeterFlow](https://meterflow.com) — usage-based billing, credit management, and metering.
3
+ [![npm version](https://img.shields.io/npm/v/meterflow)](https://www.npmjs.com/package/meterflow)
4
+ [![node](https://img.shields.io/node/v/meterflow)](https://nodejs.org)
5
+ [![license](https://img.shields.io/npm/l/meterflow)](./LICENSE)
6
+
7
+ Official Node.js SDK for [MeterFlow](https://meter-flow.com) — usage-based billing, credit management, and metering.
8
+
9
+ Track what your customers use, enforce credit balances, and manage subscriptions with a few lines of code. TypeScript-first, zero runtime dependencies, built on native `fetch`.
10
+
11
+ ---
12
+
13
+ ## Contents
14
+
15
+ - [Requirements](#requirements)
16
+ - [Installation](#installation)
17
+ - [Quick start](#quick-start)
18
+ - [Authentication](#authentication)
19
+ - [Core concepts](#core-concepts)
20
+ - [Configuration](#configuration)
21
+ - [Usage guide](#usage-guide)
22
+ - [Credits](#credits)
23
+ - [Usage events](#usage-events)
24
+ - [Subscriptions](#subscriptions)
25
+ - [Plans](#plans)
26
+ - [Idempotency — safe retries for writes](#idempotency--safe-retries-for-writes)
27
+ - [Automatic retries](#automatic-retries)
28
+ - [Error handling](#error-handling)
29
+ - [Verifying webhooks](#verifying-webhooks)
30
+ - [Using the SDK in a browser](#using-the-sdk-in-a-browser)
31
+ - [TypeScript notes](#typescript-notes)
32
+ - [Versioning & support](#versioning--support)
33
+
34
+ ---
4
35
 
5
36
  ## Requirements
6
37
 
7
- - Node.js ≥ 18
38
+ - **Node.js ≥ 20** (the SDK uses the built-in `fetch` — no HTTP library is installed)
39
+ - An API key from your [MeterFlow dashboard](https://meter-flow.com) (Project → API Keys)
40
+
41
+ > Need Node 18? It reached end-of-life in April 2025 — please upgrade. The last SDK line supporting it is `meterflow@0.2.x`.
8
42
 
9
43
  ## Installation
10
44
 
@@ -12,78 +46,395 @@ Official Node.js SDK for [MeterFlow](https://meterflow.com) — usage-based bill
12
46
  npm install meterflow
13
47
  ```
14
48
 
49
+ Ships dual **CJS + ESM** with bundled TypeScript types — `require` and `import` both work out of the box.
50
+
15
51
  ## Quick start
16
52
 
17
53
  ```typescript
18
54
  import { MeterFlow } from "meterflow";
19
55
 
20
- const client = new MeterFlow({ apiKey: "mf_live_your_api_key" });
56
+ const client = new MeterFlow({ apiKey: process.env.METERFLOW_API_KEY! });
21
57
 
22
- // Grant credits to a customer
58
+ // 1. Grant credits to a customer
23
59
  await client.credits.grant({
24
60
  customer_external_id: "customer_123",
25
- amount: 100,
26
- description: "Welcome credits",
61
+ amount: 5000,
62
+ description: "Starter plan — monthly credit grant",
63
+ metadata: {},
27
64
  });
28
65
 
29
- // Record a usage event
66
+ // 2. Report what happened, wherever your product does the billable thing
30
67
  await client.usage.record({
68
+ event_name: "images.generated",
31
69
  customer_external_id: "customer_123",
32
- meter_name: "api_calls",
33
- quantity: 1,
70
+ value: 1,
71
+ properties: {},
34
72
  });
35
73
 
36
- // Check credit balance
74
+ // 3. Check what they have left
37
75
  const balance = await client.credits.balance("customer_123");
38
- console.log(balance.balance);
76
+ console.log(balance.balance); // "4999.000000"
77
+ ```
78
+
79
+ That's the whole integration loop: grant → record → check. Everything else in this guide is detail.
80
+
81
+ ## Authentication
82
+
83
+ Every request authenticates with the API key you pass to the constructor:
84
+
85
+ | Key prefix | Environment | Use for |
86
+ |---|---|---|
87
+ | `mf_live_…` | Live | Real customers, real balances |
88
+ | `mf_test_…` | Test | Development, CI, experiments |
89
+
90
+ The key's environment is a **real data boundary, not a label**: inside the same project, `mf_test_` keys read and write a fully separate dataset from `mf_live_` keys — subscriptions, credits, and usage events created with a test key are invisible to live keys (and vice versa), while your meters and plans are shared, so tests always run against your real billing configuration. The same customer id can hold an independent balance and subscription in each environment, and idempotency keys are namespaced per environment. Point your staging/CI at a test key and production at a live key — same project, zero risk of cross-contamination.
91
+
92
+ Keys are created in the dashboard (Project → API Keys) and **shown once** at creation — MeterFlow stores only a fingerprint. If a key leaks, revoke it in the dashboard and mint a new one; revocation is immediate.
93
+
94
+ A key belongs to **one project** and can only see that project's data.
95
+
96
+ ```typescript
97
+ const client = new MeterFlow({ apiKey: "mf_test_..." }); // throws immediately if the prefix is neither mf_live_ nor mf_test_
39
98
  ```
40
99
 
100
+ > Treat API keys like passwords: read them from environment variables or a secret manager, never commit them.
101
+
102
+ ## Core concepts
103
+
104
+ Four words explain the whole product:
105
+
106
+ | Concept | What it is | Example |
107
+ |---|---|---|
108
+ | **Meter** | One countable thing, identified by its `event_name` | `images.generated`, `minutes.transcribed` |
109
+ | **Plan** | What you sell: price, billing period, and how much of each meter is included | Starter — $29/month, 5,000 images |
110
+ | **Subscription** | One customer on one plan from a date; renews itself | `customer_123` → Starter |
111
+ | **Credits** | The customer's balance — granted up, spent down, never edited in place | `+5000` granted, `−1340` consumed |
112
+
113
+ Meters and plans are defined in the dashboard. Your app, through this SDK, does the day-to-day work: creates subscriptions, grants/deducts credits, and records usage events (each event lands on the meter whose `event_name` matches).
114
+
41
115
  ## Configuration
42
116
 
43
117
  ```typescript
44
118
  const client = new MeterFlow({
45
- apiKey: "mf_live_your_api_key", // required; mf_live_* or mf_test_*
46
- baseUrl: "https://api.meterflow.com/api/v1", // optional override
47
- timeout: 30_000, // ms; default 30 s
48
- retries: 3, // default 3; 0 to disable
119
+ apiKey: "mf_live_...", // required — mf_live_* or mf_test_*
120
+ baseUrl: "https://api.meter-flow.com/api/v1", // optional — override for self-hosted / local dev
121
+ timeout: 30_000, // optional — per-request timeout in ms (default 30 s)
122
+ retries: 3, // optional — automatic retries (default 3, 0 disables)
123
+ fetch: customFetch, // optional — inject your own fetch (tests, proxies)
49
124
  });
50
125
  ```
51
126
 
52
- ## Resources
127
+ `baseUrl` is for pointing at a **different MeterFlow server** — a self-hosted deployment, or a locally running stack if you develop MeterFlow itself (`baseUrl: "http://localhost:8000/api/v1"`). If you use the hosted service, leave it at the default; to test your integration safely, use an `mf_test_` key instead (see [Authentication](#authentication)) — no URL change needed.
53
128
 
54
- | Resource | Methods |
55
- |---|---|
56
- | `client.credits` | `grant`, `deduct`, `balance`, `transactions` |
57
- | `client.usage` | `record`, `recordBatch`, `summary` |
58
- | `client.subscriptions` | `create`, `list`, `get`, `update`, `delete` |
59
- | `client.plans` | `list`, `get` |
129
+ All configuration lives on the client instance — there is no global state, so you can create multiple clients (e.g. one per project) in the same process.
130
+
131
+ ## Usage guide
132
+
133
+ ### Credits
134
+
135
+ Credits are an append-only ledger: every grant and deduction is a permanent transaction, and the balance is the sum. Nothing is ever edited in place — which is why a customer's history is always auditable.
136
+
137
+ **Grant** — add credits (plan renewals, top-ups, goodwill):
138
+
139
+ ```typescript
140
+ const txn = await client.credits.grant({
141
+ customer_external_id: "customer_123",
142
+ amount: 5000,
143
+ description: "Monthly plan grant",
144
+ metadata: {},
145
+ });
146
+ console.log(txn.balance_after); // "5000.000000"
147
+ ```
148
+
149
+ **Deduct** — remove credits. Throws `InsufficientCreditsError` (HTTP 402) if the balance can't cover it — the customer is never taken below zero:
150
+
151
+ ```typescript
152
+ import { InsufficientCreditsError } from "meterflow";
153
+
154
+ try {
155
+ await client.credits.deduct({
156
+ customer_external_id: "customer_123",
157
+ amount: 25,
158
+ description: "images.generated ×25",
159
+ metadata: {},
160
+ });
161
+ } catch (err) {
162
+ if (err instanceof InsufficientCreditsError) {
163
+ // Show your "out of credits — top up" screen. This is an upgrade prompt, not an error page.
164
+ } else {
165
+ throw err;
166
+ }
167
+ }
168
+ ```
169
+
170
+ **Balance** — the current position:
171
+
172
+ ```typescript
173
+ const bal = await client.credits.balance("customer_123");
174
+ bal.balance; // "3545.000000" ← decimal string, see note below
175
+ bal.total_granted; // "5250.000000"
176
+ bal.total_consumed; // "1705.000000"
177
+ ```
178
+
179
+ **Transactions** — the full history, newest first, paginated:
180
+
181
+ ```typescript
182
+ const txns = await client.credits.transactions("customer_123", { page: 1, limit: 50 });
183
+ for (const t of txns) {
184
+ console.log(t.transaction_type, t.amount, t.balance_after, t.description, t.created_at);
185
+ }
186
+ ```
187
+
188
+ > **Amounts are decimal strings.** Balances and amounts come back as strings (`"3545.000000"`) to avoid floating-point drift on money-like values. Parse deliberately (`Number(...)` or a decimal library) when you need arithmetic.
189
+
190
+ ### Usage events
191
+
192
+ Record an event every time a customer does the thing you charge for. Events are matched to a meter by `event_name` and processed asynchronously into totals (and, if the meter is metered on a plan, into credit deductions).
193
+
194
+ **Record one event:**
195
+
196
+ ```typescript
197
+ await client.usage.record({
198
+ event_name: "images.generated",
199
+ customer_external_id: "customer_123",
200
+ value: 1, // what the meter aggregates: 1 for counts, seconds/MB/etc. for sums
201
+ properties: {}, // free-form context; {} when unused
202
+ timestamp: new Date().toISOString(), // optional — defaults to arrival time on the server
203
+ });
204
+ ```
205
+
206
+ **Record a batch** — for high-throughput paths, flush events in groups instead of one request each:
207
+
208
+ ```typescript
209
+ await client.usage.recordBatch([
210
+ { event_name: "images.generated", customer_external_id: "customer_123", value: 1, properties: {} },
211
+ { event_name: "video.seconds_rendered", customer_external_id: "customer_123", value: 42, properties: {} },
212
+ ]);
213
+ ```
214
+
215
+ **Summary** — a customer's usage, broken down by meter:
216
+
217
+ ```typescript
218
+ const summary = await client.usage.summary("customer_123", {
219
+ // meter_id: "…", // optional — narrow to one meter
220
+ from_: "2026-09-01T00:00:00Z", // optional — note the trailing underscore
221
+ to: "2026-09-30T23:59:59Z", // optional
222
+ });
223
+ for (const m of summary.meters) {
224
+ console.log(m); // per-meter aggregation and event counts
225
+ }
226
+ ```
60
227
 
61
- ## Webhook verification
228
+ > The `from_` filter has a trailing underscore — it mirrors the API's query parameter exactly.
229
+
230
+ Recording an event returns immediately (`processed: false`); totals and any credit deductions materialise moments later. Don't read a balance in the same millisecond and expect the event to be reflected.
231
+
232
+ ### Subscriptions
233
+
234
+ A subscription puts one customer on one plan and renews itself. Typically your app creates it in the signup flow:
235
+
236
+ ```typescript
237
+ // Pick a plan (defined in the dashboard)…
238
+ const plans = await client.plans.list();
239
+ const starter = plans.find((p) => p.slug === "starter")!;
240
+
241
+ // …and put the new customer on it
242
+ const sub = await client.subscriptions.create({
243
+ plan_id: starter.id,
244
+ customer_external_id: "customer_123",
245
+ metadata: {},
246
+ });
247
+ console.log(sub.status); // "trialing" if the plan has trial days, else "active"
248
+ ```
249
+
250
+ **List / get:**
251
+
252
+ ```typescript
253
+ const all = await client.subscriptions.list(); // whole project (in your key's environment)
254
+ const theirs = await client.subscriptions.list({ customer_id: "customer_123" }); // one customer
255
+ const one = await client.subscriptions.get(sub.id);
256
+ ```
257
+
258
+ > Like all reads, these are scoped to the key's environment: a live key lists live subscriptions only, a test key test ones only.
259
+
260
+ **Update** — change status or metadata. Statuses: `active`, `trialing`, `past_due`, `paused`, `canceled`, `expired`:
261
+
262
+ ```typescript
263
+ await client.subscriptions.update(sub.id, { status: "paused" });
264
+ await client.subscriptions.update(sub.id, { status: "active" }); // reactivate
265
+ ```
266
+
267
+ **Cancel vs delete** — two different operations:
62
268
 
63
269
  ```typescript
64
- import { verifyWebhook } from "meterflow/webhook";
270
+ await client.subscriptions.update(sub.id, { status: "canceled" }); // cancel: the record stays for history
271
+ await client.subscriptions.delete(sub.id); // delete: removes the subscription record entirely
272
+ ```
273
+
274
+ Prefer cancelling: it preserves the subscription's history (a canceled subscription can't be reactivated). Reach for `delete` only when you truly want the record gone — e.g. cleaning up test data.
275
+
276
+ ### Plans
277
+
278
+ Plans are **read-only** through the SDK — pricing is managed by humans in the dashboard, so a leaked API key can never rewrite your prices.
279
+
280
+ ```typescript
281
+ const plans = await client.plans.list(); // the project's plans
282
+ const plan = await client.plans.get(plan_id); // one plan, including its per-meter limits
283
+ plan.price; // "29.00" — decimal string
284
+ plan.billing_period; // "monthly" | "yearly" | "weekly" | "one_time"
285
+ plan.meter_limits; // included units + overage rate per meter
286
+ ```
287
+
288
+ Typical use: render your pricing page or signup flow from `plans.list()` so it can never drift from what billing actually enforces.
65
289
 
66
- const isValid = verifyWebhook(rawBody, req.headers["x-meterflow-signature"], webhookSecret);
290
+ ## Idempotency — safe retries for writes
291
+
292
+ Connections drop. When your app can't tell whether a write arrived, the correct move is to send it again **with the same idempotency key** — MeterFlow recognises the key and acts only once:
293
+
294
+ ```typescript
295
+ await client.credits.deduct(
296
+ { customer_external_id: "customer_123", amount: 1, description: "ticket A7", metadata: {} },
297
+ { idempotencyKey: "deduct-ticket-A7" }, // forwarded as the Idempotency-Key header
298
+ );
299
+ // Sending this twice deducts exactly once and returns the same transaction both times.
67
300
  ```
68
301
 
302
+ Every write method accepts the option: `credits.grant/deduct`, `usage.record/recordBatch`, `subscriptions.create/update`. Use a key that identifies the *business operation* (order ID, job ID) — not a random value per attempt, which would defeat the purpose.
303
+
304
+ ## Automatic retries
305
+
306
+ The SDK retries transient failures for you — exponential backoff with jitter, 3 attempts by default:
307
+
308
+ | Situation | Behaviour |
309
+ |---|---|
310
+ | Network error / connection dropped | Retried |
311
+ | `5xx` server errors | Retried |
312
+ | `429 Too Many Requests` | Waits for the server's `Retry-After`, then retries |
313
+ | Any other `4xx` (auth, validation, not-found, insufficient credits…) | **Never retried** — it would fail identically |
314
+
315
+ Configure with `retries` in the constructor (`0` disables). Combine retries with idempotency keys on writes and a flaky network costs you nothing: the SDK re-sends, the server deduplicates.
316
+
69
317
  ## Error handling
70
318
 
319
+ Every non-2xx response is thrown as a typed error. All of them extend `MeterFlowError`:
320
+
321
+ | Class | HTTP | `errorType` | Retried by the SDK |
322
+ |---|---|---|---|
323
+ | `AuthError` | 401 / 403 | `auth_error` | no |
324
+ | `InsufficientCreditsError` | 402 | `insufficient_credits` | no |
325
+ | `NotFoundError` | 404 | `not_found` | no |
326
+ | `ConflictError` | 409 | `conflict` | no |
327
+ | `ValidationError` | 422 | `validation_error` | no |
328
+ | `RateLimitError` | 429 | `rate_limit` | yes (honours `Retry-After`, exposed as `.retryAfter`) |
329
+ | `ServerError` | 5xx | `server_error` | yes |
330
+
331
+ Every error carries:
332
+
333
+ - **`requestId`** — the API's `X-Request-ID` for that call. Include it when contacting support; it pinpoints the exact request in our logs.
334
+ - **`statusCode`**, **`errorType`**, and **`retryable`** — for programmatic handling and structured logging.
335
+
71
336
  ```typescript
72
337
  import { MeterFlowError, InsufficientCreditsError, RateLimitError } from "meterflow";
73
338
 
74
339
  try {
75
- await client.credits.deduct({ customer_external_id: "cust_1", amount: 999 });
340
+ await client.credits.deduct({ customer_external_id: "c1", amount: 999, metadata: {} });
76
341
  } catch (err) {
77
342
  if (err instanceof InsufficientCreditsError) {
78
- console.log("Not enough credits");
343
+ // expected business outcome — prompt a top-up
79
344
  } else if (err instanceof RateLimitError) {
80
- console.log(`Rate limited, retry after ${err.retryAfter}s`);
345
+ console.warn(`rate limited; server asked to wait ${err.retryAfter}s`); // only seen if retries are exhausted/disabled
81
346
  } else if (err instanceof MeterFlowError) {
82
- console.log(`Request ID for support: ${err.requestId}`);
347
+ console.error(`MeterFlow error [${err.errorType}] status=${err.statusCode} requestId=${err.requestId}`);
348
+ throw err;
349
+ } else {
350
+ throw err; // not from MeterFlow
83
351
  }
84
352
  }
85
353
  ```
86
354
 
355
+ ## Verifying webhooks
356
+
357
+ MeterFlow notifies your app of events you'd otherwise poll for (low balances, renewals, …). Every delivery is signed with **HMAC-SHA256** in the `X-MeterFlow-Signature` header, using the webhook's secret — verify before trusting:
358
+
359
+ ```typescript
360
+ import express from "express";
361
+ import { verifyWebhook } from "meterflow/webhook"; // note: separate entry point
362
+
363
+ const app = express();
364
+
365
+ // The signature covers the RAW body — capture it before any JSON parsing.
366
+ app.post("/meterflow-webhook", express.raw({ type: "application/json" }), (req, res) => {
367
+ const signature = req.header("X-MeterFlow-Signature") ?? "";
368
+
369
+ if (!verifyWebhook(req.body, signature, process.env.METERFLOW_WEBHOOK_SECRET!)) {
370
+ return res.status(401).send("invalid signature"); // forged or corrupted — discard
371
+ }
372
+
373
+ const event = JSON.parse(req.body.toString("utf8"));
374
+ // handle the event…
375
+ res.sendStatus(200);
376
+ });
377
+ ```
378
+
379
+ Details that matter:
380
+
381
+ - **Import from `meterflow/webhook`**, not the root package. The verifier uses Node's `crypto` and lives in its own entry point so the main client stays browser-safe.
382
+ - **Verify the raw bytes.** If you `JSON.parse` first and re-stringify, key ordering/whitespace changes and the signature won't match. `express.json()` users: configure it with a `verify` callback to keep `rawBody`, or use `express.raw` on the webhook route as above.
383
+ - The comparison is **timing-safe** (`crypto.timingSafeEqual`) and returns `false` on any mismatch — it never throws on bad input.
384
+
385
+ Webhooks are registered per-project in the dashboard, which is also where you'll find the secret and each delivery attempt's status.
386
+
387
+ ## Using the SDK in a browser
388
+
389
+ The root entry point is browser-safe: no Node built-ins, and `fetch` is bound correctly for browser environments. Two rules:
390
+
391
+ 1. **Never ship an `mf_live_` key to a browser.** Anyone can read it in DevTools. Browser usage is for trusted, short-lived contexts (internal tools, dashboards) with `mf_test_` or ephemeral keys — your product's customers should always go through **your** backend, which holds the key. (A leaked `mf_test_` key is contained by design: it can only ever touch the sandboxed test dataset, never live balances.)
392
+ 2. **Don't import `meterflow/webhook` in browser code** — it needs Node's `crypto` (and verifying webhooks in a browser makes no sense anyway: the secret must stay server-side).
393
+
394
+ ## TypeScript notes
395
+
396
+ - All request/response types are **generated from MeterFlow's OpenAPI contract**, so anything the API accepts or returns is statically typed — your editor autocompletes every field above.
397
+ - Request fields are `snake_case`, matching the HTTP API one-to-one (`customer_external_id`, not `customerExternalId`). What you see in the docs and dashboard is exactly what you type.
398
+ - `properties` (usage events) and `metadata` (credits/subscriptions) are required by the generated types — pass `{}` when you have nothing to attach.
399
+ - Public exports: `MeterFlow` and the error classes from `"meterflow"`; `verifyWebhook` from `"meterflow/webhook"`. Response types are inferred from method return values.
400
+
401
+ ## Versioning & support
402
+
403
+ - Semantic versioning on the `0.x` line: breaking changes bump the minor, fixes bump the patch.
404
+ - `0.4.0` changed the default API base URL to **`https://api.meter-flow.com/api/v1`** (the platform's domain). If you set `baseUrl` explicitly, nothing changes for you.
405
+ - `0.3.0` raised the Node floor to **≥ 20** (Node 18 is end-of-life). `0.2.x` remains available for Node 18.
406
+ - Tested in CI on Node **20, 22 and 24**.
407
+ - Issues and source: [github.com/meterflowio/meterflow-node](https://github.com/meterflowio/meterflow-node). Include the `requestId` from any `MeterFlowError` when reporting API issues.
408
+
87
409
  ## License
88
410
 
89
411
  MIT
412
+
413
+ ## The full loop, end to end
414
+
415
+ ```typescript
416
+ import { MeterFlow } from "meterflow";
417
+
418
+ const client = new MeterFlow({ apiKey: process.env.METERFLOW_API_KEY! });
419
+
420
+ // Signup: put the customer on a plan and give them their credits
421
+ const [starter] = await client.plans.list();
422
+ await client.subscriptions.create({ plan_id: starter.id, customer_external_id: "ana", metadata: {} });
423
+ await client.credits.grant(
424
+ { customer_external_id: "ana", amount: 5000, description: "Starter grant", metadata: {} },
425
+ { idempotencyKey: "signup-ana-2026-09" },
426
+ );
427
+
428
+ // Every time Ana uses the product
429
+ await client.usage.record({
430
+ event_name: "images.generated",
431
+ customer_external_id: "ana",
432
+ value: 1,
433
+ properties: { model: "sdxl", resolution: "1024x1024" },
434
+ });
435
+
436
+ // Support asks: "what's Ana's situation?"
437
+ const balance = await client.credits.balance("ana");
438
+ const history = await client.credits.transactions("ana", { limit: 20 });
439
+ const usage = await client.usage.summary("ana");
440
+ ```
package/dist/index.cjs CHANGED
@@ -308,7 +308,7 @@ var MeterFlow = class {
308
308
  }
309
309
  this.options = {
310
310
  apiKey: options.apiKey,
311
- baseUrl: options.baseUrl ?? "https://api.meterflow.com/api/v1",
311
+ baseUrl: options.baseUrl ?? "https://api.meter-flow.com/api/v1",
312
312
  timeout: options.timeout ?? 3e4,
313
313
  retries: options.retries ?? 3,
314
314
  // Bind to globalThis: browsers throw "Illegal invocation" if window.fetch is
package/dist/index.js CHANGED
@@ -308,7 +308,7 @@ var MeterFlow = class {
308
308
  }
309
309
  this.options = {
310
310
  apiKey: options.apiKey,
311
- baseUrl: options.baseUrl ?? "https://api.meterflow.com/api/v1",
311
+ baseUrl: options.baseUrl ?? "https://api.meter-flow.com/api/v1",
312
312
  timeout: options.timeout ?? 3e4,
313
313
  retries: options.retries ?? 3,
314
314
  // Bind to globalThis: browsers throw "Illegal invocation" if window.fetch is
package/dist/index.mjs CHANGED
@@ -274,7 +274,7 @@ var MeterFlow = class {
274
274
  }
275
275
  this.options = {
276
276
  apiKey: options.apiKey,
277
- baseUrl: options.baseUrl ?? "https://api.meterflow.com/api/v1",
277
+ baseUrl: options.baseUrl ?? "https://api.meter-flow.com/api/v1",
278
278
  timeout: options.timeout ?? 3e4,
279
279
  retries: options.retries ?? 3,
280
280
  // Bind to globalThis: browsers throw "Illegal invocation" if window.fetch is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meterflow",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Official Node.js SDK for MeterFlow — usage-based billing, credit management, and metering.",
5
5
  "license": "MIT",
6
6
  "author": "MeterFlow",
@@ -10,7 +10,7 @@
10
10
  "url": "https://github.com/meterflowio/meterflow-node.git"
11
11
  },
12
12
  "engines": {
13
- "node": ">=18"
13
+ "node": ">=20"
14
14
  },
15
15
  "main": "dist/index.cjs",
16
16
  "module": "dist/index.mjs",