@clocknext/sdk 0.7.0 → 0.10.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.cts 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. */
@@ -185,12 +208,50 @@ interface SerializedUsageLog {
185
208
  balance: UsageBalance | null;
186
209
  createdAt: string;
187
210
  }
188
- /** Result of a single signal send (sync mode, or `{ wait: true }`). */
211
+ /**
212
+ * Result of a single signal send (sync mode, or `{ wait: true }`).
213
+ *
214
+ * `queued: true` has TWO causes, and they are not the same thing:
215
+ * 1. **Client-side buffering** (async mode, no `wait`) — the signal is in this
216
+ * process's buffer and has not been sent. No `messageId`.
217
+ * 2. **Server-side durable hand-off** — the request WAS sent and the server
218
+ * answered `202 Accepted`, having enqueued it for a worker to price. Carries
219
+ * a `messageId`. `usageLog` is null: the log does not exist yet.
220
+ *
221
+ * In case 2 the signal is accepted but **not yet billed**, and it has only been
222
+ * shallowly validated. A bad `agentKey`, a customer with no matching plan, or an
223
+ * exhausted allowance is detected by the worker LATER and never surfaces here —
224
+ * the signal is simply dropped. A `202` therefore means "accepted", never
225
+ * "recorded". To confirm a signal actually landed, read it back with
226
+ * `signals.list({ customerId })`.
227
+ *
228
+ * When the server has no queue configured it prices inline instead and returns
229
+ * `queued: false` with a populated `usageLog`, validating synchronously.
230
+ */
189
231
  interface TrackResult {
190
- /** True when the signal was buffered (async mode) and not yet sent. */
232
+ /** True when the signal has not been priced yet — either buffered locally
233
+ * (async mode) or accepted for asynchronous processing by the server. */
191
234
  queued: boolean;
192
- /** The computed usage log — present only once the signal has been sent. */
235
+ /** The computed usage log. Non-null ONLY when the server priced the signal
236
+ * inline (`queued: false`); null on a `202` hand-off and absent while the
237
+ * signal sits in the client-side buffer. */
193
238
  usageLog?: SerializedUsageLog | null;
239
+ /** The server's durable-queue message id, present only on a `202` hand-off
240
+ * (server-side queueing). Absent for client-side buffering and for inline
241
+ * pricing. */
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;
194
255
  }
195
256
  interface UsageList {
196
257
  customer: {
@@ -217,14 +278,11 @@ interface ApiCustomer {
217
278
  phone: string | null;
218
279
  legalName: string | null;
219
280
  addressLine1: string | null;
220
- addressLine2: string | null;
221
281
  city: string | null;
222
282
  state: string | null;
223
283
  country: string | null;
224
284
  pincode: string | null;
225
285
  taxId: string | null;
226
- notes: string | null;
227
- logoUrl: string | null;
228
286
  currencyCode: string;
229
287
  creditAllowance: number;
230
288
  createdAt: string;
@@ -239,14 +297,11 @@ interface CustomerProfileInput {
239
297
  phone?: string | null;
240
298
  legalName?: string | null;
241
299
  addressLine1?: string | null;
242
- addressLine2?: string | null;
243
300
  city?: string | null;
244
301
  state?: string | null;
245
302
  country?: string | null;
246
303
  pincode?: string | null;
247
304
  taxId?: string | null;
248
- notes?: string | null;
249
- logoUrl?: string | null;
250
305
  /** ISO 4217 (3 letters); must be enabled for the org. */
251
306
  currencyCode?: string;
252
307
  }
@@ -279,7 +334,9 @@ interface OutcomeBalance {
279
334
  granted: number;
280
335
  remaining: number;
281
336
  /** Each step's `agentKey` is what `signals.outcome()` and the outcome adjust
282
- * API use to address this outcome (outcomes have no key of their own). */
337
+ * API use to address this outcome at runtime. The outcome also has its own
338
+ * separate key ({@link Outcome.agentKey}), but neither of those APIs accepts
339
+ * it — it identifies the outcome in the catalogue, not in a signal. */
283
340
  steps: {
284
341
  id: string;
285
342
  name: string;
@@ -415,15 +472,49 @@ type UnitComponentInput = {
415
472
  unitId: string;
416
473
  quantity?: number | null;
417
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
+ };
418
507
  /**
419
508
  * One entitlement line in a plan — a discriminated union on `type` (and, where
420
509
  * it matters, `billingMode`) so the per-type fields the server requires are
421
510
  * enforced at compile time instead of surfacing as runtime 422s:
422
511
  * - CREDIT / OUTCOME / UNIT require their matching id;
423
- * - 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;
424
515
  * - WALLET / FLAT require `amount`.
425
516
  */
426
- type PlanComponentInput = WalletComponentInput | FlatComponentInput | CreditComponentInput | OutcomeComponentInput | UnitComponentInput;
517
+ type PlanComponentInput = WalletComponentInput | FlatComponentInput | CreditComponentInput | OutcomeComponentInput | UnitComponentInput | CompositeComponentInput;
427
518
  interface CreatePlanInput {
428
519
  name: string;
429
520
  description?: string | null;
@@ -675,31 +766,36 @@ interface PaymentDetail {
675
766
  payment: Invoice;
676
767
  plan: PlanRef | null;
677
768
  }
678
- type MemberRole = "OWNER" | "ADMIN" | "MEMBER";
769
+ /** A customer member as returned by `listMembers` (`GET .../members`). */
679
770
  interface Member {
680
771
  id: string;
681
- role: MemberRole;
682
772
  name: string;
683
773
  email: string | null;
684
- employeeId: string | null;
685
- phone: string | null;
686
774
  /** True once the member has linked a login. */
687
775
  isLinked: boolean;
688
776
  joinedAt: string;
689
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
+ }
690
790
  interface AddMemberInput {
691
791
  name: string;
692
- role: "ADMIN" | "MEMBER";
792
+ /** Must be unique (case-insensitively) among the customer's active members. */
693
793
  email?: string;
694
- employeeId?: string;
695
- phone?: string;
696
794
  }
697
- /** Role is fixed after creation; omit it here. */
698
795
  interface UpdateMemberInput {
699
796
  name: string;
797
+ /** Must be unique (case-insensitively) among the customer's active members. */
700
798
  email?: string;
701
- employeeId?: string;
702
- phone?: string;
703
799
  }
704
800
  type WalletTransactionType = "CREDIT" | "DEBIT";
705
801
  interface WalletTransaction {
@@ -734,10 +830,12 @@ interface CreditAdjustInput {
734
830
  notes?: string;
735
831
  }
736
832
  /** Grant/claw an outcome balance. `delta` is a non-zero whole number; floored at
737
- * 0. The outcome is addressed by a STEP's `agentKey` (outcomes have no key of
738
- * their own). */
833
+ * 0. The outcome is addressed here by a STEP's `agentKey`, which resolves to its
834
+ * parent outcome — NOT by the outcome's own `agentKey` ({@link Outcome.agentKey}),
835
+ * which this endpoint does not accept. */
739
836
  interface OutcomeAdjustInput {
740
- /** An outcome step's stable agent key — identifies the parent outcome. */
837
+ /** An outcome STEP's stable agent key — identifies the parent outcome. Passing
838
+ * the outcome's own `agentKey` here fails with `422`. */
741
839
  agentKey: string;
742
840
  delta: number;
743
841
  notes?: string;
@@ -846,6 +944,17 @@ interface OutcomeStepInput {
846
944
  interface Outcome {
847
945
  id: string;
848
946
  name: string;
947
+ /**
948
+ * The OUTCOME's own stable agent key — its durable identity, mirroring
949
+ * {@link Credit.agentKey}. Lowercased `[a-z0-9._-]`, max 80 chars, unique per
950
+ * organisation. A rename never changes it.
951
+ *
952
+ * Distinct from each step's `agentKey` in `steps`, and in a SEPARATE
953
+ * namespace: usage is always reported against a STEP's key
954
+ * (`signals.outcome({ agentKey })`), never this one. This key identifies the
955
+ * outcome itself when creating or updating it.
956
+ */
957
+ agentKey: string;
849
958
  description: string | null;
850
959
  isActive: boolean;
851
960
  marginPercent: number;
@@ -874,6 +983,15 @@ interface OutcomeDetail extends Outcome {
874
983
  }
875
984
  interface CreateOutcomeInput {
876
985
  name: string;
986
+ /**
987
+ * The OUTCOME's own stable agent key — REQUIRED. Lowercased `[a-z0-9._-]`,
988
+ * max 80 chars, unique per organisation (the server lowercases and trims it).
989
+ *
990
+ * Separate namespace from `steps[].agentKey`: this names the outcome, a step
991
+ * key names a step and is what `signals.outcome()` reports against. Omitting
992
+ * it is rejected with a `ValidationError` (400).
993
+ */
994
+ agentKey: string;
877
995
  basePrice: number;
878
996
  marginPercent: number;
879
997
  pricePerOutcome: number;
@@ -973,6 +1091,160 @@ interface UnitInput {
973
1091
  customerId: string;
974
1092
  agentKey: string;
975
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;
976
1248
  }
977
1249
  /** One serialized unit-consumption event — the result of `signals.unit()` and
978
1250
  * each element of `UnitUsageList.events`. */
@@ -1135,6 +1407,35 @@ declare class Transport {
1135
1407
  private buildUrl;
1136
1408
  }
1137
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
+
1138
1439
  /**
1139
1440
  * The `credits` resource — manage the org's credit *types* (the catalog). A
1140
1441
  * credit's stable `agentKey` is what you pass to `signals.credit()` when
@@ -1225,12 +1526,12 @@ declare class Customers {
1225
1526
  plan(id: string): Promise<CustomerPlan>;
1226
1527
  /** List a customer's members. `GET …/:id/members`. */
1227
1528
  listMembers(id: string): Promise<Member[]>;
1228
- /** Add a member to a customer. Duplicate `employeeId` → ConflictError. */
1229
- addMember(id: string, input: AddMemberInput): Promise<Member>;
1230
- /** Update a member's profile (role is fixed). `PATCH …/:id/members/:memberId`. */
1231
- updateMember(id: string, memberId: string, input: UpdateMemberInput): Promise<Member>;
1232
- /** Remove a member from a customer. `DELETE …/:id/members/:memberId`. */
1233
- 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>;
1234
1535
  /** The customer's wallet balance + transactions. `GET …/:id/wallet`. */
1235
1536
  wallet(id: string, params?: {
1236
1537
  limit?: number;
@@ -1280,8 +1581,14 @@ declare class Invoices {
1280
1581
 
1281
1582
  /**
1282
1583
  * The `outcomes` resource — manage the org's outcome *types* (the catalog). An
1283
- * outcome is a multi-step workflow; each step has a stable `agentKey` you pass
1284
- * to `signals.outcome()`. `/api/v1/outcomes`.
1584
+ * outcome is a multi-step workflow. `/api/v1/outcomes`.
1585
+ *
1586
+ * Two distinct kinds of agent key, in separate namespaces, each unique per
1587
+ * organisation — do not conflate them:
1588
+ * - the OUTCOME's own `agentKey`, REQUIRED on `create()` and `update()`, which
1589
+ * names the outcome itself in the catalogue;
1590
+ * - each STEP's `agentKey`, which is what you pass to `signals.outcome()` to
1591
+ * report usage and to the outcome-adjust API.
1285
1592
  */
1286
1593
  declare class Outcomes {
1287
1594
  private readonly transport;
@@ -1387,7 +1694,17 @@ declare class Purchases {
1387
1694
 
1388
1695
  /** One buffered, wire-ready signal awaiting a background flush. */
1389
1696
  interface QueuedSignal {
1390
- /** 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. */
1391
1708
  body: Record<string, unknown>;
1392
1709
  /** The original signal — passed to onError/onDrop hooks. */
1393
1710
  signal: Signal;
@@ -1426,20 +1743,54 @@ declare class Flusher {
1426
1743
  close(): Promise<void>;
1427
1744
  }
1428
1745
 
1429
- /** 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
+ */
1430
1763
  declare function signalToWire(signal: Signal): Record<string, unknown>;
1431
1764
  /**
1432
1765
  * The `signals` resource: record usage signals and read them back.
1433
1766
  *
1434
- * In async mode (default), `track()` buffers the signal and returns immediately
1435
- * with `{ queued: true }`. Pass `{ wait: true }` (or construct the client in
1436
- * `sync` mode) to await the send and receive the computed `usageLog`.
1767
+ * 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.
1770
+ *
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
+ * `verify()` is the way to validate and price a signal synchronously, and
1779
+ * `list()` is how you confirm one actually landed.
1437
1780
  */
1438
1781
  declare class Signals {
1439
1782
  private readonly transport;
1440
1783
  private readonly flusher;
1441
1784
  private readonly cfg;
1442
1785
  constructor(transport: Transport, flusher: Flusher, cfg: ResolvedConfig);
1786
+ /**
1787
+ * Record one usage signal, to the route its `type` selects:
1788
+ * `POST /api/v1/signal/credit` | `/signal/outcome` | `/signal/wallet`.
1789
+ *
1790
+ * Returns as soon as the signal is buffered (async mode) or the request has
1791
+ * been answered (`{ wait: true }` / sync mode). See {@link TrackResult}: a
1792
+ * resolved promise means "accepted", which is not the same as "billed".
1793
+ */
1443
1794
  track(signal: Signal, opts?: {
1444
1795
  wait?: boolean;
1445
1796
  }): Promise<TrackResult>;
@@ -1447,21 +1798,34 @@ declare class Signals {
1447
1798
  * Validate + price a signal WITHOUT recording it (server `dryRun`). Returns
1448
1799
  * the projected usage log so an integration can preflight before sending real
1449
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()`.
1450
1806
  */
1451
1807
  verify(signal: Signal): Promise<SerializedUsageLog | null>;
1452
- /** Meter a named credit. */
1808
+ /** Meter a named credit. `POST /api/v1/signal/credit`. */
1453
1809
  credit(input: CreditInput, opts?: {
1454
1810
  wait?: boolean;
1455
1811
  }): Promise<TrackResult>;
1456
- /** Debit the customer's wallet at model cost. */
1812
+ /** Debit the customer's wallet at model cost. `POST /api/v1/signal/wallet`. */
1457
1813
  wallet(input: WalletInput, opts?: {
1458
1814
  wait?: boolean;
1459
1815
  }): Promise<TrackResult>;
1460
- /** Advance one step of an outcome workflow. */
1816
+ /** Advance one step of an outcome workflow. `POST /api/v1/signal/outcome`. */
1461
1817
  outcome(input: OutcomeInput, opts?: {
1462
1818
  wait?: boolean;
1463
1819
  }): Promise<TrackResult>;
1464
- /** Recent usage logs + totals for a customer. `GET /api/v1/usage`. */
1820
+ /**
1821
+ * Recent usage logs + totals for a customer. `GET /api/v1/usage`.
1822
+ *
1823
+ * Still the legacy path, deliberately. The `/api/v1/signal/*` family is
1824
+ * POST-only — it replaced how usage is INGESTED, not how it is read — so
1825
+ * `GET /api/v1/usage` has no successor and is not deprecated. It is also the
1826
+ * only way to prove a signal landed, which matters more now that ingestion
1827
+ * answers `202` for everything.
1828
+ */
1465
1829
  list(params: {
1466
1830
  customerId: string;
1467
1831
  limit?: number;
@@ -1469,12 +1833,44 @@ declare class Signals {
1469
1833
  /**
1470
1834
  * Record one unit-metered event (a fixed-price meter with no tokens),
1471
1835
  * addressed to a catalog unit by its `agentKey`. Sent immediately — unit
1472
- * events are not buffered. `POST /api/v1/units`.
1836
+ * events are not buffered. `POST /api/v1/signal/unit`.
1837
+ *
1838
+ * Two things changed with the move off `POST /api/v1/units`, and both show up
1839
+ * in the return type. The route is now durable (the report lands in an inbox
1840
+ * before it is priced, so a failure parks as Pending and can be retried
1841
+ * instead of losing a billable event) and it answers `202` when it queues —
1842
+ * so this resolves {@link UnitTrackResult}, not a bare `{ unitUsage }`.
1843
+ * Branch on `queued`.
1844
+ */
1845
+ unit(input: UnitInput): Promise<UnitTrackResult>;
1846
+ /**
1847
+ * Close ONE occurrence of a composite — a distinct tag value.
1848
+ * `POST /api/v1/signal/composite/complete`.
1849
+ *
1850
+ * A composite bundles several credits / outcomes / units and is billed as one
1851
+ * thing. The occurrence opens on the first tagged signal and costs nothing;
1852
+ * this call is the moment it is billed. Only your code knows when the
1853
+ * workflow ended, so this is a DECLARATION, not a hint: tagged signals may
1854
+ * arrive in any order and the occurrence stays open and unbilled until you
1855
+ * say otherwise. Put it on the line before your own `return`.
1856
+ *
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.
1860
+ *
1861
+ * Closing twice is safe: the second call answers `ALREADY_COMPLETED` and
1862
+ * cannot bill again. Calling it EARLY is also safe — before every entitlement
1863
+ * the composite is restricted to has reported, or while an ADVANCE pool is
1864
+ * full — the server parks the ping and applies it when the blocker clears,
1865
+ * rather than failing it.
1866
+ */
1867
+ completeComposite(input: CompositeCompletionInput): Promise<CompositeCompletionResult>;
1868
+ /**
1869
+ * A customer's unit balances + recent unit events. `GET /api/v1/units`.
1870
+ *
1871
+ * Legacy path for the same reason as {@link list}: the signal family is
1872
+ * POST-only, so the read side has no successor and is not deprecated.
1473
1873
  */
1474
- unit(input: UnitInput): Promise<{
1475
- unitUsage: SerializedUnitUsage;
1476
- }>;
1477
- /** A customer's unit balances + recent unit events. `GET /api/v1/units`. */
1478
1874
  unitUsage(params: {
1479
1875
  customerId: string;
1480
1876
  limit?: number;
@@ -1569,6 +1965,8 @@ declare class ClockNext {
1569
1965
  readonly payments: Payments;
1570
1966
  /** Manage the credit-type catalog. */
1571
1967
  readonly credits: Credits;
1968
+ /** Manage the composite catalog — bundles billed as one thing. */
1969
+ readonly composites: Composites;
1572
1970
  /** Manage the outcome-type catalog. */
1573
1971
  readonly outcomes: Outcomes;
1574
1972
  /** Manage the unit-type catalog. */
@@ -1639,4 +2037,4 @@ declare class NetworkError extends ClockNextError {
1639
2037
  constructor(message: string);
1640
2038
  }
1641
2039
 
1642
- 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 };
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 };