@clocknext/sdk 0.8.0 → 0.11.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
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * Public types for the ClockNext SDK.
3
3
  *
4
- * The signal types mirror the server's `recordSchema` (the body of
5
- * `POST /api/v1/usage`) as a discriminated union, so the per-type required
6
- * fields the server enforces with Zod refinements are enforced here at compile
7
- * time instead of surfacing as runtime 422s.
4
+ * The signal types mirror the bodies of the `/api/v1/signal/*` routes as a
5
+ * discriminated union, so the per-kind required fields the server enforces with
6
+ * Zod are enforced here at compile time instead of surfacing as runtime 400s.
7
+ *
8
+ * The server now has one schema per route rather than one schema with a `type`
9
+ * discriminator, which is why `type` no longer appears on the wire — it selects
10
+ * the route instead. It stays on these types because it is still the
11
+ * discriminator callers write and TypeScript narrows on.
8
12
  */
9
13
  type SignalType = "wallet" | "credit" | "outcome";
10
14
  type SignalStatus = "SUCCESS" | "FAILURE" | "PENDING";
@@ -19,18 +23,37 @@ interface Tokens {
19
23
  cache?: number;
20
24
  }
21
25
  /**
22
- * Tags a signal with a pricing metric for reporting. `ref` is the metric's
23
- * `refId` (defined in Settings → Pricing metrics) and `value` is your own
24
- * correlation id — a session, a call, a tenant — which the metric rolls up one
25
- * row per distinct value. Reporting only: a metric never affects billing, a bad
26
- * tag never fails the signal, and a `ref` naming no live metric is simply
27
- * ignored. Applies to credit and outcome signals (ignored on wallet).
26
+ * Groups a signal into one occurrence of a **composite** — a bundle of credits
27
+ * / outcomes / units that is priced and billed as a single thing.
28
+ *
29
+ * `ref` is the composite's `refId` (defined in the dashboard) and `value` is
30
+ * your own correlation id for one occurrence: a session, a call, a job. Every
31
+ * signal sharing a value belongs to the same occurrence.
32
+ *
33
+ * This is NOT reporting-only (earlier versions of this SDK called it a
34
+ * "pricing metric" and said so — that was wrong). Where the customer's plan
35
+ * sells the composite, the occurrence IS the billable unit: it opens on the
36
+ * first tagged signal costing nothing, and is charged once when you close it
37
+ * with `signals.completeComposite()` — claiming a prepaid slot on an ADVANCE
38
+ * component, or becoming countable on an ARREAR one.
39
+ *
40
+ * What stays true is that a bad tag never fails the signal: a `ref` naming no
41
+ * live composite is ignored rather than rejected, because the signal carries
42
+ * money and the tag only decides what it is grouped under. Applies to credit,
43
+ * outcome and unit signals; a tag on a wallet signal is accepted and ignored,
44
+ * since raw wallet spend is not entitlement traffic.
45
+ *
46
+ * Only ONE composite per signal is expressible here. The wire format accepts
47
+ * several (`composite` is an object of ref → value), so if you need a signal
48
+ * in two composites at once, that is the limit to lift.
28
49
  */
29
- interface MetricTag {
30
- /** The metric's `refId`. Case-insensitive; must not collide with a reserved
31
- * ingest field (e.g. `model`, `member`, `agentKey`, `runId`, `custom`). */
50
+ interface CompositeTag {
51
+ /** The composite's `refId`. Case-insensitive. Unlike earlier versions this
52
+ * no longer shares a namespace with the ingest fields — the tag is nested
53
+ * under `composite` on the wire — so a composite may safely be called
54
+ * `model`, `status`, or anything else. */
32
55
  ref: string;
33
- /** Your correlation value for this metric, stored verbatim. */
56
+ /** Your correlation value for this occurrence, stored verbatim. */
34
57
  value: string | number;
35
58
  }
36
59
  /** The organisation + key behind a request — `cnk.workspace.me()`. */
@@ -90,8 +113,8 @@ interface CreditSignal extends SignalBase {
90
113
  type: "credit";
91
114
  /** The Credit's stable agent key (assigned when the credit was created). */
92
115
  agentKey: string;
93
- /** Optional pricing-metric tag for reporting. */
94
- metric?: MetricTag;
116
+ /** Optional composite this signal belongs to. See {@link CompositeTag}. */
117
+ composite?: CompositeTag;
95
118
  }
96
119
  /** Debits the customer's wallet in USD at the model's cost. */
97
120
  interface WalletSignal extends SignalBase {
@@ -118,8 +141,8 @@ interface OutcomeSignal extends SignalBase {
118
141
  * can't bill again.
119
142
  */
120
143
  complete?: boolean;
121
- /** Optional pricing-metric tag for reporting. */
122
- metric?: MetricTag;
144
+ /** Optional composite this signal belongs to. See {@link CompositeTag}. */
145
+ composite?: CompositeTag;
123
146
  }
124
147
  type Signal = CreditSignal | WalletSignal | OutcomeSignal;
125
148
  /** Helper input shapes for the per-type convenience methods. */
@@ -153,7 +176,7 @@ interface SerializedOutcomeAttribution {
153
176
  /** The customer's remaining balance for the dimension a signal metered, AFTER
154
177
  * the signal was applied — update an on-screen meter straight from the record
155
178
  * response. Populated on recorded (inline / `{ wait: true }`) signals; `null`
156
- * on dry runs and on read-back. */
179
+ * on read-back. */
157
180
  interface UsageBalance {
158
181
  dimension: "credit" | "outcome" | "wallet";
159
182
  /** Post-signal remaining. `null` when the meter is uncapped — an ARREAR
@@ -180,7 +203,7 @@ interface SerializedUsageLog {
180
203
  status: SignalStatus;
181
204
  appliedRules: SerializedRuleApplication[];
182
205
  outcome: SerializedOutcomeAttribution | null;
183
- /** Post-signal balance for the metered dimension. Null on dry runs and
206
+ /** Post-signal balance for the metered dimension. Null on
184
207
  * read-back. */
185
208
  balance: UsageBalance | null;
186
209
  createdAt: string;
@@ -217,6 +240,18 @@ interface TrackResult {
217
240
  * (server-side queueing). Absent for client-side buffering and for inline
218
241
  * pricing. */
219
242
  messageId?: string;
243
+ /**
244
+ * The signal's durable inbox id, returned by every `/api/v1/signal/*`
245
+ * response — the `202` hand-off AND inline pricing. Present whenever the
246
+ * server has written the request down, which it now does BEFORE validating
247
+ * or pricing it, so a `rawId` means "this signal cannot be lost" even when
248
+ * `usageLog` is null. Quote it when asking why a signal has not been billed;
249
+ * it is the row the dashboard shows as Processing / Pending / Processed.
250
+ *
251
+ * Absent while the signal sits in the client-side buffer (nothing has been
252
+ * sent yet, so the server has no id for it).
253
+ */
254
+ rawId?: string;
220
255
  }
221
256
  interface UsageList {
222
257
  customer: {
@@ -243,14 +278,11 @@ interface ApiCustomer {
243
278
  phone: string | null;
244
279
  legalName: string | null;
245
280
  addressLine1: string | null;
246
- addressLine2: string | null;
247
281
  city: string | null;
248
282
  state: string | null;
249
283
  country: string | null;
250
284
  pincode: string | null;
251
285
  taxId: string | null;
252
- notes: string | null;
253
- logoUrl: string | null;
254
286
  currencyCode: string;
255
287
  creditAllowance: number;
256
288
  createdAt: string;
@@ -265,14 +297,11 @@ interface CustomerProfileInput {
265
297
  phone?: string | null;
266
298
  legalName?: string | null;
267
299
  addressLine1?: string | null;
268
- addressLine2?: string | null;
269
300
  city?: string | null;
270
301
  state?: string | null;
271
302
  country?: string | null;
272
303
  pincode?: string | null;
273
304
  taxId?: string | null;
274
- notes?: string | null;
275
- logoUrl?: string | null;
276
305
  /** ISO 4217 (3 letters); must be enabled for the org. */
277
306
  currencyCode?: string;
278
307
  }
@@ -443,15 +472,49 @@ type UnitComponentInput = {
443
472
  unitId: string;
444
473
  quantity?: number | null;
445
474
  };
475
+ /**
476
+ * A COMPOSITE sold on the plan, referenced by `pricingMetricId` (the id from
477
+ * `composites.list()`). This is the line that makes a composite bill: the
478
+ * catalogue entry on its own charges nobody, and this component is what turns
479
+ * it into money — `quantity × the composite's price`, frozen into the purchase
480
+ * snapshot at buy time.
481
+ *
482
+ * The two billing modes differ in what `quantity` means:
483
+ * - **ADVANCE** — a PREPAID POOL of `quantity` flat-rate slots, shared
484
+ * across whichever credits / outcomes / units the composite wraps. Each
485
+ * distinct tag value claims one slot on completion; repeat signals under a
486
+ * value reuse the slot they already claimed, at no extra cost. Once every
487
+ * slot is claimed, further occurrences are refused rather than billed.
488
+ * - **ARREAR** — nothing prepaid; completed occurrences are counted on the
489
+ * cycle-end line.
490
+ *
491
+ * `type` is the wire value `"PRICING_METRIC"`, not `"COMPOSITE"`: the product
492
+ * word changed but the stored enum did not, and the API still speaks the old
493
+ * one. That mismatch is the server's, and it is surfaced rather than hidden so
494
+ * a response and a request read the same.
495
+ */
496
+ type CompositeComponentInput = {
497
+ type: "PRICING_METRIC";
498
+ billingMode: "ADVANCE";
499
+ pricingMetricId: string;
500
+ quantity: number;
501
+ } | {
502
+ type: "PRICING_METRIC";
503
+ billingMode: "ARREAR";
504
+ pricingMetricId: string;
505
+ quantity?: number | null;
506
+ };
446
507
  /**
447
508
  * One entitlement line in a plan — a discriminated union on `type` (and, where
448
509
  * it matters, `billingMode`) so the per-type fields the server requires are
449
510
  * enforced at compile time instead of surfacing as runtime 422s:
450
511
  * - CREDIT / OUTCOME / UNIT require their matching id;
451
- * - CREDIT / OUTCOME / UNIT require `quantity` when billed ADVANCE;
512
+ * - PRICING_METRIC (a composite) requires `pricingMetricId`;
513
+ * - CREDIT / OUTCOME / UNIT / PRICING_METRIC require `quantity` when billed
514
+ * ADVANCE;
452
515
  * - WALLET / FLAT require `amount`.
453
516
  */
454
- type PlanComponentInput = WalletComponentInput | FlatComponentInput | CreditComponentInput | OutcomeComponentInput | UnitComponentInput;
517
+ type PlanComponentInput = WalletComponentInput | FlatComponentInput | CreditComponentInput | OutcomeComponentInput | UnitComponentInput | CompositeComponentInput;
455
518
  interface CreatePlanInput {
456
519
  name: string;
457
520
  description?: string | null;
@@ -703,31 +766,36 @@ interface PaymentDetail {
703
766
  payment: Invoice;
704
767
  plan: PlanRef | null;
705
768
  }
706
- type MemberRole = "OWNER" | "ADMIN" | "MEMBER";
769
+ /** A customer member as returned by `listMembers` (`GET .../members`). */
707
770
  interface Member {
708
771
  id: string;
709
- role: MemberRole;
710
772
  name: string;
711
773
  email: string | null;
712
- employeeId: string | null;
713
- phone: string | null;
714
774
  /** True once the member has linked a login. */
715
775
  isLinked: boolean;
716
776
  joinedAt: string;
717
777
  }
778
+ /** A customer member as returned by `addMember` / `updateMember`. */
779
+ interface MemberProfile {
780
+ id: string;
781
+ name: string;
782
+ email: string | null;
783
+ joinedAt: string;
784
+ }
785
+ /** The member removed by `removeMember` (`DELETE .../members/:memberId`). */
786
+ interface RemovedMember {
787
+ name: string;
788
+ email: string | null;
789
+ }
718
790
  interface AddMemberInput {
719
791
  name: string;
720
- role: "ADMIN" | "MEMBER";
792
+ /** Must be unique (case-insensitively) among the customer's active members. */
721
793
  email?: string;
722
- employeeId?: string;
723
- phone?: string;
724
794
  }
725
- /** Role is fixed after creation; omit it here. */
726
795
  interface UpdateMemberInput {
727
796
  name: string;
797
+ /** Must be unique (case-insensitively) among the customer's active members. */
728
798
  email?: string;
729
- employeeId?: string;
730
- phone?: string;
731
799
  }
732
800
  type WalletTransactionType = "CREDIT" | "DEBIT";
733
801
  interface WalletTransaction {
@@ -1023,6 +1091,160 @@ interface UnitInput {
1023
1091
  customerId: string;
1024
1092
  agentKey: string;
1025
1093
  member?: string;
1094
+ /**
1095
+ * How many units this one report covers. Defaults to 1 server-side, so a
1096
+ * caller reporting one unit per call simply omits it. Whole numbers, at most
1097
+ * 1,000,000 per call.
1098
+ *
1099
+ * On an ADVANCE (prepaid) plan the draw is all-or-nothing: asking for more
1100
+ * than remains is refused IN FULL rather than partially filled.
1101
+ */
1102
+ quantity?: number;
1103
+ /** Optional composite this unit report belongs to. See {@link CompositeTag}. */
1104
+ composite?: CompositeTag;
1105
+ /**
1106
+ * Idempotency key for safe retries — a repeat carrying the same key returns
1107
+ * the original result instead of drawing the balance down twice.
1108
+ *
1109
+ * New in the `/api/v1/signal/unit` route: the legacy `/api/v1/units` had no
1110
+ * idempotency at all, so a retried unit report double-charged.
1111
+ *
1112
+ * **Auto-generated (UUID v4) if omitted** and reused across every retry of
1113
+ * this call, exactly like a signal's. That matters more here than it looks:
1114
+ * unit reports opt into transport retries, so without a key one 500-then-200
1115
+ * is two distinct billable reports.
1116
+ *
1117
+ * Supply your own — a stable id for this logical event — to also dedup
1118
+ * across process restarts or at-least-once redelivery, which an in-memory
1119
+ * auto key cannot survive.
1120
+ */
1121
+ idempotencyKey?: string;
1122
+ }
1123
+ /**
1124
+ * The result of `signals.unit()`.
1125
+ *
1126
+ * Shaped like {@link TrackResult} and for the same reason: `/api/v1/signal/unit`
1127
+ * is durable, so it answers `202` with no priced event when the server hands
1128
+ * the report to its queue, and `200` with `unitUsage` when it prices inline.
1129
+ * The legacy `/api/v1/units` always priced inline, which is why this used to be
1130
+ * a bare `{ unitUsage }`. Branch on `queued`; never assume `unitUsage`.
1131
+ */
1132
+ interface UnitTrackResult {
1133
+ /** True when the report was accepted but not yet priced (`202`). */
1134
+ queued: boolean;
1135
+ /** The priced unit event. Non-null ONLY when the server priced inline. */
1136
+ unitUsage?: SerializedUnitUsage | null;
1137
+ /** The durable-queue message id, present only on a `202`. */
1138
+ messageId?: string;
1139
+ /** The durable inbox id — see {@link TrackResult.rawId}. */
1140
+ rawId?: string;
1141
+ }
1142
+ /**
1143
+ * Input for `signals.completeComposite()` — closes ONE occurrence of a
1144
+ * composite, which is the moment it is billed.
1145
+ */
1146
+ interface CompositeCompletionInput {
1147
+ customerId: string;
1148
+ /** Which occurrence to close: the composite's `refId` and the correlation
1149
+ * value the signals were tagged with. */
1150
+ composite: CompositeTag;
1151
+ /** Idempotency key, auto-generated if omitted. Closing twice is already a
1152
+ * safe no-op, so this only collapses retries into one recorded attempt
1153
+ * rather than protecting against a double charge. */
1154
+ idempotencyKey?: string;
1155
+ }
1156
+ /** One catalogue item a composite is restricted to. `agentKey` is what a
1157
+ * signal names, so it is the field you actually integrate against. */
1158
+ interface CompositeCatalogueRef {
1159
+ id: string;
1160
+ name: string;
1161
+ agentKey: string;
1162
+ }
1163
+ /** One composite in the org's catalogue — `cnk.composites.list()`. */
1164
+ interface Composite {
1165
+ id: string;
1166
+ name: string;
1167
+ /** The stable identifier you put in a signal's `composite` block as `ref`. */
1168
+ refId: string;
1169
+ description: string | null;
1170
+ /** USD charged per occurrence WHEN A PLAN SELLS THIS as a composite
1171
+ * component (`quantity × price`, frozen into the purchase at buy time). The
1172
+ * catalogue row alone bills nothing. */
1173
+ price: number;
1174
+ /** False once archived. An archived composite stops NEW signals resolving to
1175
+ * it but keeps every row it already owns. */
1176
+ isActive: boolean;
1177
+ createdAt: string;
1178
+ /**
1179
+ * The credits / outcomes / units this composite is restricted to — only
1180
+ * signals naming one of these may carry its tag.
1181
+ *
1182
+ * All three lists empty means UNRESTRICTED (it accepts any of the org's
1183
+ * traffic under this tag). Nothing created through the API can be
1184
+ * unrestricted, but rows predating that rule exist — so don't assume
1185
+ * non-empty.
1186
+ */
1187
+ entitlements: {
1188
+ credits: CompositeCatalogueRef[];
1189
+ outcomes: CompositeCatalogueRef[];
1190
+ units: CompositeCatalogueRef[];
1191
+ };
1192
+ }
1193
+ /** Input for `cnk.composites.create()`. */
1194
+ interface CreateCompositeInput {
1195
+ name: string;
1196
+ /**
1197
+ * The tag identifier. Lowercased, and restricted to the same characters as
1198
+ * an `agentKey` — but validated harder than one, because it may not collide
1199
+ * with a field the ingest schemas already own (`customerId`, `usage`,
1200
+ * `agentKey`, `runId`, `member`, `custom`, `composite`, …). A colliding
1201
+ * `refId` is refused at create time rather than silently ignored at ingest.
1202
+ */
1203
+ refId: string;
1204
+ /** USD per occurrence once a plan sells it. Required — an unpriced composite
1205
+ * put on a plan would bill 0, which is a money bug that is hard to see. */
1206
+ price: number;
1207
+ description?: string | null;
1208
+ /**
1209
+ * What the composite wraps. At least one id across the three lists is
1210
+ * REQUIRED: a composite restricted to nothing would accept every signal in
1211
+ * the org. Get the ids from `credits.list()`, `outcomes.list()` and
1212
+ * `units.list()`.
1213
+ */
1214
+ entitlements: {
1215
+ creditIds?: string[];
1216
+ outcomeIds?: string[];
1217
+ unitIds?: string[];
1218
+ };
1219
+ }
1220
+ /**
1221
+ * The result of `signals.completeComposite()`.
1222
+ *
1223
+ * `status` distinguishes the first close from a replay. Note that an EARLY
1224
+ * ping — one sent before every credit / outcome / unit the composite is
1225
+ * restricted to has reported under the same value — is not an error: the
1226
+ * server parks it and completes it automatically when the missing signal
1227
+ * lands. That case answers with no `status`, only a `rawId` to follow.
1228
+ */
1229
+ interface CompositeCompletionResult {
1230
+ /** True when the ping was accepted but not yet applied (`202`). */
1231
+ queued: boolean;
1232
+ /** `COMPLETED` on the first close, `ALREADY_COMPLETED` on a replay. Absent
1233
+ * when the ping was queued or parked. */
1234
+ status?: "COMPLETED" | "ALREADY_COMPLETED";
1235
+ /** The composite that was closed. Null on the replay of a historical row
1236
+ * whose occurrence can no longer be read back. */
1237
+ composite?: {
1238
+ id: string;
1239
+ name: string;
1240
+ refId: string;
1241
+ } | null;
1242
+ /** The correlation value whose occurrence was closed. */
1243
+ value?: string | null;
1244
+ /** The durable-queue message id, present only on a `202`. */
1245
+ messageId?: string;
1246
+ /** The durable inbox id — see {@link TrackResult.rawId}. */
1247
+ rawId?: string;
1026
1248
  }
1027
1249
  /** One serialized unit-consumption event — the result of `signals.unit()` and
1028
1250
  * each element of `UnitUsageList.events`. */
@@ -1185,6 +1407,35 @@ declare class Transport {
1185
1407
  private buildUrl;
1186
1408
  }
1187
1409
 
1410
+ /**
1411
+ * The `composites` resource — manage the org's composite *catalogue*: the
1412
+ * bundles of credits / outcomes / units that are grouped under one tag and
1413
+ * sold on a plan as a single thing. `/api/v1/composites`.
1414
+ *
1415
+ * Two things live elsewhere, deliberately:
1416
+ * • Tagging a signal into a composite occurrence is `composite: { ref, value }`
1417
+ * on the signal itself (see `signals.credit()` / `.outcome()` / `.unit()`).
1418
+ * • Closing an occurrence — the moment it is billed — is
1419
+ * `signals.completeComposite()`.
1420
+ *
1421
+ * This resource only defines what composites EXIST. Creating one prices it but
1422
+ * charges nobody: an invoice is built from plan components, never from a
1423
+ * catalogue entry, so a composite starts costing money only once a plan sells
1424
+ * it (`quantity × price`, frozen into the purchase at buy time).
1425
+ */
1426
+ declare class Composites {
1427
+ private readonly transport;
1428
+ constructor(transport: Transport);
1429
+ /** List composites. Pass `{ active: true }` for only the ones a signal can
1430
+ * still be tagged with — the default list INCLUDES archived ones, which
1431
+ * keep every row they already own. */
1432
+ list(params?: {
1433
+ active?: boolean;
1434
+ }): Promise<Composite[]>;
1435
+ /** Create a composite. `POST /api/v1/composites`. */
1436
+ create(input: CreateCompositeInput): Promise<Composite>;
1437
+ }
1438
+
1188
1439
  /**
1189
1440
  * The `credits` resource — manage the org's credit *types* (the catalog). A
1190
1441
  * credit's stable `agentKey` is what you pass to `signals.credit()` when
@@ -1275,12 +1526,12 @@ declare class Customers {
1275
1526
  plan(id: string): Promise<CustomerPlan>;
1276
1527
  /** List a customer's members. `GET …/:id/members`. */
1277
1528
  listMembers(id: string): Promise<Member[]>;
1278
- /** Add a member to a customer. Duplicate `employeeId` → ConflictError. */
1279
- addMember(id: string, input: AddMemberInput): Promise<Member>;
1280
- /** Update a member's profile (role is fixed). `PATCH …/:id/members/:memberId`. */
1281
- updateMember(id: string, memberId: string, input: UpdateMemberInput): Promise<Member>;
1282
- /** Remove a member from a customer. `DELETE …/:id/members/:memberId`. */
1283
- removeMember(id: string, memberId: string): Promise<void>;
1529
+ /** Add a member to a customer. Duplicate `email` → ConflictError. */
1530
+ addMember(id: string, input: AddMemberInput): Promise<MemberProfile>;
1531
+ /** Update a member's profile. Duplicate `email` → ConflictError. `PATCH …/:id/members/:memberId`. */
1532
+ updateMember(id: string, memberId: string, input: UpdateMemberInput): Promise<MemberProfile>;
1533
+ /** Remove a member from a customer, returning who was removed. `DELETE …/:id/members/:memberId`. */
1534
+ removeMember(id: string, memberId: string): Promise<RemovedMember>;
1284
1535
  /** The customer's wallet balance + transactions. `GET …/:id/wallet`. */
1285
1536
  wallet(id: string, params?: {
1286
1537
  limit?: number;
@@ -1443,7 +1694,17 @@ declare class Purchases {
1443
1694
 
1444
1695
  /** One buffered, wire-ready signal awaiting a background flush. */
1445
1696
  interface QueuedSignal {
1446
- /** The exact JSON body to POST to /api/v1/usage. */
1697
+ /**
1698
+ * The route this signal posts to (`/api/v1/signal/credit` etc.), decided by
1699
+ * `track()` when the signal was buffered.
1700
+ *
1701
+ * It is stored per item rather than fixed on the Flusher because each meter
1702
+ * now has its own endpoint. A single hardcoded path was fine when everything
1703
+ * went to `/api/v1/usage`; keeping one would have meant async mode silently
1704
+ * posting to the deprecated route while sync mode used the new ones.
1705
+ */
1706
+ path: string;
1707
+ /** The exact JSON body to POST. */
1447
1708
  body: Record<string, unknown>;
1448
1709
  /** The original signal — passed to onError/onDrop hooks. */
1449
1710
  signal: Signal;
@@ -1482,7 +1743,23 @@ declare class Flusher {
1482
1743
  close(): Promise<void>;
1483
1744
  }
1484
1745
 
1485
- /** Builds the `POST /api/v1/usage` body from a typed Signal. */
1746
+ /**
1747
+ * Builds the request body for a typed Signal.
1748
+ *
1749
+ * Two grouping changes from the pre-0.10 `POST /api/v1/usage` body, both of
1750
+ * which the server now requires:
1751
+ *
1752
+ * usage: { model, inputTokens, outputTokens, cacheTokens }
1753
+ * composite: { "<refId>": "<value>" }
1754
+ *
1755
+ * `usage` collects the four fields describing one LLM call. `composite` is the
1756
+ * more consequential one: the tag used to sit at the TOP LEVEL under its own
1757
+ * caller-defined property name, which meant a composite called `model` or
1758
+ * `status` shadowed a real ingest field. Nested, the two namespaces are
1759
+ * separate and that collision is impossible.
1760
+ *
1761
+ * The route is NOT part of this body — see {@link SIGNAL_PATHS}.
1762
+ */
1486
1763
  declare function signalToWire(signal: Signal): Record<string, unknown>;
1487
1764
  /**
1488
1765
  * The `signals` resource: record usage signals and read them back.
@@ -1491,14 +1768,15 @@ declare function signalToWire(signal: Signal): Record<string, unknown>;
1491
1768
  * immediately with `{ queued: true }`. Pass `{ wait: true }` (or construct the
1492
1769
  * client in `sync` mode) to await the request.
1493
1770
  *
1494
- * Awaiting the request does NOT guarantee a priced result. Where the server has
1495
- * a durable queue configured it answers `202` and prices the signal in a worker,
1496
- * so `track()` resolves `{ queued: true, messageId, usageLog: null }` — accepted,
1497
- * not yet billed, and validated only later (a bad `agentKey` is dropped there,
1498
- * silently). Only when the server prices inline do you get
1499
- * `{ queued: false, usageLog }`. Branch on `queued`; never assume `usageLog`.
1500
- * `verify()` is the way to validate and price a signal synchronously, and
1501
- * `list()` is how you confirm one actually landed.
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`.
1777
+ *
1778
+ * `list()` is how you confirm one actually landed — and, ingestion being
1779
+ * asynchronous, the only way to.
1502
1780
  */
1503
1781
  declare class Signals {
1504
1782
  private readonly transport;
@@ -1506,7 +1784,8 @@ declare class Signals {
1506
1784
  private readonly cfg;
1507
1785
  constructor(transport: Transport, flusher: Flusher, cfg: ResolvedConfig);
1508
1786
  /**
1509
- * Record one usage signal. `POST /api/v1/usage`.
1787
+ * Record one usage signal, to the route its `type` selects:
1788
+ * `POST /api/v1/signal/credit` | `/signal/outcome` | `/signal/wallet`.
1510
1789
  *
1511
1790
  * Returns as soon as the signal is buffered (async mode) or the request has
1512
1791
  * been answered (`{ wait: true }` / sync mode). See {@link TrackResult}: a
@@ -1515,25 +1794,27 @@ declare class Signals {
1515
1794
  track(signal: Signal, opts?: {
1516
1795
  wait?: boolean;
1517
1796
  }): Promise<TrackResult>;
1518
- /**
1519
- * Validate + price a signal WITHOUT recording it (server `dryRun`). Returns
1520
- * the projected usage log so an integration can preflight before sending real
1521
- * traffic — no inbox row, wallet / outcome writes, or webhooks fire.
1522
- */
1523
- verify(signal: Signal): Promise<SerializedUsageLog | null>;
1524
- /** Meter a named credit. */
1797
+ /** Meter a named credit. `POST /api/v1/signal/credit`. */
1525
1798
  credit(input: CreditInput, opts?: {
1526
1799
  wait?: boolean;
1527
1800
  }): Promise<TrackResult>;
1528
- /** Debit the customer's wallet at model cost. */
1801
+ /** Debit the customer's wallet at model cost. `POST /api/v1/signal/wallet`. */
1529
1802
  wallet(input: WalletInput, opts?: {
1530
1803
  wait?: boolean;
1531
1804
  }): Promise<TrackResult>;
1532
- /** Advance one step of an outcome workflow. */
1805
+ /** Advance one step of an outcome workflow. `POST /api/v1/signal/outcome`. */
1533
1806
  outcome(input: OutcomeInput, opts?: {
1534
1807
  wait?: boolean;
1535
1808
  }): Promise<TrackResult>;
1536
- /** Recent usage logs + totals for a customer. `GET /api/v1/usage`. */
1809
+ /**
1810
+ * Recent usage logs + totals for a customer. `GET /api/v1/usage`.
1811
+ *
1812
+ * Still the legacy path, deliberately. The `/api/v1/signal/*` family is
1813
+ * POST-only — it replaced how usage is INGESTED, not how it is read — so
1814
+ * `GET /api/v1/usage` has no successor and is not deprecated. It is also the
1815
+ * only way to prove a signal landed, which matters more now that ingestion
1816
+ * answers `202` for everything.
1817
+ */
1537
1818
  list(params: {
1538
1819
  customerId: string;
1539
1820
  limit?: number;
@@ -1541,12 +1822,44 @@ declare class Signals {
1541
1822
  /**
1542
1823
  * Record one unit-metered event (a fixed-price meter with no tokens),
1543
1824
  * addressed to a catalog unit by its `agentKey`. Sent immediately — unit
1544
- * events are not buffered. `POST /api/v1/units`.
1825
+ * events are not buffered. `POST /api/v1/signal/unit`.
1826
+ *
1827
+ * Two things changed with the move off `POST /api/v1/units`, and both show up
1828
+ * in the return type. The route is now durable (the report lands in an inbox
1829
+ * before it is priced, so a failure parks as Pending and can be retried
1830
+ * instead of losing a billable event) and it answers `202` when it queues —
1831
+ * so this resolves {@link UnitTrackResult}, not a bare `{ unitUsage }`.
1832
+ * Branch on `queued`.
1833
+ */
1834
+ unit(input: UnitInput): Promise<UnitTrackResult>;
1835
+ /**
1836
+ * Close ONE occurrence of a composite — a distinct tag value.
1837
+ * `POST /api/v1/signal/composite/complete`.
1838
+ *
1839
+ * A composite bundles several credits / outcomes / units and is billed as one
1840
+ * thing. The occurrence opens on the first tagged signal and costs nothing;
1841
+ * this call is the moment it is billed. Only your code knows when the
1842
+ * workflow ended, so this is a DECLARATION, not a hint: tagged signals may
1843
+ * arrive in any order and the occurrence stays open and unbilled until you
1844
+ * say otherwise. Put it on the line before your own `return`.
1845
+ *
1846
+ * An OUTCOME never needs this — its own `complete: true` closes its run and
1847
+ * the matching occurrence together. Credits and units have no "done" signal
1848
+ * of their own, so this is theirs.
1849
+ *
1850
+ * Closing twice is safe: the second call answers `ALREADY_COMPLETED` and
1851
+ * cannot bill again. Calling it EARLY is also safe — before every entitlement
1852
+ * the composite is restricted to has reported, or while an ADVANCE pool is
1853
+ * full — the server parks the ping and applies it when the blocker clears,
1854
+ * rather than failing it.
1855
+ */
1856
+ completeComposite(input: CompositeCompletionInput): Promise<CompositeCompletionResult>;
1857
+ /**
1858
+ * A customer's unit balances + recent unit events. `GET /api/v1/units`.
1859
+ *
1860
+ * Legacy path for the same reason as {@link list}: the signal family is
1861
+ * POST-only, so the read side has no successor and is not deprecated.
1545
1862
  */
1546
- unit(input: UnitInput): Promise<{
1547
- unitUsage: SerializedUnitUsage;
1548
- }>;
1549
- /** A customer's unit balances + recent unit events. `GET /api/v1/units`. */
1550
1863
  unitUsage(params: {
1551
1864
  customerId: string;
1552
1865
  limit?: number;
@@ -1641,6 +1954,8 @@ declare class ClockNext {
1641
1954
  readonly payments: Payments;
1642
1955
  /** Manage the credit-type catalog. */
1643
1956
  readonly credits: Credits;
1957
+ /** Manage the composite catalog — bundles billed as one thing. */
1958
+ readonly composites: Composites;
1644
1959
  /** Manage the outcome-type catalog. */
1645
1960
  readonly outcomes: Outcomes;
1646
1961
  /** Manage the unit-type catalog. */
@@ -1711,4 +2026,4 @@ declare class NetworkError extends ClockNextError {
1711
2026
  constructor(message: string);
1712
2027
  }
1713
2028
 
1714
- export { type AddMemberInput, AllowanceError, type ApiCustomer, AuthError, type BillingCycle, type BillingMode, ClockNext, type ClockNextConfig, ClockNextError, type ComponentType, ConflictError, type CostPoint, type CostStats, 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 MemberRole, type MetricTag, 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 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 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 };
2029
+ 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 };