meterflow 0.5.1 → 0.7.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
@@ -23,6 +23,7 @@ Track what your customers use, enforce credit balances, and manage subscriptions
23
23
  - [Usage events](#usage-events)
24
24
  - [Subscriptions](#subscriptions)
25
25
  - [Plans](#plans)
26
+ - [Webhook receipts](#webhook-receipts)
26
27
  - [Entitlements — ask before you act](#entitlements--ask-before-you-act)
27
28
  - [Idempotency — safe retries for writes](#idempotency--safe-retries-for-writes)
28
29
  - [Automatic retries](#automatic-retries)
@@ -177,15 +178,26 @@ bal.total_granted; // "5250.000000"
177
178
  bal.total_consumed; // "1705.000000"
178
179
  ```
179
180
 
180
- **Transactions** — the full history, newest first, paginated:
181
+ **Transactions** — the customer's history, newest first, **a page at a time**:
181
182
 
182
183
  ```typescript
183
- const txns = await client.credits.transactions("customer_123", { page: 1, limit: 50 });
184
- for (const t of txns) {
184
+ const page = await client.credits.transactions("customer_123"); // 50 rows by default
185
+ for (const t of page.data) {
185
186
  console.log(t.transaction_type, t.amount, t.balance_after, t.description, t.created_at);
186
187
  }
188
+
189
+ // The whole history, page by page. `next_cursor` is null once there is nothing after it.
190
+ let cursor: string | undefined;
191
+ do {
192
+ const page = await client.credits.transactions("customer_123", { limit: 200, cursor });
193
+ handle(page.data);
194
+ cursor = page.next_cursor ?? undefined;
195
+ } while (cursor);
187
196
  ```
188
197
 
198
+ > **The cursor is opaque.** Pass back exactly what `next_cursor` gave you; do not build one, and do
199
+ > not read anything out of it. `limit` is 1–200 and defaults to 50.
200
+
189
201
  > **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.
190
202
 
191
203
  ### Usage events
@@ -198,7 +210,7 @@ Record an event every time a customer does the thing you charge for. Events are
198
210
  await client.usage.record({
199
211
  event_name: "images.generated",
200
212
  customer_external_id: "customer_123",
201
- value: 1, // what the meter aggregates: 1 for counts, seconds/MB/etc. for sums
213
+ value: 1, // optional — what the meter aggregates: 1 for counts, seconds/MB/etc. for sums
202
214
  properties: {}, // free-form context; {} when unused
203
215
  timestamp: new Date().toISOString(), // optional — defaults to arrival time on the server
204
216
  });
@@ -224,6 +236,40 @@ for (let i = 0; i < events.length; i += MAX_BATCH_EVENTS) {
224
236
  }
225
237
  ```
226
238
 
239
+ ### Record quantities, not calls
240
+
241
+ **A batch of 100 events is still 100 events.** Batching saves HTTP requests, not quota: each item in a `recordBatch` is its own record, and your plan's monthly allowance counts records. The thing that actually moves the number is sending **one event that carries the quantity**.
242
+
243
+ If your meter's aggregation is `sum`, MeterFlow adds up the `value` of its events — so one event with `value: 100` is **one** event against your monthly allowance and **100 units** against the meter. Record per unit of work instead of per API call:
244
+
245
+ ```typescript
246
+ // ❌ One event per call — 100 calls is 100 events against your plan.
247
+ for (const chunk of completion.chunks) {
248
+ await client.usage.record({
249
+ event_name: "tokens.used",
250
+ customer_external_id: "customer_123",
251
+ value: 1,
252
+ properties: {},
253
+ });
254
+ }
255
+
256
+ // ✅ One event carrying the quantity — 1 event against your plan, 1,300 units on the meter.
257
+ await client.usage.record({
258
+ event_name: "tokens.used",
259
+ customer_external_id: "customer_123",
260
+ value: 1300, // a `sum` meter adds these up
261
+ properties: { model: "gpt-4o-mini" },
262
+ });
263
+ ```
264
+
265
+ This is usually the difference between fitting a plan and outgrowing it: 1,000 users × 100 calls a day is ~3M events a month recorded per call, and ~30k recorded per session. Aggregate in your app over whatever window you can afford to lose on a crash — a request, a job, a minute — then send one event.
266
+
267
+ Two things to keep in mind:
268
+
269
+ - **It only helps a `sum` meter.** A `count` meter ignores `value` and counts occurrences, so there each occurrence has to be its own event. `max` and `min` read `value` as a measurement, not a quantity to add, and `unique_count` does not read it at all — it counts distinct values of the property its meter names.
270
+ - **`value` is optional.** Leave it out and MeterFlow fills it in: for a `sum`, `max` or `min` meter that names an `aggregation_field`, the numeric value of that property on the event; otherwise `1`. So the event above can be written `properties: { tokens: 1300 }` with no `value`, as long as the meter's `aggregation_field` is `tokens`. An explicit `value` always wins, a property that is absent or is not a number falls through to `1`, and a negative one is refused with a `422`.
271
+ - **Billing is unchanged.** A metered plan limit charges per unit — the event's `value` — so 100 units cost the same whether they arrived as 100 events or one. You are not rounding anyone's bill by batching.
272
+
227
273
  **Summary** — a customer's usage, broken down by meter:
228
274
 
229
275
  ```typescript
@@ -262,11 +308,16 @@ console.log(sub.status); // "trialing" if the plan has trial days, else "active"
262
308
  **List / get:**
263
309
 
264
310
  ```typescript
265
- const all = await client.subscriptions.list(); // whole project (in your key's environment)
311
+ const page = await client.subscriptions.list(); // whole project (in your key's environment)
266
312
  const theirs = await client.subscriptions.list({ customer_id: "customer_123" }); // one customer
313
+ const more = await client.subscriptions.list({ limit: 200, cursor: page.next_cursor ?? undefined });
267
314
  const one = await client.subscriptions.get(sub.id);
315
+
316
+ page.data.forEach((subscription) => console.log(subscription.customer_external_id, subscription.status));
268
317
  ```
269
318
 
319
+ > **`list()` answers a page**, `{ data, next_cursor }` — `get()` still answers one subscription.
320
+
270
321
  > Like all reads, these are scoped to the key's environment: a live key lists live subscriptions only, a test key test ones only.
271
322
 
272
323
  **Update** — change status or metadata. Statuses: `active`, `trialing`, `past_due`, `paused`, `canceled`, `expired`:
@@ -299,6 +350,27 @@ plan.meter_limits; // included units + overage rate per meter
299
350
 
300
351
  Typical use: render your pricing page or signup flow from `plans.list()` so it can never drift from what billing actually enforces.
301
352
 
353
+ ### Webhook receipts
354
+
355
+ Webhooks are **read-only** through the SDK too: endpoints are registered in the dashboard, where the signing secret is shown once and never again. What the SDK gives you is the other half — which endpoints exist, and what MeterFlow actually sent them.
356
+
357
+ ```typescript
358
+ const endpoints = await client.webhooks.list(); // this project's endpoints
359
+ const receipts = await client.webhooks.deliveries(endpoint_id); // newest first
360
+ receipts[0].event_type; // "usage.recorded"
361
+ receipts[0].status; // "pending" | "retrying" | "success" | "failed"
362
+ receipts[0].response_code; // what your server answered — 200, 500, or null if it never answered
363
+ receipts[0].attempts; // how many times we have tried
364
+ receipts[0].next_retry_at; // when we will try again, or null
365
+ receipts[0].payload; // exactly what we sent
366
+ ```
367
+
368
+ Typical use: answering "did that event reach my endpoint?" from your own tooling instead of opening the dashboard — and reading `response_body` when the answer is no.
369
+
370
+ Two things to know. The list is capped at the **most recent 100** attempts per endpoint; it is a debugging surface, not an export. And it follows your key: a `mf_test_*` key reads test receipts, a `mf_live_*` key live ones, while the endpoint list itself is shared by both worlds, because an endpoint is configuration and receives events from each.
371
+
372
+ > This is not the same as [verifying webhooks](#verifying-webhooks) below. That proves a request your server just received really came from us; this reads what we sent.
373
+
302
374
  ### Entitlements — ask before you act
303
375
 
304
376
  The one call to make **before** doing work for a customer: "may they use this feature — and how much is left?" A **hard** limit says no here; recording usage afterwards never blocks, so an app that skips this call gets an honest ledger but no enforcement.
@@ -307,7 +379,7 @@ The one call to make **before** doing work for a customer: "may they use this fe
307
379
  const gate = await client.entitlements.check("customer_123", "images.generated");
308
380
 
309
381
  if (!gate.allowed) {
310
- // gate.reason: "hard_limit_reached" | "no_subscription" | "not_in_plan" | "insufficient_credits"
382
+ // gate.reason: "hard_limit_reached" | "no_subscription" | "not_in_plan" | "insufficient_credits" | "spend_cap_reached"
311
383
  return showUpgradePrompt(gate); // gate.used / gate.included / gate.period_end are there for the copy
312
384
  }
313
385
 
@@ -331,6 +403,30 @@ const { entitlements, period_end } = await client.entitlements.get("customer_123
331
403
 
332
404
  Prefer to be told rather than to ask? Subscribe a webhook to `limit.reached` (see below).
333
405
 
406
+ ### The balance portal — a page you don't have to build
407
+
408
+ Mint a short-lived link to a MeterFlow-hosted page showing **one** customer's balance and ledger.
409
+ Call it from your server; send the `url` to that customer.
410
+
411
+ ```typescript
412
+ const link = await client.portal.createSession({
413
+ customer_external_id: "customer_123",
414
+ topup_url: "https://acme.test/billing", // your checkout — "buy more" goes here
415
+ return_url: "https://acme.test/app", // where "done" goes
416
+ expires_in_minutes: 60, // default 60, maximum 1440
417
+ });
418
+
419
+ sendEmail(customer.email, `Your balance: ${link.url}`);
420
+ ```
421
+
422
+ The token in that link reads **that one customer's** balance and ledger, in the environment of the
423
+ key you minted it with, and nothing else: it cannot write, cannot name another customer, and is
424
+ refused everywhere else in the API. It is never your API key — never send that to anyone.
425
+
426
+ > **MeterFlow never takes the payment.** `topup_url` is a link we carry and hand back to the page;
427
+ > what happens there is between you and your own payment provider. Grant the credits with
428
+ > `credits.grant()` when your checkout tells you it was paid.
429
+
334
430
  ## Idempotency — safe retries for writes
335
431
 
336
432
  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:
@@ -345,6 +441,17 @@ await client.credits.deduct(
345
441
 
346
442
  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.
347
443
 
444
+ **A key is bound to the body you first used it with.** MeterFlow stores a fingerprint of the request alongside the key, so a retry only replays when it is genuinely the same request. Reuse the key with a *different* body and the call is refused with `ConflictError` (409) instead of quietly handing back the original record:
445
+
446
+ ```typescript
447
+ await client.credits.grant({ customer_external_id: "customer_123", amount: 100, metadata: {} }, { idempotencyKey: "welcome-123" });
448
+
449
+ // Same key, different amount → ConflictError. Nothing is written, and the first grant stands.
450
+ await client.credits.grant({ customer_external_id: "customer_123", amount: 250, metadata: {} }, { idempotencyKey: "welcome-123" });
451
+ ```
452
+
453
+ That is deliberate: silently returning the first record would turn two different operations into one and report success. One key per operation, reused only to retry that same operation. Keys stay namespaced per environment, so a `mf_test_` and a `mf_live_` key never collide.
454
+
348
455
  ## Automatic retries
349
456
 
350
457
  The SDK retries transient failures for you — exponential backoff with jitter, 3 attempts by default:
@@ -377,7 +484,7 @@ Every error carries:
377
484
 
378
485
  - **`requestId`** — the API's `X-Request-ID` for that call. Include it when contacting support; it pinpoints the exact request in our logs.
379
486
  - **`statusCode`**, **`errorType`**, and **`retryable`** — for programmatic handling and structured logging.
380
- - **`message`** — the API's own sentence (e.g. `Plan limit reached: the Drip plan includes 5,000 usage events per month…`). A `ValidationError` appends its field detail (`Validation failed: amount: must be greater than 0`) and also exposes it structured as **`.fields`** (`{ field, message }[]`).
487
+ - **`message`** — the API's own sentence (e.g. `Plan limit reached: the Drip plan includes 50,000 usage events per month…`). A `ValidationError` appends its field detail (`Validation failed: amount: must be greater than 0`) and also exposes it structured as **`.fields`** (`{ field, message }[]`).
381
488
 
382
489
  ```typescript
383
490
  import { MeterFlowError, InsufficientCreditsError, RateLimitError } from "meterflow";
@@ -440,6 +547,12 @@ Every delivery is a flat JSON object (`Content-Type: application/json`) with two
440
547
  | `subscription.created` | a subscription is created | `customer_id`, `plan_id`, `subscription_id`, `status` |
441
548
  | `subscription.updated` | a subscription is updated | `customer_id`, `subscription_id`, `status` |
442
549
  | `limit.reached` | a customer first reaches a **hard** or **soft** limit's included units this billing period (once per meter per period; metered limits report through `credit.deducted` instead) | `customer_id`, `subscription_id`, `feature` (the meter's `event_name`), `meter_id`, `limit_type`, `included`, `used`, `period_start`, `period_end` |
550
+ | `balance.low` | a spend takes a customer's balance **down through** this endpoint's `low_balance_threshold` — a crossing, so it fires once and not again while they stay below. Raised by both doors that spend: `credits.deduct` and a metered usage charge. **Both crossing events follow from the endpoint's `low_balance_threshold`: set one and the API adds them, clear it and the API removes them — so a line is the only thing to configure.** | `customer_id`, `balance`, `threshold`, `suggested_topup` (the subscription's `topup_amount`, `null` when unset), `transaction_id` |
551
+ | `balance.exhausted` | a balance reaches zero, **or** a request is refused for want of credits (a deduction returning 402, or `entitlements.check` answering `insufficient_credits`). Unlike `balance.low` this repeats: every refused request is its own blocked request | `customer_id`, `balance`, `reason` (`balance_zero` / `insufficient_credits`), `requested_amount` and `feature` where they apply, `transaction_id` on the zero case |
552
+ | `subscription.renewed` | a billing period opens — either a paid period rolling forward or a **trial converting** to its first paid period, told apart by `reason` (the renewer emits no `subscription.updated` for either) | `customer_id`, `subscription_id`, `plan_id`, `status`, `reason` (`period_renewed` / `trial_converted`), `current_period_start`, `current_period_end`, `credits_granted` |
553
+ | `subscription.canceled` | a subscription's status becomes canceled — fired **alongside** `subscription.updated`, once, on the call that actually cancels | `customer_id`, `subscription_id`, `plan_id`, `canceled_at` |
554
+ | `balance.topped_up` | a grant takes a customer's balance **up through** this endpoint's `low_balance_threshold` — the upward mirror of `balance.low`, on the same per-endpoint line, so the endpoint told "low" is the one told "recovered". Subscribed together with `balance.low` by setting the threshold, never on its own | `customer_id`, `balance`, `threshold`, `amount`, `transaction_id` |
555
+ | `cap.reached` | a customer's credit spending reaches their subscription's `spend_cap` for the period — once per subscription per period, whether they landed on the cap or were refused by it. A refusal that still leaves headroom is not a reach and does not fire | `customer_id`, `subscription_id`, `cap`, `spent`, `period_start`, `period_end` |
443
556
 
444
557
  For example, a `credit.granted` delivery:
445
558
 
@@ -509,6 +622,6 @@ await client.usage.record({
509
622
 
510
623
  // Support asks: "what's Ana's situation?"
511
624
  const balance = await client.credits.balance("ana");
512
- const history = await client.credits.transactions("ana", { limit: 20 });
625
+ const { data: history } = await client.credits.transactions("ana");
513
626
  const usage = await client.usage.summary("ana");
514
627
  ```
package/dist/client.d.ts CHANGED
@@ -2,7 +2,9 @@ import { CreditsResource } from "./resources/credits";
2
2
  import { UsageResource } from "./resources/usage";
3
3
  import { SubscriptionsResource } from "./resources/subscriptions";
4
4
  import { PlansResource } from "./resources/plans";
5
+ import { PortalResource } from "./resources/portal";
5
6
  import { EntitlementsResource } from "./resources/entitlements";
7
+ import { WebhooksResource } from "./resources/webhooks";
6
8
  export interface MeterFlowOptions {
7
9
  apiKey: string;
8
10
  baseUrl?: string;
@@ -22,13 +24,18 @@ export declare class MeterFlow {
22
24
  private _usage?;
23
25
  private _subscriptions?;
24
26
  private _plans?;
27
+ private _portal?;
25
28
  private _entitlements?;
29
+ private _webhooks?;
26
30
  constructor(options: MeterFlowOptions);
31
+ get portal(): PortalResource;
27
32
  get credits(): CreditsResource;
28
33
  get usage(): UsageResource;
29
34
  get subscriptions(): SubscriptionsResource;
30
35
  get plans(): PlansResource;
31
36
  get entitlements(): EntitlementsResource;
37
+ /** Read-only: the endpoints registered for this project, and what we sent them. */
38
+ get webhooks(): WebhooksResource;
32
39
  /** @internal — used by resource classes only; not part of the public API */
33
40
  request<T>(method: string, path: string, opts?: RequestOptions): Promise<T>;
34
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAE7C,OAAO,CAAC,QAAQ,CAAC,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,cAAc,CAAC,CAAwB;IAC/C,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,aAAa,CAAC,CAAuB;gBAEjC,OAAO,EAAE,gBAAgB;IAerC,IAAI,OAAO,IAAI,eAAe,CAE7B;IAED,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED,IAAI,aAAa,IAAI,qBAAqB,CAEzC;IAED,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED,IAAI,YAAY,IAAI,oBAAoB,CAEvC;IAED,4EAA4E;IAC5E,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAG5E"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,SAAS;IACpB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAE7C,OAAO,CAAC,QAAQ,CAAC,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,cAAc,CAAC,CAAwB;IAC/C,OAAO,CAAC,MAAM,CAAC,CAAgB;IAC/B,OAAO,CAAC,OAAO,CAAC,CAAiB;IACjC,OAAO,CAAC,aAAa,CAAC,CAAuB;IAC7C,OAAO,CAAC,SAAS,CAAC,CAAmB;gBAEzB,OAAO,EAAE,gBAAgB;IAerC,IAAI,MAAM,IAAI,cAAc,CAE3B;IAED,IAAI,OAAO,IAAI,eAAe,CAE7B;IAED,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED,IAAI,aAAa,IAAI,qBAAqB,CAEzC;IAED,IAAI,KAAK,IAAI,aAAa,CAEzB;IAED,IAAI,YAAY,IAAI,oBAAoB,CAEvC;IAED,mFAAmF;IACnF,IAAI,QAAQ,IAAI,gBAAgB,CAE/B;IAED,4EAA4E;IAC5E,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAG5E"}
package/dist/index.cjs CHANGED
@@ -35,7 +35,7 @@ __export(index_exports, {
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
37
  // package.json
38
- var version = "0.5.1";
38
+ var version = "0.7.0";
39
39
 
40
40
  // src/errors.ts
41
41
  var MeterFlowError = class extends Error {
@@ -245,6 +245,13 @@ var CreditsResource = class {
245
245
  balance(customerId) {
246
246
  return this.client.request("GET", `credits/${encodeURIComponent(customerId)}/balance`);
247
247
  }
248
+ /**
249
+ * One page of the customer's ledger, newest first.
250
+ *
251
+ * The ledger grows with every charge, so it is read a page at a time: `data` holds the rows and
252
+ * `next_cursor` the mark for the page after this one (`null` on the last). Pass it back as
253
+ * `cursor` unchanged — it is opaque, and nothing but this API reads it.
254
+ */
248
255
  transactions(customerId, query) {
249
256
  return this.client.request("GET", `credits/${encodeURIComponent(customerId)}/transactions`, {
250
257
  ...query !== void 0 && { query }
@@ -303,6 +310,12 @@ var SubscriptionsResource = class {
303
310
  ...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
304
311
  });
305
312
  }
313
+ /**
314
+ * One page of the project's subscriptions in this key's environment, newest first.
315
+ *
316
+ * `data` holds the rows and `next_cursor` the mark for the page after this one (`null` on the
317
+ * last). Pass it back as `cursor` unchanged — it is opaque, and nothing but this API reads it.
318
+ */
306
319
  list(query) {
307
320
  return this.client.request("GET", "subscriptions", {
308
321
  ...query !== void 0 && { query }
@@ -339,6 +352,29 @@ var PlansResource = class {
339
352
  }
340
353
  };
341
354
 
355
+ // src/resources/portal.ts
356
+ var PortalResource = class {
357
+ constructor(client) {
358
+ this.client = client;
359
+ }
360
+ /**
361
+ * Mint a link to the hosted balance page for one of your customers.
362
+ *
363
+ * Call this from **your** server and send the `url` to that customer — it carries a short-lived
364
+ * token that can read their balance and their ledger and nothing else, in the environment of the
365
+ * key you called with. It is never your API key, and it can write nothing.
366
+ *
367
+ * The page shows the balance and sends "buy more" to your own checkout (`topup_url`): MeterFlow
368
+ * never takes the payment. `expires_in_minutes` defaults to 60 and may not exceed 1440.
369
+ *
370
+ * The two reads behind the page are **not** on this client by design: they belong to the person
371
+ * holding the link, and this client holds a secret key.
372
+ */
373
+ createSession(body) {
374
+ return this.client.request("POST", "portal/sessions", { body });
375
+ }
376
+ };
377
+
342
378
  // src/resources/entitlements.ts
343
379
  var EntitlementsResource = class {
344
380
  constructor(client) {
@@ -356,6 +392,28 @@ var EntitlementsResource = class {
356
392
  }
357
393
  };
358
394
 
395
+ // src/resources/webhooks.ts
396
+ var WebhooksResource = class {
397
+ constructor(client) {
398
+ this.client = client;
399
+ }
400
+ /** Every endpoint registered for the key's project. Never includes a secret. */
401
+ list() {
402
+ return this.client.request("GET", "webhooks");
403
+ }
404
+ /**
405
+ * One endpoint's delivery attempts, newest first: the payload sent, the HTTP status that came
406
+ * back, how many attempts and when the next retry is due.
407
+ *
408
+ * Capped at the most recent 100, and scoped to the key's own environment — a `mf_test_*` key
409
+ * reads test receipts, a `mf_live_*` key live ones. A webhook belonging to another project
410
+ * answers 404.
411
+ */
412
+ deliveries(webhookId) {
413
+ return this.client.request("GET", `webhooks/${encodeURIComponent(webhookId)}/deliveries`);
414
+ }
415
+ };
416
+
359
417
  // src/client.ts
360
418
  var MeterFlow = class {
361
419
  options;
@@ -363,7 +421,9 @@ var MeterFlow = class {
363
421
  _usage;
364
422
  _subscriptions;
365
423
  _plans;
424
+ _portal;
366
425
  _entitlements;
426
+ _webhooks;
367
427
  constructor(options) {
368
428
  if (!options.apiKey.startsWith("mf_live_") && !options.apiKey.startsWith("mf_test_")) {
369
429
  throw new Error("apiKey must start with mf_live_ or mf_test_");
@@ -378,6 +438,9 @@ var MeterFlow = class {
378
438
  fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
379
439
  };
380
440
  }
441
+ get portal() {
442
+ return this._portal ??= new PortalResource(this);
443
+ }
381
444
  get credits() {
382
445
  return this._credits ??= new CreditsResource(this);
383
446
  }
@@ -393,6 +456,10 @@ var MeterFlow = class {
393
456
  get entitlements() {
394
457
  return this._entitlements ??= new EntitlementsResource(this);
395
458
  }
459
+ /** Read-only: the endpoints registered for this project, and what we sent them. */
460
+ get webhooks() {
461
+ return this._webhooks ??= new WebhooksResource(this);
462
+ }
396
463
  /** @internal — used by resource classes only; not part of the public API */
397
464
  request(method, path, opts) {
398
465
  return httpRequest(this.options, method, path, opts);
package/dist/index.js CHANGED
@@ -35,7 +35,7 @@ __export(index_exports, {
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
37
  // package.json
38
- var version = "0.5.1";
38
+ var version = "0.7.0";
39
39
 
40
40
  // src/errors.ts
41
41
  var MeterFlowError = class extends Error {
@@ -245,6 +245,13 @@ var CreditsResource = class {
245
245
  balance(customerId) {
246
246
  return this.client.request("GET", `credits/${encodeURIComponent(customerId)}/balance`);
247
247
  }
248
+ /**
249
+ * One page of the customer's ledger, newest first.
250
+ *
251
+ * The ledger grows with every charge, so it is read a page at a time: `data` holds the rows and
252
+ * `next_cursor` the mark for the page after this one (`null` on the last). Pass it back as
253
+ * `cursor` unchanged — it is opaque, and nothing but this API reads it.
254
+ */
248
255
  transactions(customerId, query) {
249
256
  return this.client.request("GET", `credits/${encodeURIComponent(customerId)}/transactions`, {
250
257
  ...query !== void 0 && { query }
@@ -303,6 +310,12 @@ var SubscriptionsResource = class {
303
310
  ...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
304
311
  });
305
312
  }
313
+ /**
314
+ * One page of the project's subscriptions in this key's environment, newest first.
315
+ *
316
+ * `data` holds the rows and `next_cursor` the mark for the page after this one (`null` on the
317
+ * last). Pass it back as `cursor` unchanged — it is opaque, and nothing but this API reads it.
318
+ */
306
319
  list(query) {
307
320
  return this.client.request("GET", "subscriptions", {
308
321
  ...query !== void 0 && { query }
@@ -339,6 +352,29 @@ var PlansResource = class {
339
352
  }
340
353
  };
341
354
 
355
+ // src/resources/portal.ts
356
+ var PortalResource = class {
357
+ constructor(client) {
358
+ this.client = client;
359
+ }
360
+ /**
361
+ * Mint a link to the hosted balance page for one of your customers.
362
+ *
363
+ * Call this from **your** server and send the `url` to that customer — it carries a short-lived
364
+ * token that can read their balance and their ledger and nothing else, in the environment of the
365
+ * key you called with. It is never your API key, and it can write nothing.
366
+ *
367
+ * The page shows the balance and sends "buy more" to your own checkout (`topup_url`): MeterFlow
368
+ * never takes the payment. `expires_in_minutes` defaults to 60 and may not exceed 1440.
369
+ *
370
+ * The two reads behind the page are **not** on this client by design: they belong to the person
371
+ * holding the link, and this client holds a secret key.
372
+ */
373
+ createSession(body) {
374
+ return this.client.request("POST", "portal/sessions", { body });
375
+ }
376
+ };
377
+
342
378
  // src/resources/entitlements.ts
343
379
  var EntitlementsResource = class {
344
380
  constructor(client) {
@@ -356,6 +392,28 @@ var EntitlementsResource = class {
356
392
  }
357
393
  };
358
394
 
395
+ // src/resources/webhooks.ts
396
+ var WebhooksResource = class {
397
+ constructor(client) {
398
+ this.client = client;
399
+ }
400
+ /** Every endpoint registered for the key's project. Never includes a secret. */
401
+ list() {
402
+ return this.client.request("GET", "webhooks");
403
+ }
404
+ /**
405
+ * One endpoint's delivery attempts, newest first: the payload sent, the HTTP status that came
406
+ * back, how many attempts and when the next retry is due.
407
+ *
408
+ * Capped at the most recent 100, and scoped to the key's own environment — a `mf_test_*` key
409
+ * reads test receipts, a `mf_live_*` key live ones. A webhook belonging to another project
410
+ * answers 404.
411
+ */
412
+ deliveries(webhookId) {
413
+ return this.client.request("GET", `webhooks/${encodeURIComponent(webhookId)}/deliveries`);
414
+ }
415
+ };
416
+
359
417
  // src/client.ts
360
418
  var MeterFlow = class {
361
419
  options;
@@ -363,7 +421,9 @@ var MeterFlow = class {
363
421
  _usage;
364
422
  _subscriptions;
365
423
  _plans;
424
+ _portal;
366
425
  _entitlements;
426
+ _webhooks;
367
427
  constructor(options) {
368
428
  if (!options.apiKey.startsWith("mf_live_") && !options.apiKey.startsWith("mf_test_")) {
369
429
  throw new Error("apiKey must start with mf_live_ or mf_test_");
@@ -378,6 +438,9 @@ var MeterFlow = class {
378
438
  fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
379
439
  };
380
440
  }
441
+ get portal() {
442
+ return this._portal ??= new PortalResource(this);
443
+ }
381
444
  get credits() {
382
445
  return this._credits ??= new CreditsResource(this);
383
446
  }
@@ -393,6 +456,10 @@ var MeterFlow = class {
393
456
  get entitlements() {
394
457
  return this._entitlements ??= new EntitlementsResource(this);
395
458
  }
459
+ /** Read-only: the endpoints registered for this project, and what we sent them. */
460
+ get webhooks() {
461
+ return this._webhooks ??= new WebhooksResource(this);
462
+ }
396
463
  /** @internal — used by resource classes only; not part of the public API */
397
464
  request(method, path, opts) {
398
465
  return httpRequest(this.options, method, path, opts);
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // package.json
2
- var version = "0.5.1";
2
+ var version = "0.7.0";
3
3
 
4
4
  // src/errors.ts
5
5
  var MeterFlowError = class extends Error {
@@ -209,6 +209,13 @@ var CreditsResource = class {
209
209
  balance(customerId) {
210
210
  return this.client.request("GET", `credits/${encodeURIComponent(customerId)}/balance`);
211
211
  }
212
+ /**
213
+ * One page of the customer's ledger, newest first.
214
+ *
215
+ * The ledger grows with every charge, so it is read a page at a time: `data` holds the rows and
216
+ * `next_cursor` the mark for the page after this one (`null` on the last). Pass it back as
217
+ * `cursor` unchanged — it is opaque, and nothing but this API reads it.
218
+ */
212
219
  transactions(customerId, query) {
213
220
  return this.client.request("GET", `credits/${encodeURIComponent(customerId)}/transactions`, {
214
221
  ...query !== void 0 && { query }
@@ -267,6 +274,12 @@ var SubscriptionsResource = class {
267
274
  ...opts?.idempotencyKey !== void 0 && { idempotencyKey: opts.idempotencyKey }
268
275
  });
269
276
  }
277
+ /**
278
+ * One page of the project's subscriptions in this key's environment, newest first.
279
+ *
280
+ * `data` holds the rows and `next_cursor` the mark for the page after this one (`null` on the
281
+ * last). Pass it back as `cursor` unchanged — it is opaque, and nothing but this API reads it.
282
+ */
270
283
  list(query) {
271
284
  return this.client.request("GET", "subscriptions", {
272
285
  ...query !== void 0 && { query }
@@ -303,6 +316,29 @@ var PlansResource = class {
303
316
  }
304
317
  };
305
318
 
319
+ // src/resources/portal.ts
320
+ var PortalResource = class {
321
+ constructor(client) {
322
+ this.client = client;
323
+ }
324
+ /**
325
+ * Mint a link to the hosted balance page for one of your customers.
326
+ *
327
+ * Call this from **your** server and send the `url` to that customer — it carries a short-lived
328
+ * token that can read their balance and their ledger and nothing else, in the environment of the
329
+ * key you called with. It is never your API key, and it can write nothing.
330
+ *
331
+ * The page shows the balance and sends "buy more" to your own checkout (`topup_url`): MeterFlow
332
+ * never takes the payment. `expires_in_minutes` defaults to 60 and may not exceed 1440.
333
+ *
334
+ * The two reads behind the page are **not** on this client by design: they belong to the person
335
+ * holding the link, and this client holds a secret key.
336
+ */
337
+ createSession(body) {
338
+ return this.client.request("POST", "portal/sessions", { body });
339
+ }
340
+ };
341
+
306
342
  // src/resources/entitlements.ts
307
343
  var EntitlementsResource = class {
308
344
  constructor(client) {
@@ -320,6 +356,28 @@ var EntitlementsResource = class {
320
356
  }
321
357
  };
322
358
 
359
+ // src/resources/webhooks.ts
360
+ var WebhooksResource = class {
361
+ constructor(client) {
362
+ this.client = client;
363
+ }
364
+ /** Every endpoint registered for the key's project. Never includes a secret. */
365
+ list() {
366
+ return this.client.request("GET", "webhooks");
367
+ }
368
+ /**
369
+ * One endpoint's delivery attempts, newest first: the payload sent, the HTTP status that came
370
+ * back, how many attempts and when the next retry is due.
371
+ *
372
+ * Capped at the most recent 100, and scoped to the key's own environment — a `mf_test_*` key
373
+ * reads test receipts, a `mf_live_*` key live ones. A webhook belonging to another project
374
+ * answers 404.
375
+ */
376
+ deliveries(webhookId) {
377
+ return this.client.request("GET", `webhooks/${encodeURIComponent(webhookId)}/deliveries`);
378
+ }
379
+ };
380
+
323
381
  // src/client.ts
324
382
  var MeterFlow = class {
325
383
  options;
@@ -327,7 +385,9 @@ var MeterFlow = class {
327
385
  _usage;
328
386
  _subscriptions;
329
387
  _plans;
388
+ _portal;
330
389
  _entitlements;
390
+ _webhooks;
331
391
  constructor(options) {
332
392
  if (!options.apiKey.startsWith("mf_live_") && !options.apiKey.startsWith("mf_test_")) {
333
393
  throw new Error("apiKey must start with mf_live_ or mf_test_");
@@ -342,6 +402,9 @@ var MeterFlow = class {
342
402
  fetch: options.fetch ?? globalThis.fetch.bind(globalThis)
343
403
  };
344
404
  }
405
+ get portal() {
406
+ return this._portal ??= new PortalResource(this);
407
+ }
345
408
  get credits() {
346
409
  return this._credits ??= new CreditsResource(this);
347
410
  }
@@ -357,6 +420,10 @@ var MeterFlow = class {
357
420
  get entitlements() {
358
421
  return this._entitlements ??= new EntitlementsResource(this);
359
422
  }
423
+ /** Read-only: the endpoints registered for this project, and what we sent them. */
424
+ get webhooks() {
425
+ return this._webhooks ??= new WebhooksResource(this);
426
+ }
360
427
  /** @internal — used by resource classes only; not part of the public API */
361
428
  request(method, path, opts) {
362
429
  return httpRequest(this.options, method, path, opts);
@@ -3,6 +3,7 @@ import type { components } from "../types/openapi";
3
3
  type CreditGrantRequest = components["schemas"]["CreditGrantRequest"];
4
4
  type CreditDeductRequest = components["schemas"]["CreditDeductRequest"];
5
5
  type CreditTransactionResponse = components["schemas"]["CreditTransactionResponse"];
6
+ type CreditTransactionPage = components["schemas"]["CreditTransactionPage"];
6
7
  type CreditBalanceResponse = components["schemas"]["CreditBalanceResponse"];
7
8
  export interface GrantOptions {
8
9
  idempotencyKey?: string;
@@ -10,9 +11,10 @@ export interface GrantOptions {
10
11
  export interface DeductOptions {
11
12
  idempotencyKey?: string;
12
13
  }
13
- export interface TransactionsQuery {
14
- page?: number;
14
+ /** One page of a list: how many rows, and where to carry on from. */
15
+ export interface PageQuery {
15
16
  limit?: number;
17
+ cursor?: string;
16
18
  }
17
19
  export declare class CreditsResource {
18
20
  private readonly client;
@@ -20,7 +22,14 @@ export declare class CreditsResource {
20
22
  grant(body: CreditGrantRequest, opts?: GrantOptions): Promise<CreditTransactionResponse>;
21
23
  deduct(body: CreditDeductRequest, opts?: DeductOptions): Promise<CreditTransactionResponse>;
22
24
  balance(customerId: string): Promise<CreditBalanceResponse>;
23
- transactions(customerId: string, query?: TransactionsQuery): Promise<CreditTransactionResponse[]>;
25
+ /**
26
+ * One page of the customer's ledger, newest first.
27
+ *
28
+ * The ledger grows with every charge, so it is read a page at a time: `data` holds the rows and
29
+ * `next_cursor` the mark for the page after this one (`null` on the last). Pass it back as
30
+ * `cursor` unchanged — it is opaque, and nothing but this API reads it.
31
+ */
32
+ transactions(customerId: string, query?: PageQuery): Promise<CreditTransactionPage>;
24
33
  }
25
34
  export {};
26
35
  //# sourceMappingURL=credits.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"credits.d.ts","sourceRoot":"","sources":["../../../src/resources/credits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,KAAK,kBAAkB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,CAAC;AACtE,KAAK,mBAAmB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC;AACxE,KAAK,yBAAyB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,2BAA2B,CAAC,CAAC;AACpF,KAAK,qBAAqB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,uBAAuB,CAAC,CAAC;AAE5E,MAAM,WAAW,YAAY;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS;IAE9C,KAAK,CAAC,IAAI,EAAE,kBAAkB,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAOxF,MAAM,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAO3F,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI3D,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,yBAAyB,EAAE,CAAC;CAKlG"}
1
+ {"version":3,"file":"credits.d.ts","sourceRoot":"","sources":["../../../src/resources/credits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,KAAK,kBAAkB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,CAAC;AACtE,KAAK,mBAAmB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC;AACxE,KAAK,yBAAyB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,2BAA2B,CAAC,CAAC;AACpF,KAAK,qBAAqB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,uBAAuB,CAAC,CAAC;AAC5E,KAAK,qBAAqB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,uBAAuB,CAAC,CAAC;AAE5E,MAAM,WAAW,YAAY;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS;IAE9C,KAAK,CAAC,IAAI,EAAE,kBAAkB,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAOxF,MAAM,CAAC,IAAI,EAAE,mBAAmB,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAO3F,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAI3D;;;;;;OAMG;IACH,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAKpF"}
@@ -0,0 +1,24 @@
1
+ import type { MeterFlow } from "../client";
2
+ import type { components } from "../types/openapi";
3
+ type PortalLinkRequest = components["schemas"]["PortalLinkRequest"];
4
+ type PortalLinkResponse = components["schemas"]["PortalLinkResponse"];
5
+ export declare class PortalResource {
6
+ private readonly client;
7
+ constructor(client: MeterFlow);
8
+ /**
9
+ * Mint a link to the hosted balance page for one of your customers.
10
+ *
11
+ * Call this from **your** server and send the `url` to that customer — it carries a short-lived
12
+ * token that can read their balance and their ledger and nothing else, in the environment of the
13
+ * key you called with. It is never your API key, and it can write nothing.
14
+ *
15
+ * The page shows the balance and sends "buy more" to your own checkout (`topup_url`): MeterFlow
16
+ * never takes the payment. `expires_in_minutes` defaults to 60 and may not exceed 1440.
17
+ *
18
+ * The two reads behind the page are **not** on this client by design: they belong to the person
19
+ * holding the link, and this client holds a secret key.
20
+ */
21
+ createSession(body: PortalLinkRequest): Promise<PortalLinkResponse>;
22
+ }
23
+ export {};
24
+ //# sourceMappingURL=portal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../../src/resources/portal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,KAAK,iBAAiB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,mBAAmB,CAAC,CAAC;AACpE,KAAK,kBAAkB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,oBAAoB,CAAC,CAAC;AAEtE,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS;IAE9C;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAGpE"}
@@ -3,17 +3,28 @@ import type { components } from "../types/openapi";
3
3
  type SubscriptionCreateRequest = components["schemas"]["SubscriptionCreateRequest"];
4
4
  type SubscriptionUpdateRequest = components["schemas"]["SubscriptionUpdateRequest"];
5
5
  type SubscriptionResponse = components["schemas"]["SubscriptionResponse"];
6
+ type SubscriptionPage = components["schemas"]["SubscriptionPage"];
6
7
  export interface CreateOptions {
7
8
  idempotencyKey?: string;
8
9
  }
9
10
  export interface ListQuery {
10
11
  customer_id?: string;
12
+ /** Rows per page, 1–200 (50 by default). */
13
+ limit?: number;
14
+ /** `next_cursor` from the previous page; omit for the first. */
15
+ cursor?: string;
11
16
  }
12
17
  export declare class SubscriptionsResource {
13
18
  private readonly client;
14
19
  constructor(client: MeterFlow);
15
20
  create(body: SubscriptionCreateRequest, opts?: CreateOptions): Promise<SubscriptionResponse>;
16
- list(query?: ListQuery): Promise<SubscriptionResponse[]>;
21
+ /**
22
+ * One page of the project's subscriptions in this key's environment, newest first.
23
+ *
24
+ * `data` holds the rows and `next_cursor` the mark for the page after this one (`null` on the
25
+ * last). Pass it back as `cursor` unchanged — it is opaque, and nothing but this API reads it.
26
+ */
27
+ list(query?: ListQuery): Promise<SubscriptionPage>;
17
28
  get(subscriptionId: string): Promise<SubscriptionResponse>;
18
29
  update(subscriptionId: string, body: SubscriptionUpdateRequest, opts?: {
19
30
  idempotencyKey?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../../../src/resources/subscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,KAAK,yBAAyB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,2BAA2B,CAAC,CAAC;AACpF,KAAK,yBAAyB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,2BAA2B,CAAC,CAAC;AACpF,KAAK,oBAAoB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,CAAC;AAE1E,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS;IAE9C,MAAM,CAAC,IAAI,EAAE,yBAAyB,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAO5F,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAMxD,GAAG,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAI1D,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,EAAE,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAOlI,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG9C"}
1
+ {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../../../src/resources/subscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,KAAK,yBAAyB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,2BAA2B,CAAC,CAAC;AACpF,KAAK,yBAAyB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,2BAA2B,CAAC,CAAC;AACpF,KAAK,oBAAoB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,sBAAsB,CAAC,CAAC;AAC1E,KAAK,gBAAgB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,kBAAkB,CAAC,CAAC;AAElE,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS;IAE9C,MAAM,CAAC,IAAI,EAAE,yBAAyB,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAO5F;;;;;OAKG;IACH,IAAI,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAMlD,GAAG,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAI1D,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,EAAE,IAAI,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAOlI,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG9C"}
@@ -0,0 +1,31 @@
1
+ import type { MeterFlow } from "../client";
2
+ import type { components } from "../types/openapi";
3
+ type WebhookResponse = components["schemas"]["WebhookResponse"];
4
+ type WebhookDeliveryResponse = components["schemas"]["WebhookDeliveryResponse"];
5
+ /**
6
+ * Webhook **receipts** — read-only on purpose.
7
+ *
8
+ * An endpoint is registered, edited and deleted in the dashboard, where the signing secret is
9
+ * shown once and never again. What an SDK consumer needs at 3am is the other half: which
10
+ * endpoints exist, and what MeterFlow actually sent them. That is this.
11
+ *
12
+ * Not to be confused with `meterflow/webhook`, which verifies the signature on a webhook your
13
+ * server has just received. This reads what we sent; that proves we sent it.
14
+ */
15
+ export declare class WebhooksResource {
16
+ private readonly client;
17
+ constructor(client: MeterFlow);
18
+ /** Every endpoint registered for the key's project. Never includes a secret. */
19
+ list(): Promise<WebhookResponse[]>;
20
+ /**
21
+ * One endpoint's delivery attempts, newest first: the payload sent, the HTTP status that came
22
+ * back, how many attempts and when the next retry is due.
23
+ *
24
+ * Capped at the most recent 100, and scoped to the key's own environment — a `mf_test_*` key
25
+ * reads test receipts, a `mf_live_*` key live ones. A webhook belonging to another project
26
+ * answers 404.
27
+ */
28
+ deliveries(webhookId: string): Promise<WebhookDeliveryResponse[]>;
29
+ }
30
+ export {};
31
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../../src/resources/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,KAAK,eAAe,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,iBAAiB,CAAC,CAAC;AAChE,KAAK,uBAAuB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,yBAAyB,CAAC,CAAC;AAEhF;;;;;;;;;GASG;AACH,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,SAAS;IAE9C,gFAAgF;IAChF,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAIlC;;;;;;;OAOG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;CAGlE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meterflow",
3
- "version": "0.5.1",
3
+ "version": "0.7.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",