meterflow 0.3.0 → 0.4.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
@@ -4,7 +4,7 @@
4
4
  [![node](https://img.shields.io/node/v/meterflow)](https://nodejs.org)
5
5
  [![license](https://img.shields.io/npm/l/meterflow)](./LICENSE)
6
6
 
7
- Official Node.js SDK for [MeterFlow](https://meterflow.com) — usage-based billing, credit management, and metering.
7
+ Official Node.js SDK for [MeterFlow](https://meter-flow.com) — usage-based billing, credit management, and metering.
8
8
 
9
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
10
 
@@ -36,7 +36,7 @@ Track what your customers use, enforce credit balances, and manage subscriptions
36
36
  ## Requirements
37
37
 
38
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://meterflow.com) (Project → API Keys)
39
+ - An API key from your [MeterFlow dashboard](https://meter-flow.com) (Project → API Keys)
40
40
 
41
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`.
42
42
 
@@ -87,9 +87,11 @@ Every request authenticates with the API key you pass to the constructor:
87
87
  | `mf_live_…` | Live | Real customers, real balances |
88
88
  | `mf_test_…` | Test | Development, CI, experiments |
89
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
+
90
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.
91
93
 
92
- A key belongs to **one project** and can only see that project's data. Keep live and test traffic in separate projects and the two can never mix.
94
+ A key belongs to **one project** and can only see that project's data.
93
95
 
94
96
  ```typescript
95
97
  const client = new MeterFlow({ apiKey: "mf_test_..." }); // throws immediately if the prefix is neither mf_live_ nor mf_test_
@@ -115,14 +117,14 @@ Meters and plans are defined in the dashboard. Your app, through this SDK, does
115
117
  ```typescript
116
118
  const client = new MeterFlow({
117
119
  apiKey: "mf_live_...", // required — mf_live_* or mf_test_*
118
- baseUrl: "https://api.meterflow.com/api/v1", // optional — override for self-hosted / local dev
120
+ baseUrl: "https://api.meter-flow.com/api/v1", // optional — override for self-hosted / local dev
119
121
  timeout: 30_000, // optional — per-request timeout in ms (default 30 s)
120
122
  retries: 3, // optional — automatic retries (default 3, 0 disables)
121
123
  fetch: customFetch, // optional — inject your own fetch (tests, proxies)
122
124
  });
123
125
  ```
124
126
 
125
- Running against a local MeterFlow stack? `baseUrl: "http://localhost:8000/api/v1"`.
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.
126
128
 
127
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.
128
130
 
@@ -248,11 +250,13 @@ console.log(sub.status); // "trialing" if the plan has trial days, else "active"
248
250
  **List / get:**
249
251
 
250
252
  ```typescript
251
- const all = await client.subscriptions.list(); // whole project
253
+ const all = await client.subscriptions.list(); // whole project (in your key's environment)
252
254
  const theirs = await client.subscriptions.list({ customer_id: "customer_123" }); // one customer
253
255
  const one = await client.subscriptions.get(sub.id);
254
256
  ```
255
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
+
256
260
  **Update** — change status or metadata. Statuses: `active`, `trialing`, `past_due`, `paused`, `canceled`, `expired`:
257
261
 
258
262
  ```typescript
@@ -260,12 +264,15 @@ await client.subscriptions.update(sub.id, { status: "paused" });
260
264
  await client.subscriptions.update(sub.id, { status: "active" }); // reactivate
261
265
  ```
262
266
 
263
- **Cancel:**
267
+ **Cancel vs delete** — two different operations:
264
268
 
265
269
  ```typescript
266
- await client.subscriptions.delete(sub.id); // marks it canceled; history is preserved
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
267
272
  ```
268
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
+
269
276
  ### Plans
270
277
 
271
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.
@@ -347,7 +354,7 @@ try {
347
354
 
348
355
  ## Verifying webhooks
349
356
 
350
- 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:
357
+ MeterFlow notifies your app of activity you'd otherwise poll for — credits granted or deducted, usage recorded, subscriptions created or updated. Every delivery is signed with **HMAC-SHA256** in the `X-MeterFlow-Signature` header, using the webhook's secret — verify before trusting:
351
358
 
352
359
  ```typescript
353
360
  import express from "express";
@@ -375,13 +382,40 @@ Details that matter:
375
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.
376
383
  - The comparison is **timing-safe** (`crypto.timingSafeEqual`) and returns `false` on any mismatch — it never throws on bad input.
377
384
 
385
+ ### Payload shape & event types
386
+
387
+ Every delivery is a flat JSON object (`Content-Type: application/json`) with two envelope fields — `event` (the type) and `environment` (`"live"` or `"test"`, matching the mode of the API key that caused the activity) — plus type-specific fields:
388
+
389
+ | `event` | Fired when | Extra fields |
390
+ |---|---|---|
391
+ | `usage.recorded` | a usage event is ingested (single or batch) | `customer_id`, `event_name`, `value`, `event_id` |
392
+ | `credit.granted` | credits are granted | `customer_id`, `amount`, `balance_after`, `transaction_id` |
393
+ | `credit.deducted` | credits are deducted | `customer_id`, `amount`, `balance_after`, `transaction_id` |
394
+ | `subscription.created` | a subscription is created | `customer_id`, `plan_id`, `subscription_id`, `status` |
395
+ | `subscription.updated` | a subscription is updated | `customer_id`, `subscription_id`, `status` |
396
+
397
+ For example, a `credit.granted` delivery:
398
+
399
+ ```json
400
+ {
401
+ "event": "credit.granted",
402
+ "environment": "live",
403
+ "customer_id": "cust_123",
404
+ "amount": 500.0,
405
+ "balance_after": 1250.0,
406
+ "transaction_id": "9b2f6c1e-…"
407
+ }
408
+ ```
409
+
410
+ When registering a webhook you pick which of these event types it should receive. Deliveries are retried with backoff on non-2xx responses — respond `200` quickly and do the heavy work asynchronously.
411
+
378
412
  Webhooks are registered per-project in the dashboard, which is also where you'll find the secret and each delivery attempt's status.
379
413
 
380
414
  ## Using the SDK in a browser
381
415
 
382
416
  The root entry point is browser-safe: no Node built-ins, and `fetch` is bound correctly for browser environments. Two rules:
383
417
 
384
- 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.
418
+ 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.)
385
419
  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).
386
420
 
387
421
  ## TypeScript notes
@@ -394,6 +428,7 @@ The root entry point is browser-safe: no Node built-ins, and `fetch` is bound co
394
428
  ## Versioning & support
395
429
 
396
430
  - Semantic versioning on the `0.x` line: breaking changes bump the minor, fixes bump the patch.
431
+ - `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.
397
432
  - `0.3.0` raised the Node floor to **≥ 20** (Node 18 is end-of-life). `0.2.x` remains available for Node 18.
398
433
  - Tested in CI on Node **20, 22 and 24**.
399
434
  - Issues and source: [github.com/meterflowio/meterflow-node](https://github.com/meterflowio/meterflow-node). Include the `requestId` from any `MeterFlowError` when reporting API issues.
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.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Official Node.js SDK for MeterFlow — usage-based billing, credit management, and metering.",
5
5
  "license": "MIT",
6
6
  "author": "MeterFlow",