@clocknext/sdk 0.10.0 → 0.12.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/dist/index.d.ts CHANGED
@@ -139,6 +139,11 @@ interface OutcomeSignal extends SignalBase {
139
139
  * Set `true` on the LAST step signal to declare the run finished — completion
140
140
  * is never inferred from step coverage. Sending it twice is a safe no-op and
141
141
  * can't bill again.
142
+ *
143
+ * This closes the RUN only. If the signal also carries a `composite`, that
144
+ * occurrence stays open and unbilled until you call
145
+ * `signals.completeComposite()` — the two completions are separate, and
146
+ * neither implies the other.
142
147
  */
143
148
  complete?: boolean;
144
149
  /** Optional composite this signal belongs to. See {@link CompositeTag}. */
@@ -176,7 +181,7 @@ interface SerializedOutcomeAttribution {
176
181
  /** The customer's remaining balance for the dimension a signal metered, AFTER
177
182
  * the signal was applied — update an on-screen meter straight from the record
178
183
  * response. Populated on recorded (inline / `{ wait: true }`) signals; `null`
179
- * on dry runs and on read-back. */
184
+ * on read-back. */
180
185
  interface UsageBalance {
181
186
  dimension: "credit" | "outcome" | "wallet";
182
187
  /** Post-signal remaining. `null` when the meter is uncapped — an ARREAR
@@ -203,7 +208,7 @@ interface SerializedUsageLog {
203
208
  status: SignalStatus;
204
209
  appliedRules: SerializedRuleApplication[];
205
210
  outcome: SerializedOutcomeAttribution | null;
206
- /** Post-signal balance for the metered dimension. Null on dry runs and
211
+ /** Post-signal balance for the metered dimension. Null on
207
212
  * read-back. */
208
213
  balance: UsageBalance | null;
209
214
  createdAt: string;
@@ -228,9 +233,44 @@ interface SerializedUsageLog {
228
233
  * When the server has no queue configured it prices inline instead and returns
229
234
  * `queued: false` with a populated `usageLog`, validating synchronously.
230
235
  */
236
+ /**
237
+ * How far a metering call actually got. See {@link TrackResult.status} — the
238
+ * three states differ in whether the signal can still be lost, and in whether
239
+ * it has been billed.
240
+ */
241
+ type SignalDelivery = "buffered" | "accepted" | "recorded" | "dropped";
231
242
  interface TrackResult {
232
- /** True when the signal has not been priced yet — either buffered locally
233
- * (async mode) or accepted for asynchronous processing by the server. */
243
+ /**
244
+ * How far this signal has actually got. Branch on this, not on `queued`.
245
+ *
246
+ * `"buffered"` — held in THIS process's memory and not yet sent. Lost if
247
+ * the process exits first, so `flush()`/`close()` before it
248
+ * can. No `rawId` exists yet.
249
+ * `"accepted"` — the server has written it to its durable inbox (`202`)
250
+ * and will price it shortly. It can no longer be lost, but
251
+ * it is not billed yet and `usageLog` is null. A rejection
252
+ * discovered during pricing will NOT come back to you here —
253
+ * read it back with `signals.list()`.
254
+ * `"recorded"` — the server priced it inline and `usageLog` is populated.
255
+ * `"dropped"` — the SDK threw it away without sending: the client is
256
+ * closed, or the local queue is full. `onDrop` fired with
257
+ * the reason. This state exists because the alternative was
258
+ * reporting `"buffered"` for a signal that was discarded —
259
+ * a success-shaped answer to a lost billable event.
260
+ *
261
+ * Note what none of these mean: "waited for a price". Even `{ wait: true }`
262
+ * waits only for the server to ACCEPT the signal, never for it to finish
263
+ * pricing — holding a request thread open for a billing round-trip is
264
+ * exactly the cost this design exists to avoid.
265
+ */
266
+ status: SignalDelivery;
267
+ /**
268
+ * True for both `"buffered"` and `"accepted"` — i.e. "not priced yet".
269
+ *
270
+ * Kept for compatibility, but it collapses two states with opposite failure
271
+ * behaviour (one is lost when the process dies, the other cannot be) into
272
+ * one boolean, which is why {@link TrackResult.status} exists. Prefer that.
273
+ */
234
274
  queued: boolean;
235
275
  /** The computed usage log. Non-null ONLY when the server priced the signal
236
276
  * inline (`queued: false`); null on a `202` hand-off and absent while the
@@ -416,7 +456,15 @@ type BillingCycle = "MONTHLY" | "QUARTERLY" | "SEMI_ANNUAL" | "YEARLY" | "EVERY_
416
456
  type PlanType = "WALLET" | "BUNDLE";
417
457
  type PlanKind = "FLAT_PRICE" | "PAY_AS_YOU_GO" | "PAY_PER_SEAT";
418
458
  /** A plan component's meter type. */
419
- type ComponentType = "WALLET" | "CREDIT" | "OUTCOME" | "UNIT" | "FLAT";
459
+ /**
460
+ * A plan component's kind.
461
+ *
462
+ * `PRICING_METRIC` is a COMPOSITE component — the server's wire name for it,
463
+ * kept verbatim because that is the string you will actually receive and must
464
+ * send back. It was missing here, which made a `switch` over `PlanComponent.type`
465
+ * look exhaustive to TypeScript while silently skipping every composite.
466
+ */
467
+ type ComponentType = "WALLET" | "CREDIT" | "OUTCOME" | "UNIT" | "FLAT" | "PRICING_METRIC";
420
468
  /** ADVANCE bills up-front for the cycle; ARREAR bills for what was consumed. */
421
469
  type BillingMode = "ADVANCE" | "ARREAR";
422
470
  /** Prepaid USD wallet. `amount` is the funded balance (required). Max one
@@ -536,6 +584,11 @@ interface PlanComponent {
536
584
  creditId: string | null;
537
585
  outcomeId: string | null;
538
586
  unitId: string | null;
587
+ /** The composite this component sells, on a `PRICING_METRIC` component.
588
+ * Without it a plan read back could not be written back: the write shape
589
+ * ({@link CompositeComponentInput}) requires this id, so a read-modify-write
590
+ * silently dropped every composite component. */
591
+ pricingMetricId: string | null;
539
592
  targetName: string | null;
540
593
  quantity: number | null;
541
594
  amount: number | null;
@@ -550,6 +603,9 @@ interface Plan {
550
603
  cost: number;
551
604
  /** Signed adjustment applied to the plan's headline price (default 0). */
552
605
  priceAdjustment: number;
606
+ /** When true, ARREAR consumption is funded from the customer's wallet as it
607
+ * happens instead of being invoiced at cycle end. All-or-nothing per plan. */
608
+ walletFundedArrear: boolean;
553
609
  currencyCode: string;
554
610
  isActive: boolean;
555
611
  components: PlanComponent[];
@@ -591,6 +647,8 @@ interface Purchase {
591
647
  hasCredit: boolean;
592
648
  hasOutcome: boolean;
593
649
  hasUnit: boolean;
650
+ /** True when the purchased plan sells at least one composite. */
651
+ hasComposite: boolean;
594
652
  billingCycle: BillingCycle;
595
653
  cost: number;
596
654
  currencyCode: string;
@@ -798,9 +856,16 @@ interface UpdateMemberInput {
798
856
  email?: string;
799
857
  }
800
858
  type WalletTransactionType = "CREDIT" | "DEBIT";
859
+ /** What produced a wallet entry. `null` on rows written before the column
860
+ * existed. */
861
+ type WalletTransactionSource = "TOPUP" | "USAGE" | "SETTLE" | "ADJUSTMENT";
862
+ /** The entitlement kinds a wallet-funded metering debit can name. */
863
+ type WalletEntitlementKind = "CREDIT" | "OUTCOME" | "UNIT";
801
864
  interface WalletTransaction {
802
865
  id: string;
803
866
  type: WalletTransactionType;
867
+ /** What produced the entry. Null on rows older than the column. */
868
+ source: WalletTransactionSource | null;
804
869
  amount: number;
805
870
  amountUsd: number;
806
871
  currencyCode: string;
@@ -809,6 +874,19 @@ interface WalletTransaction {
809
874
  batchId: string | null;
810
875
  createdAt: string;
811
876
  createdByName: string | null;
877
+ /** Set only on a wallet-funded metering debit: which kind of entitlement was
878
+ * consumed. Null on top-ups and manual adjustments. */
879
+ entitlementType: WalletEntitlementKind | null;
880
+ /** Id of the credit / outcome / unit consumed. Null unless
881
+ * `entitlementType` is set. */
882
+ targetId: string | null;
883
+ /** Its name at the time of the debit. */
884
+ targetName: string | null;
885
+ /** The signal this debit paid for (credit / outcome). In wallet-funded mode
886
+ * no invoice exists, so this is the only trail from a charge to its cause. */
887
+ usageLogId: string | null;
888
+ /** The unit event this debit paid for. */
889
+ unitUsageLogId: string | null;
812
890
  }
813
891
  interface WalletList {
814
892
  balanceUsd: number;
@@ -959,14 +1037,14 @@ interface Outcome {
959
1037
  isActive: boolean;
960
1038
  marginPercent: number;
961
1039
  pricePerOutcome: number;
962
- createdAt: string;
963
- steps?: {
964
- name: string;
965
- agentKey: string;
966
- }[];
967
1040
  }
968
1041
  /** `outcomes.get()` — adds steps plus in-flight/completed stats. */
969
1042
  interface OutcomeDetail extends Outcome {
1043
+ /** Only the DETAIL row carries these — `outcomes.list()` returns neither.
1044
+ * They were previously declared on {@link Outcome} itself, where
1045
+ * `createdAt` was typed as a required `string` that is always `undefined`
1046
+ * in practice. */
1047
+ createdAt: string;
970
1048
  createdByName: string | null;
971
1049
  steps: {
972
1050
  name: string;
@@ -1130,7 +1208,11 @@ interface UnitInput {
1130
1208
  * a bare `{ unitUsage }`. Branch on `queued`; never assume `unitUsage`.
1131
1209
  */
1132
1210
  interface UnitTrackResult {
1133
- /** True when the report was accepted but not yet priced (`202`). */
1211
+ /** How far the report got. Never `"buffered"` — unit reports are always sent
1212
+ * immediately, not queued client-side. See {@link TrackResult.status}. */
1213
+ status: Extract<SignalDelivery, "accepted" | "recorded">;
1214
+ /** True when the report was accepted but not yet priced (`202`). Prefer
1215
+ * {@link UnitTrackResult.status}. */
1134
1216
  queued: boolean;
1135
1217
  /** The priced unit event. Non-null ONLY when the server priced inline. */
1136
1218
  unitUsage?: SerializedUnitUsage | null;
@@ -1227,10 +1309,16 @@ interface CreateCompositeInput {
1227
1309
  * lands. That case answers with no `status`, only a `rawId` to follow.
1228
1310
  */
1229
1311
  interface CompositeCompletionResult {
1230
- /** True when the ping was accepted but not yet applied (`202`). */
1312
+ /** How far the ping got. Never `"buffered"` — completions are always sent
1313
+ * immediately. `"recorded"` means the occurrence was closed inline. See
1314
+ * {@link TrackResult.status}. */
1315
+ delivery: Extract<SignalDelivery, "accepted" | "recorded">;
1316
+ /** True when the ping was accepted but not yet applied (`202`). Prefer
1317
+ * {@link CompositeCompletionResult.delivery}. */
1231
1318
  queued: boolean;
1232
1319
  /** `COMPLETED` on the first close, `ALREADY_COMPLETED` on a replay. Absent
1233
- * when the ping was queued or parked. */
1320
+ * when the ping was queued or parked. Distinct from `delivery`: this is the
1321
+ * server's verdict on the OCCURRENCE, that is how far the REQUEST got. */
1234
1322
  status?: "COMPLETED" | "ALREADY_COMPLETED";
1235
1323
  /** The composite that was closed. Null on the replay of a historical row
1236
1324
  * whose occurrence can no longer be read back. */
@@ -1281,10 +1369,14 @@ interface UnitUsageList {
1281
1369
  }
1282
1370
  type ThresholdKind = "CREDIT" | "OUTCOME" | "WALLET";
1283
1371
  interface Webhook {
1372
+ id: string;
1284
1373
  kind: ThresholdKind;
1285
1374
  threshold: number;
1286
1375
  url: string;
1287
1376
  isActive: boolean;
1377
+ createdAt: string;
1378
+ updatedAt: string;
1379
+ createdByName: string | null;
1288
1380
  }
1289
1381
  interface UpsertWebhookInput {
1290
1382
  kind: ThresholdKind;
@@ -1300,7 +1392,16 @@ interface Logger {
1300
1392
  warn?: (...args: unknown[]) => void;
1301
1393
  error?: (...args: unknown[]) => void;
1302
1394
  }
1303
- type DropReason = "queue_full" | "send_failed";
1395
+ /**
1396
+ * Why a signal was dropped without being billed.
1397
+ *
1398
+ * `queue_full` — the local buffer hit `batch.maxQueueSize`; never sent.
1399
+ * `closed` — tracked after `close()`; never sent.
1400
+ * `send_failed` — sent, and permanently rejected or unreachable. This is the
1401
+ * only reason that also fires `onError`, which carries the
1402
+ * error (and its `idempotencyKey`, for a safe replay).
1403
+ */
1404
+ type DropReason = "queue_full" | "closed" | "send_failed";
1304
1405
  interface ClockNextConfig {
1305
1406
  /** The org API key (`cnk_…`). Required. */
1306
1407
  apiKey: string;
@@ -1391,6 +1492,15 @@ interface RequestOptions {
1391
1492
  * opt in explicitly.
1392
1493
  */
1393
1494
  retry?: boolean;
1495
+ /**
1496
+ * The idempotency key carried in this request's body, for metering calls.
1497
+ *
1498
+ * Copied onto any error thrown, so a caller handling a permanent failure can
1499
+ * replay the signal with the SAME key instead of minting a fresh one — which
1500
+ * would defeat server-side dedup and bill twice. See
1501
+ * `ClockNextError.idempotencyKey`.
1502
+ */
1503
+ idempotencyKey?: string;
1394
1504
  }
1395
1505
  /**
1396
1506
  * The single HTTP seam. Builds the authenticated request, enforces a timeout,
@@ -1428,7 +1538,9 @@ declare class Composites {
1428
1538
  constructor(transport: Transport);
1429
1539
  /** List composites. Pass `{ active: true }` for only the ones a signal can
1430
1540
  * still be tagged with — the default list INCLUDES archived ones, which
1431
- * keep every row they already own. */
1541
+ * keep every row they already own.
1542
+ * `{ active: false }` returns only archived ones; omitting it returns
1543
+ * both. See `activeQuery` — the API itself only supports `true`. */
1432
1544
  list(params?: {
1433
1545
  active?: boolean;
1434
1546
  }): Promise<Composite[]>;
@@ -1444,7 +1556,9 @@ declare class Composites {
1444
1556
  declare class Credits {
1445
1557
  private readonly transport;
1446
1558
  constructor(transport: Transport);
1447
- /** List credit types. Pass `{ active: true }` to only return active ones. */
1559
+ /** List credit types. Pass `{ active: true }` to only return active ones.
1560
+ * `{ active: false }` returns only archived ones; omitting it returns
1561
+ * both. See `activeQuery` — the API itself only supports `true`. */
1448
1562
  list(params?: {
1449
1563
  active?: boolean;
1450
1564
  }): Promise<Credit[]>;
@@ -1593,7 +1707,9 @@ declare class Invoices {
1593
1707
  declare class Outcomes {
1594
1708
  private readonly transport;
1595
1709
  constructor(transport: Transport);
1596
- /** List outcome types. Pass `{ active: true }` to only return active ones. */
1710
+ /** List outcome types. Pass `{ active: true }` to only return active ones.
1711
+ * `{ active: false }` returns only archived ones; omitting it returns
1712
+ * both. See `activeQuery` — the API itself only supports `true`. */
1597
1713
  list(params?: {
1598
1714
  active?: boolean;
1599
1715
  }): Promise<Outcome[]>;
@@ -1633,7 +1749,9 @@ declare class Payments {
1633
1749
  declare class Plans {
1634
1750
  private readonly transport;
1635
1751
  constructor(transport: Transport);
1636
- /** List plans, oldest-first. Pass `{ active: true }` to only return active plans. */
1752
+ /** List plans, oldest-first. Pass `{ active: true }` to only return active plans.
1753
+ * `{ active: false }` returns only archived ones; omitting it returns
1754
+ * both. See `activeQuery` — the API itself only supports `true`. */
1637
1755
  list(params?: {
1638
1756
  active?: boolean;
1639
1757
  }): Promise<Plan[]>;
@@ -1725,13 +1843,30 @@ declare class Flusher {
1725
1843
  private draining;
1726
1844
  private closed;
1727
1845
  constructor(transport: Transport, cfg: ResolvedConfig);
1728
- /** Buffer one signal. Drops (with onDrop) when closed or the queue is full. */
1729
- enqueue(item: QueuedSignal): void;
1846
+ /**
1847
+ * Buffer one signal. Drops (with onDrop) when closed or the queue is full.
1848
+ * Returns whether it was actually buffered, so `track()` can report a drop
1849
+ * honestly instead of answering "buffered" for a signal it threw away.
1850
+ */
1851
+ enqueue(item: QueuedSignal): boolean;
1730
1852
  /** Arm the interval timer if it isn't already running. */
1731
1853
  private arm;
1854
+ /** Cancel the interval timer if one is armed. */
1855
+ private clearTimer;
1732
1856
  /**
1733
- * Send everything currently buffered. Overlapping calls share the single
1734
- * in-progress drain, so total in-flight sends never exceed `maxConcurrency`.
1857
+ * Send everything buffered at the moment this resolves.
1858
+ *
1859
+ * Overlapping calls CHAIN onto the in-flight drain rather than sharing it.
1860
+ * Sharing looks equivalent and is not: a drain that has already passed its
1861
+ * final `size() > 0` check cannot see a signal enqueued a moment later, so
1862
+ * handing that same promise back would resolve `flush()` — and therefore
1863
+ * `close()` — with the buffer non-empty. Since `close()` has already set
1864
+ * `closed`, `arm()` will not re-arm, and the timer it did arm is `unref`'d,
1865
+ * so the process is free to exit and that signal is simply lost. Chaining
1866
+ * guarantees at least one full drain begins after this call.
1867
+ *
1868
+ * Concurrency is unaffected: drains run one after another, and each one
1869
+ * sends at most `maxConcurrency` signals at a time.
1735
1870
  */
1736
1871
  flush(): Promise<void>;
1737
1872
  /** Drain the buffer in concurrency-bounded waves until it's empty. */
@@ -1765,18 +1900,24 @@ declare function signalToWire(signal: Signal): Record<string, unknown>;
1765
1900
  * The `signals` resource: record usage signals and read them back.
1766
1901
  *
1767
1902
  * In async mode (default), `track()` buffers the signal locally and returns
1768
- * immediately with `{ queued: true }`. Pass `{ wait: true }` (or construct the
1769
- * client in `sync` mode) to await the request.
1903
+ * immediately with `{ status: "buffered" }`. Pass `{ wait: true }` (or
1904
+ * construct the client in `sync` mode) to await the request instead.
1905
+ *
1906
+ * Awaiting the request does NOT mean waiting for a price, and deliberately so.
1907
+ * The server writes every signal to a durable inbox and hands it to a worker,
1908
+ * answering `202` — so `track()` resolves `{ status: "accepted", messageId,
1909
+ * rawId, usageLog: null }`: impossible to lose, not yet billed. Only when the
1910
+ * server prices inline do you get `{ status: "recorded", usageLog }`. Branch on
1911
+ * `status`; never assume `usageLog`.
1770
1912
  *
1771
- * Awaiting the request does NOT guarantee a priced result. The server writes
1772
- * every signal to a durable inbox and then hands it to a worker, so it answers
1773
- * `202` and `track()` resolves `{ queued: true, messageId, rawId, usageLog:
1774
- * null }` — accepted and impossible to lose, but not yet billed. Only when the
1775
- * server prices inline do you get `{ queued: false, usageLog }`. Branch on
1776
- * `queued`; never assume `usageLog`.
1913
+ * Nothing here ever blocks a request thread on billing. That is the point: a
1914
+ * meter that makes the caller wait for pricing has moved your billing system
1915
+ * onto your product's critical path.
1777
1916
  *
1778
- * `verify()` is the way to validate and price a signal synchronously, and
1779
- * `list()` is how you confirm one actually landed.
1917
+ * Because of that, a signal the server later REJECTS (unknown `agentKey`, a
1918
+ * model the org has not enabled, no active plan) cannot come back through this
1919
+ * promise. It surfaces on the `onError` hook, and `list()` is how you confirm
1920
+ * one actually landed — ingestion being asynchronous, the only way to.
1780
1921
  */
1781
1922
  declare class Signals {
1782
1923
  private readonly transport;
@@ -1794,17 +1935,6 @@ declare class Signals {
1794
1935
  track(signal: Signal, opts?: {
1795
1936
  wait?: boolean;
1796
1937
  }): Promise<TrackResult>;
1797
- /**
1798
- * Validate + price a signal WITHOUT recording it (server `dryRun`). Returns
1799
- * the projected usage log so an integration can preflight before sending real
1800
- * traffic — no inbox row, wallet / outcome writes, or webhooks fire.
1801
- *
1802
- * Only the model-priced kinds support this. A dry run is not offered for unit
1803
- * reports or composite completions, and the server REJECTS the flag on those
1804
- * routes rather than accepting and ignoring it — which is why there is no
1805
- * dry-run path through `unit()` or `completeComposite()`.
1806
- */
1807
- verify(signal: Signal): Promise<SerializedUsageLog | null>;
1808
1938
  /** Meter a named credit. `POST /api/v1/signal/credit`. */
1809
1939
  credit(input: CreditInput, opts?: {
1810
1940
  wait?: boolean;
@@ -1854,9 +1984,13 @@ declare class Signals {
1854
1984
  * arrive in any order and the occurrence stays open and unbilled until you
1855
1985
  * say otherwise. Put it on the line before your own `return`.
1856
1986
  *
1857
- * An OUTCOME never needs this — its own `complete: true` closes its run and
1858
- * the matching occurrence together. Credits and units have no "done" signal
1859
- * of their own, so this is theirs.
1987
+ * EVERY composite needs this, outcome-backed ones included. An outcome's own
1988
+ * `complete: true` closes its RUN and nothing else — it does not close the
1989
+ * composite occurrence the run was tagged into, whatever mix of credits,
1990
+ * outcomes and units that composite wraps. (Earlier versions of this SDK
1991
+ * documented the opposite. Believing it means the occurrence stays ONGOING
1992
+ * forever and is never billed, so if you tag outcome signals with a
1993
+ * `composite`, you must still call this.)
1860
1994
  *
1861
1995
  * Closing twice is safe: the second call answers `ALREADY_COMPLETED` and
1862
1996
  * cannot bill again. Calling it EARLY is also safe — before every entitlement
@@ -1885,7 +2019,9 @@ declare class Signals {
1885
2019
  declare class Units {
1886
2020
  private readonly transport;
1887
2021
  constructor(transport: Transport);
1888
- /** List unit types. Pass `{ active: true }` to only return active ones. */
2022
+ /** List unit types. Pass `{ active: true }` to only return active ones.
2023
+ * `{ active: false }` returns only archived ones; omitting it returns
2024
+ * both. See `activeQuery` — the API itself only supports `true`. */
1889
2025
  list(params?: {
1890
2026
  active?: boolean;
1891
2027
  }): Promise<Unit[]>;
@@ -1927,7 +2063,9 @@ declare class Workspace {
1927
2063
  /** Identify the org + key behind the request, incl. `sandbox` | `live`. */
1928
2064
  me(): Promise<WorkspaceIdentity>;
1929
2065
  /** The org's enabled models with live prices. `{ active: true }` returns only
1930
- * the models that can be metered right now. */
2066
+ * the models that can be metered right now.
2067
+ * `{ active: false }` returns only archived ones; omitting it returns
2068
+ * both. See `activeQuery` — the API itself only supports `true`. */
1931
2069
  models(params?: {
1932
2070
  active?: boolean;
1933
2071
  }): Promise<Model[]>;
@@ -1989,24 +2127,45 @@ declare class ClockNext {
1989
2127
  }
1990
2128
 
1991
2129
  /**
1992
- * Typed error hierarchy. The server returns `{ ok: false, error }` with a
1993
- * meaningful HTTP status; we map that status to a class so callers can branch
1994
- * on the *kind* of failure (e.g. allowance exhausted ⇒ upsell; network ⇒ ignore,
1995
- * it's queued) rather than string-matching messages.
2130
+ * Typed error hierarchy. The server returns `{ statusCode, statusDetail, result }`
2131
+ * with a meaningful HTTP status; we map that status to a class so callers can
2132
+ * branch on the *kind* of failure (e.g. allowance exhausted ⇒ upsell; network ⇒
2133
+ * ignore, it's queued) rather than string-matching messages.
1996
2134
  */
2135
+ /** Machine-readable failure code, when the server sends one
2136
+ * (`statusDetail.code`). Preferred over any message heuristic. */
2137
+ type ClockNextErrorCode = "ALLOWANCE_EXHAUSTED" | "PLAN_REJECTED" | "IDEMPOTENCY_UNRESOLVED" | (string & {});
1997
2138
  declare class ClockNextError extends Error {
1998
2139
  /** HTTP status, when the failure came from a server response. */
1999
2140
  readonly status?: number;
2000
2141
  /** Whether the SDK considers this failure worth retrying. */
2001
2142
  readonly retryable: boolean;
2143
+ /**
2144
+ * The server's machine-readable code, when it sent one. Branch on this in
2145
+ * preference to `.message`, which is human copy and may be reworded.
2146
+ */
2147
+ readonly code?: ClockNextErrorCode;
2148
+ /**
2149
+ * The idempotency key of the request that failed, for metering calls.
2150
+ *
2151
+ * This is what makes a dead-letter retry safe: re-send the failed signal with
2152
+ * this SAME key and the server deduplicates it instead of billing twice. A
2153
+ * fresh key would double-bill, so never generate one when replaying.
2154
+ */
2155
+ readonly idempotencyKey?: string;
2002
2156
  constructor(message: string, opts?: {
2003
2157
  status?: number;
2004
2158
  retryable?: boolean;
2159
+ code?: ClockNextErrorCode;
2160
+ idempotencyKey?: string;
2005
2161
  });
2006
2162
  }
2007
2163
  /** 401 — missing / invalid / expired API key. */
2008
2164
  declare class AuthError extends ClockNextError {
2009
2165
  }
2166
+ /** 403 — the key is valid but cannot perform this operation. */
2167
+ declare class ForbiddenError extends ClockNextError {
2168
+ }
2010
2169
  /** 400 — malformed request / schema validation failure. */
2011
2170
  declare class ValidationError extends ClockNextError {
2012
2171
  }
@@ -2016,10 +2175,13 @@ declare class NotFoundError extends ClockNextError {
2016
2175
  /** 422 — no active plan, meter not in plan, model not enabled, member missing. */
2017
2176
  declare class PlanError extends ClockNextError {
2018
2177
  }
2019
- /** 422 — insufficient credits / units / outcomes for the call. */
2178
+ /** 422 — the entitlement ran out: no credits / units / outcomes / composite
2179
+ * slots left. Distinct from {@link PlanError} because the fix is different —
2180
+ * this one is a top-up or an upsell, not a configuration change. */
2020
2181
  declare class AllowanceError extends ClockNextError {
2021
2182
  }
2022
- /** 409 — conflict. */
2183
+ /** 409 — conflict. Almost always permanent (an archived customer); only the
2184
+ * idempotency-resolution conflict is transient, and only that one retries. */
2023
2185
  declare class ConflictError extends ClockNextError {
2024
2186
  }
2025
2187
  /** 429 — rate limited. */
@@ -2027,6 +2189,7 @@ declare class RateLimitError extends ClockNextError {
2027
2189
  readonly retryAfterMs?: number;
2028
2190
  constructor(message: string, opts?: {
2029
2191
  retryAfterMs?: number;
2192
+ idempotencyKey?: string;
2030
2193
  });
2031
2194
  }
2032
2195
  /** 5xx — server-side error. */
@@ -2034,7 +2197,9 @@ declare class ServerError extends ClockNextError {
2034
2197
  }
2035
2198
  /** fetch threw / request timed out — request never completed. */
2036
2199
  declare class NetworkError extends ClockNextError {
2037
- constructor(message: string);
2200
+ constructor(message: string, opts?: {
2201
+ idempotencyKey?: string;
2202
+ });
2038
2203
  }
2039
2204
 
2040
- export { type AddMemberInput, AllowanceError, type ApiCustomer, AuthError, type BillingCycle, type BillingMode, ClockNext, type ClockNextConfig, ClockNextError, type ComponentType, type Composite, type CompositeCatalogueRef, type CompositeCompletionInput, type CompositeCompletionResult, type CompositeComponentInput, type CompositeTag, ConflictError, type CostPoint, type CostStats, type CreateCompositeInput, type CreateCreditInput, type CreateCustomerInput, type CreateOutcomeInput, type CreatePlanInput, type CreatePurchaseInput, type CreateUnitInput, type Credit, type CreditAdjustInput, type CreditAnalytics, type CreditBalance, type CreditComponentInput, type CreditDetail, type CreditInput, type CreditSignal, type CustomerBalances, type CustomerList, type CustomerPlan, type CustomerPlanDetails, type CustomerProfileInput, type DropReason, type FetchLike, type FlatComponentInput, type FlatUnitInput, type Invoice, type InvoiceDetail, type InvoiceLineItem, type InvoiceListRow, type InvoiceParty, type InvoicePlanSnapshot, type InvoiceStatus, type Logger, type Member, type MemberProfile, type Model, type ModelBundleEntry, NetworkError, NotFoundError, type Outcome, type OutcomeAdjustInput, type OutcomeAnalytics, type OutcomeBalance, type OutcomeComponentInput, type OutcomeDetail, type OutcomeInput, type OutcomeSignal, type OutcomeStepInput, type PayLink, type PaymentDetail, type PaymentRow, type Plan, type PlanComponent, type PlanComponentInput, type PlanDetail, PlanError, type PlanKind, type PlanRef, type PlanType, type PortalToken, type PortalTokenInput, type Purchase, type PurchaseDetail, type PurchaseStatus, RateLimitError, type RemovedMember, type SerializedOutcomeAttribution, type SerializedRuleApplication, type SerializedUnitUsage, type SerializedUsageLog, ServerError, type Signal, type SignalStatus, type SignalType, type ThresholdKind, type TieredUnitInput, type Tokens, type TrackResult, type Unit, type UnitAdjustInput, type UnitBalance, type UnitComponentInput, type UnitCustomerRow, type UnitDetail, type UnitInput, type UnitPricingType, type UnitTier, type UnitTierInput, type UnitTrackResult, type UnitUsageList, type UnitUsageRow, type UpdateCreditInput, type UpdateMemberInput, type UpdateOutcomeInput, type UpdatePlanInput, type UpdateUnitInput, type UpsertWebhookInput, type UsageBalance, type UsageList, ValidationError, type WalletComponentInput, type WalletEntryInput, type WalletInput, type WalletList, type WalletSignal, type WalletTransaction, type WalletTransactionType, type Webhook, type WorkspaceIdentity, signalToWire };
2205
+ export { type AddMemberInput, AllowanceError, type ApiCustomer, AuthError, type BillingCycle, type BillingMode, ClockNext, type ClockNextConfig, ClockNextError, type ClockNextErrorCode, type ComponentType, type Composite, type CompositeCatalogueRef, type CompositeCompletionInput, type CompositeCompletionResult, type CompositeComponentInput, type CompositeTag, ConflictError, type CostPoint, type CostStats, type CreateCompositeInput, type CreateCreditInput, type CreateCustomerInput, type CreateOutcomeInput, type CreatePlanInput, type CreatePurchaseInput, type CreateUnitInput, type Credit, type CreditAdjustInput, type CreditAnalytics, type CreditBalance, type CreditComponentInput, type CreditDetail, type CreditInput, type CreditSignal, type CustomerBalances, type CustomerList, type CustomerPlan, type CustomerPlanDetails, type CustomerProfileInput, type DropReason, type FetchLike, type FlatComponentInput, type FlatUnitInput, ForbiddenError, type Invoice, type InvoiceDetail, type InvoiceLineItem, type InvoiceListRow, type InvoiceParty, type InvoicePlanSnapshot, type InvoiceStatus, type Logger, type Member, type MemberProfile, type Model, type ModelBundleEntry, NetworkError, NotFoundError, type Outcome, type OutcomeAdjustInput, type OutcomeAnalytics, type OutcomeBalance, type OutcomeComponentInput, type OutcomeDetail, type OutcomeInput, type OutcomeSignal, type OutcomeStepInput, type PayLink, type PaymentDetail, type PaymentRow, type Plan, type PlanComponent, type PlanComponentInput, type PlanDetail, PlanError, type PlanKind, type PlanRef, type PlanType, type PortalToken, type PortalTokenInput, type Purchase, type PurchaseDetail, type PurchaseStatus, RateLimitError, type RemovedMember, type SerializedOutcomeAttribution, type SerializedRuleApplication, type SerializedUnitUsage, type SerializedUsageLog, ServerError, type Signal, type SignalDelivery, type SignalStatus, type SignalType, type ThresholdKind, type TieredUnitInput, type Tokens, type TrackResult, type Unit, type UnitAdjustInput, type UnitBalance, type UnitComponentInput, type UnitCustomerRow, type UnitDetail, type UnitInput, type UnitPricingType, type UnitTier, type UnitTierInput, type UnitTrackResult, type UnitUsageList, type UnitUsageRow, type UpdateCreditInput, type UpdateMemberInput, type UpdateOutcomeInput, type UpdatePlanInput, type UpdateUnitInput, type UpsertWebhookInput, type UsageBalance, type UsageList, ValidationError, type WalletComponentInput, type WalletEntitlementKind, type WalletEntryInput, type WalletInput, type WalletList, type WalletSignal, type WalletTransaction, type WalletTransactionSource, type WalletTransactionType, type Webhook, type WorkspaceIdentity, signalToWire };