@proveanything/smartlinks 2.1.15 → 2.1.17

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.
@@ -490,6 +490,53 @@ export interface ServerFunctionBookings {
490
490
  }>;
491
491
  };
492
492
  }
493
+ /** One write in ctx.sl.transaction. */
494
+ export type RecordTransactionOp = {
495
+ op: 'create';
496
+ record: Record<string, any>;
497
+ } | {
498
+ op: 'update';
499
+ recordId: string;
500
+ fields: Record<string, any>;
501
+ } | {
502
+ op: 'adjust';
503
+ recordId: string;
504
+ by: Record<string, number>;
505
+ atLeast?: Record<string, number>;
506
+ atMost?: Record<string, number>;
507
+ } | {
508
+ op: 'delete';
509
+ recordId: string;
510
+ }
511
+ /** An event in the app's history (BigQuery): commits with the other ops, delivered after. Needs sl:history:write. */
512
+ | {
513
+ op: 'log';
514
+ type: string;
515
+ data?: Record<string, any>;
516
+ entityId?: string;
517
+ };
518
+ /** One event from an app's history. */
519
+ export interface AppHistoryEvent {
520
+ id: string;
521
+ at: string;
522
+ type: string;
523
+ entityId: string | null;
524
+ data: Record<string, any>;
525
+ actorType: 'user' | 'contact' | null;
526
+ actorId: string | null;
527
+ }
528
+ /** Reading an app's history: its own events in this collection, newest first. */
529
+ export interface AppHistoryQuery {
530
+ types?: string | string[];
531
+ entityId?: string;
532
+ /** Exact match on top-level data keys (one value or several). */
533
+ data?: Record<string, string | number | boolean | Array<string | number | boolean>>;
534
+ /** ISO dates. Default: the last 30 days; at most 400 days. */
535
+ from?: string;
536
+ to?: string;
537
+ /** Default 100, at most 1000. */
538
+ limit?: number;
539
+ }
493
540
  export interface ServerFunctionSl {
494
541
  /** THIS app's bookings, slots and blocks (`sl:bookings:read` / `sl:bookings:write`). */
495
542
  bookings: ServerFunctionBookings;
@@ -502,8 +549,48 @@ export interface ServerFunctionSl {
502
549
  */
503
550
  appRecords: {
504
551
  claim(fields: Record<string, any>): Promise<AppRecordClaimResult>;
552
+ /**
553
+ * Numbers in a record's `data`, changed in place in one statement: two scanners on one bin never lose
554
+ * a count. `by` `{ qty: -5 }` (missing counts as 0). `atLeast` / `atMost` are checked on the values
555
+ * AFTER the change: `{ atLeast: { qty: 0 } }` never takes a bin below zero. Refusals carry `code`:
556
+ * `NOT_FOUND`, `NOT_A_NUMBER`, `CONDITION_FAILED` (with `current` values). → the updated record.
557
+ */
558
+ adjust(recordId: string, by: Record<string, number>, opts?: {
559
+ atLeast?: Record<string, number>;
560
+ atMost?: Record<string, number>;
561
+ }): Promise<any>;
562
+ /** Totals over THIS app's records: count, sum/avg/min/max of data numbers, grouped by data keys. */
563
+ aggregate(params?: import('./appObjects.js').AggregateRequest): Promise<import('./appObjects.js').AggregateResponse & Record<string, unknown>>;
505
564
  [method: string]: any;
506
565
  };
566
+ /**
567
+ * Several record writes that land together or not at all (take 5 from bin A AND add 5 to order line B
568
+ * AND log the move). Needs `sl:records:write`; THIS app's records in THIS collection only. A failing op
569
+ * rolls everything back; its error says which (`opIndex`) and why (`code`). With `idempotencyKey`, a
570
+ * repeat of the key (a scanner retrying after a dropped signal) returns the first results
571
+ * (`replayed: true`) instead of writing twice; keys are remembered for 30 days.
572
+ */
573
+ transaction(ops: RecordTransactionOp[], opts?: {
574
+ idempotencyKey?: string;
575
+ }): Promise<{
576
+ results: any[];
577
+ replayed: boolean;
578
+ }>;
579
+ /**
580
+ * The app's HISTORY: append-only events kept in BigQuery (stock moved, a count taken, an order shipped),
581
+ * so the app's records hold current state and never grow with activity. `log` (`sl:history:write`)
582
+ * stores one event (it arrives within seconds; a failed delivery is retried, never lost); inside a
583
+ * transaction use a `log` op instead, so it commits with the change. `query` (`sl:history:read`) reads
584
+ * this app's events in this collection, newest first.
585
+ */
586
+ history: {
587
+ log(type: string, data?: Record<string, any>, opts?: {
588
+ entityId?: string;
589
+ }): Promise<{
590
+ id: string;
591
+ }>;
592
+ query(params?: AppHistoryQuery): Promise<AppHistoryEvent[]>;
593
+ };
507
594
  /** Atomic named counters for this app in this collection (`sl:data:write` / `sl:data:read`). */
508
595
  counters: ServerFunctionCounters;
509
596
  /** Tags (`sl:tags:read`): `resolve(id)` → the real tag in THIS collection, or `null` for a made-up id. */
@@ -55,8 +55,29 @@ export interface AggregateRequest {
55
55
  expires_at?: {
56
56
  lte?: string;
57
57
  };
58
+ updated_at?: {
59
+ gte?: string;
60
+ lte?: string;
61
+ };
62
+ /** Records only: exact match on top-level data keys, one value or any of several. */
63
+ data?: Record<string, string | number | boolean | Array<string | number | boolean>>;
64
+ /** Records only: numeric comparisons on top-level data keys (numbers only; others never match). */
65
+ dataRange?: Record<string, {
66
+ gte?: number;
67
+ gt?: number;
68
+ lte?: number;
69
+ lt?: number;
70
+ }>;
58
71
  };
72
+ /**
73
+ * Columns per resource; records also take top-level data keys as `'data.<key>'` (e.g. `'data.location'`),
74
+ * up to 5. Each group comes back with its key under the same name.
75
+ */
59
76
  groupBy?: string[];
77
+ /**
78
+ * `'count'`; records also take `'sum:<key>'`, `'avg:<key>'`, `'min:<key>'`, `'max:<key>'` over numbers in a
79
+ * top-level data key (non-numbers ignored) → `sum_<key>`, `avg_<key>`… in the result.
80
+ */
60
81
  metrics?: string[];
61
82
  timeSeriesField?: string;
62
83
  timeSeriesInterval?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
@@ -66,7 +87,7 @@ export interface AggregateRequest {
66
87
  */
67
88
  export interface AggregateResponse {
68
89
  groups?: ({
69
- count: number;
90
+ count?: number;
70
91
  } & Record<string, unknown>)[];
71
92
  timeSeries?: ({
72
93
  bucket: string;
@@ -129,21 +129,32 @@ export type ClaimSetScope = {
129
129
  partnerId: string;
130
130
  };
131
131
  export type ClaimSetTagType = 'ntag424' | 'ntag21x' | 'qr';
132
- export type ClaimSetRangeStatus = 'minting' | 'minted' | 'sent' | 'received' | 'assigned';
132
+ /**
133
+ * range: a contiguous block of indexes minted up front. open: grows one index per tag programmed on a phone
134
+ * (count unknown in advance), closed into a range later. group: existing tags added by tapping, assigned by
135
+ * hand and moved together; no indexes.
136
+ */
137
+ export type ClaimSetRangeKind = 'range' | 'open' | 'group';
138
+ export type ClaimSetRangeStatus = 'minting' | 'minted' | 'sent' | 'received' | 'assigned' | 'open' | 'group';
133
139
  export interface ClaimSetRange {
134
140
  id: string;
135
141
  claimSetId: string;
136
142
  namespace: string;
143
+ kind: ClaimSetRangeKind;
137
144
  name: string;
138
145
  notes: string | null;
139
- startIndex: number;
140
- endIndex: number;
146
+ /** null for a group */
147
+ startIndex: number | null;
148
+ /** open: the highest index minted so far (null until the first); group: null */
149
+ endIndex: number | null;
150
+ /** range: the block; open: minted so far; group: its members */
141
151
  quantity: number;
142
152
  tagType: ClaimSetTagType;
153
+ /** open ranges and groups are always 'sidekick' */
143
154
  fulfilment: 'printer' | 'sidekick';
144
- /** The stored step: minting | minted | sent | received */
155
+ /** The stored step: minting | minted | sent | received (always 'minted' for open ranges and groups) */
145
156
  stage: 'minting' | 'minted' | 'sent' | 'received';
146
- /** What to show: the stage, or 'assigned' once every tag is assigned */
157
+ /** What to show: the stage, 'open', 'group', or 'assigned' once every tag is assigned */
147
158
  status: ClaimSetRangeStatus;
148
159
  intendedCollectionId: string | null;
149
160
  intendedProductId: string | null;
@@ -162,9 +173,11 @@ export interface ClaimSetRange {
162
173
  updatedAt: string;
163
174
  }
164
175
  export interface CreateClaimSetRangeRequest {
176
+ /** Default 'range'. Owners and admins make ranges and open ranges; assigners may make groups too. */
177
+ kind?: ClaimSetRangeKind;
165
178
  name: string;
166
- /** 1 to 10,000 */
167
- quantity: number;
179
+ /** 1 to 10,000 (a range); ignored for open ranges and groups */
180
+ quantity?: number;
168
181
  /** Pre-assign every tag at mint time (you must administer the collection) */
169
182
  intendedCollectionId?: string;
170
183
  intendedProductId?: string;
@@ -194,6 +207,10 @@ export interface ClaimSetRangeTag {
194
207
  productId: string | null;
195
208
  batchId: string | null;
196
209
  proofId: string | null;
210
+ /** The range it was minted into, if any */
211
+ rangeId: string | null;
212
+ /** The group it was added to, if any */
213
+ groupId: string | null;
197
214
  programmedAt: string | null;
198
215
  scans: number;
199
216
  }
@@ -207,8 +224,42 @@ export interface ClaimSetRangeNextTag extends ClaimSetRangeTag {
207
224
  url: string;
208
225
  piccOffset?: number;
209
226
  mirrorOffset?: number;
210
- remaining: number;
227
+ /** Tags still without a chip; null for an open range (it grows as you go) */
228
+ remaining: number | null;
229
+ }
230
+ /** Add an existing tag of the set to a group, and (with a collection, or clear) assign it as you go. */
231
+ export interface AddClaimSetRangeMemberRequest {
232
+ /** The tag's code, printed or bare… */
233
+ code?: string;
234
+ /** …or its chip UID (14 hex) */
235
+ uid?: string;
236
+ collectionId?: string;
237
+ productId?: string;
238
+ batchId?: string;
239
+ data?: Record<string, unknown>;
240
+ clear?: boolean;
211
241
  }
242
+ export interface AddClaimSetRangeMemberResponse {
243
+ tag: ClaimSetRangeTag;
244
+ range: ClaimSetRange;
245
+ /** The assignment, with the same skips as assign (has_proof, assigned_elsewhere) */
246
+ assigned: {
247
+ changed: number;
248
+ skipped: Array<{
249
+ code: string;
250
+ index: number | null;
251
+ reason: 'other_range' | 'has_proof' | 'assigned_elsewhere';
252
+ collectionId?: string;
253
+ }>;
254
+ };
255
+ /** The group the tag was taken from, if it was in another one */
256
+ movedFrom: string | null;
257
+ }
258
+ /** Closing an open range: the range it became, or that it was removed because nothing was minted. */
259
+ export type CloseClaimSetRangeResponse = ClaimSetRange | {
260
+ id: string;
261
+ deleted: true;
262
+ };
212
263
  export type ClaimSetImportResult = 'ok' | 'unchanged' | 'unknown_code' | 'duplicate_row' | 'index_mismatch' | 'no_uid' | 'bad_uid' | 'duplicate_uid' | 'uid_in_use' | 'uid_conflict';
213
264
  export interface ImportClaimSetRangeUidsRequest {
214
265
  /** The manufacturer's file as CSV text (the file we sent, with UID filled in) */
@@ -310,6 +361,8 @@ export interface PartnerTagHit {
310
361
  url: string | null;
311
362
  };
312
363
  range: ClaimSetRange | null;
364
+ /** The group the tag was added to, if any */
365
+ group: ClaimSetRange | null;
313
366
  }
314
367
  export interface CreatePartnerClaimSetRequest {
315
368
  name: string;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * An app reporting an error it saw (errors.report): kept in the platform's one error log (CloudEvents + ECS
3
+ * rows in BigQuery), the same place server-side errors go. Say what it was about with entityType/entityId
4
+ * (a tag's chip UID, a product id, an order id), put routing facts in labels and the detail in context.
5
+ * Never include keys, passwords or tokens: the server drops any field whose name looks like one.
6
+ */
7
+ export interface ClientErrorReport {
8
+ /** Which app: 'sidekick', 'studio', a site's app id. Default 'client'. */
9
+ app?: string;
10
+ appVersion?: string;
11
+ /** The collection it concerns, when there is one (owners see their collection's errors) */
12
+ collectionId?: string;
13
+ /** What was being done: 'tag.program', 'order.submit', … */
14
+ operation?: string;
15
+ severity?: 'debug' | 'info' | 'warning' | 'error' | 'critical';
16
+ /** A short stable code: 'TAG_LOST', 'TIMEOUT', … (this or message is required) */
17
+ errorCode?: string;
18
+ errorType?: string;
19
+ message?: string;
20
+ stack?: string;
21
+ /** What it was about: 'tag', 'product', 'order', … */
22
+ entityType?: string;
23
+ /** …and which one: a chip UID (normalised for 'tag'), a product id, … */
24
+ entityId?: string;
25
+ labels?: Record<string, unknown>;
26
+ context?: Record<string, unknown>;
27
+ /** An operation id to group by (a batch, a job) */
28
+ refId?: string;
29
+ correlationId?: string;
30
+ }
31
+ /** One kept error event, as the lookups return it (newest first). */
32
+ export interface ClientErrorEvent {
33
+ id: string;
34
+ time: string;
35
+ event_category: string;
36
+ event_action: string | null;
37
+ log_level: string;
38
+ error_code: string | null;
39
+ error_message: string | null;
40
+ service_name: string | null;
41
+ service_version: string | null;
42
+ collection_id: string | null;
43
+ user_id: string | null;
44
+ labels: Record<string, unknown> | null;
45
+ context: Record<string, unknown> | null;
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -45,3 +45,4 @@ export * from "./research.js";
45
45
  export * from "./config.js";
46
46
  export * from "./widgets.js";
47
47
  export * from "./headless.js";
48
+ export * from "./clientLog.js";
@@ -47,3 +47,4 @@ export * from "./research.js";
47
47
  export * from "./config.js";
48
48
  export * from "./widgets.js";
49
49
  export * from "./headless.js";
50
+ export * from "./clientLog.js";
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.1.15 | Generated: 2026-11-09T15:17:42.472Z
3
+ Version: 2.1.17 | Generated: 2026-11-11T09:04:21.796Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -144,6 +144,7 @@ The Smartlinks SDK is organized into the following namespaces:
144
144
  - **bookings** - Functions for bookings operations
145
145
  - **config** - Functions for config operations
146
146
  - **containers** - Functions for containers operations
147
+ - **errors** - Functions for errors operations
147
148
  - **facets** - Functions for facets operations
148
149
  - **functions** - Functions for functions operations
149
150
  - **http** - Functions for http operations
@@ -2838,11 +2839,46 @@ interface ServerFunctionBookings {
2838
2839
  }
2839
2840
  ```
2840
2841
 
2842
+ **AppHistoryEvent** (interface)
2843
+ ```typescript
2844
+ interface AppHistoryEvent {
2845
+ id: string;
2846
+ at: string;
2847
+ type: string;
2848
+ entityId: string | null;
2849
+ data: Record<string, any>;
2850
+ actorType: 'user' | 'contact' | null;
2851
+ actorId: string | null;
2852
+ }
2853
+ ```
2854
+
2855
+ **AppHistoryQuery** (interface)
2856
+ ```typescript
2857
+ interface AppHistoryQuery {
2858
+ types?: string | string[];
2859
+ entityId?: string;
2860
+ data?: Record<string, string | number | boolean | Array<string | number | boolean>>;
2861
+ from?: string;
2862
+ to?: string;
2863
+ limit?: number;
2864
+ }
2865
+ ```
2866
+
2841
2867
  **ServerFunctionSl** (interface)
2842
2868
  ```typescript
2843
2869
  interface ServerFunctionSl {
2844
2870
  bookings: ServerFunctionBookings;
2845
- appRecords: { claim(fields: Record<string, any>): Promise<AppRecordClaimResult>; [method: string]: any };
2871
+ appRecords: {
2872
+ claim(fields: Record<string, any>): Promise<AppRecordClaimResult>;
2873
+ adjust(recordId: string, by: Record<string, number>, opts?: { atLeast?: Record<string, number>; atMost?: Record<string, number> }): Promise<any>;
2874
+ aggregate(params?: import('./appObjects').AggregateRequest): Promise<import('./appObjects').AggregateResponse & Record<string, unknown>>;
2875
+ [method: string]: any;
2876
+ };
2877
+ transaction(ops: RecordTransactionOp[], opts?: { idempotencyKey?: string }): Promise<{ results: any[]; replayed: boolean }>;
2878
+ history: {
2879
+ log(type: string, data?: Record<string, any>, opts?: { entityId?: string }): Promise<{ id: string }>;
2880
+ query(params?: AppHistoryQuery): Promise<AppHistoryEvent[]>;
2881
+ };
2846
2882
  counters: ServerFunctionCounters;
2847
2883
  tags: { resolve(id: string): Promise<ResolvedTag | null> };
2848
2884
  contacts: {
@@ -3298,6 +3334,8 @@ interface GetCollectionWidgetsOptions {
3298
3334
 
3299
3335
  **AppFunctionAuthority** = `'caller' | 'collection'`
3300
3336
 
3337
+ **RecordTransactionOp** = ``
3338
+
3301
3339
  ### appObjects
3302
3340
 
3303
3341
  **PaginatedResponse<T>** (interface)
@@ -3324,9 +3362,12 @@ interface AggregateRequest {
3324
3362
  created_at?: { gte?: string; lte?: string }
3325
3363
  closed_at?: '__notnull__' | { gte?: string; lte?: string } // cases only
3326
3364
  expires_at?: { lte?: string } // records only
3365
+ updated_at?: { gte?: string; lte?: string } // records only
3366
+ data?: Record<string, string | number | boolean | Array<string | number | boolean>>
3367
+ dataRange?: Record<string, { gte?: number; gt?: number; lte?: number; lt?: number }>
3327
3368
  }
3328
- groupBy?: string[] // see per-resource allowed values
3329
- metrics?: string[] // see per-resource allowed values below
3369
+ groupBy?: string[]
3370
+ metrics?: string[]
3330
3371
  timeSeriesField?: string // e.g. 'created_at'
3331
3372
  timeSeriesInterval?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'
3332
3373
  }
@@ -3335,7 +3376,7 @@ interface AggregateRequest {
3335
3376
  **AggregateResponse** (interface)
3336
3377
  ```typescript
3337
3378
  interface AggregateResponse {
3338
- groups?: ({ count: number } & Record<string, unknown>)[]
3379
+ groups?: ({ count?: number } & Record<string, unknown>)[]
3339
3380
  timeSeries?: ({ bucket: string; count: number } & Record<string, unknown>)[]
3340
3381
  count?: number
3341
3382
  avg_close_time_seconds?: number
@@ -5775,10 +5816,11 @@ interface ClaimSetRange {
5775
5816
  id: string
5776
5817
  claimSetId: string
5777
5818
  namespace: string
5819
+ kind: ClaimSetRangeKind
5778
5820
  name: string
5779
5821
  notes: string | null
5780
- startIndex: number
5781
- endIndex: number
5822
+ startIndex: number | null
5823
+ endIndex: number | null
5782
5824
  quantity: number
5783
5825
  tagType: ClaimSetTagType
5784
5826
  fulfilment: 'printer' | 'sidekick'
@@ -5801,8 +5843,9 @@ interface ClaimSetRange {
5801
5843
  **CreateClaimSetRangeRequest** (interface)
5802
5844
  ```typescript
5803
5845
  interface CreateClaimSetRangeRequest {
5846
+ kind?: ClaimSetRangeKind
5804
5847
  name: string
5805
- quantity: number
5848
+ quantity?: number
5806
5849
  intendedCollectionId?: string
5807
5850
  intendedProductId?: string
5808
5851
  intendedBatchId?: string
@@ -5837,6 +5880,8 @@ interface ClaimSetRangeTag {
5837
5880
  productId: string | null
5838
5881
  batchId: string | null
5839
5882
  proofId: string | null
5883
+ rangeId: string | null
5884
+ groupId: string | null
5840
5885
  programmedAt: string | null
5841
5886
  scans: number
5842
5887
  }
@@ -5850,6 +5895,29 @@ interface ClaimSetRangeTagsPage {
5850
5895
  }
5851
5896
  ```
5852
5897
 
5898
+ **AddClaimSetRangeMemberRequest** (interface)
5899
+ ```typescript
5900
+ interface AddClaimSetRangeMemberRequest {
5901
+ code?: string
5902
+ uid?: string
5903
+ collectionId?: string
5904
+ productId?: string
5905
+ batchId?: string
5906
+ data?: Record<string, unknown>
5907
+ clear?: boolean
5908
+ }
5909
+ ```
5910
+
5911
+ **AddClaimSetRangeMemberResponse** (interface)
5912
+ ```typescript
5913
+ interface AddClaimSetRangeMemberResponse {
5914
+ tag: ClaimSetRangeTag
5915
+ range: ClaimSetRange
5916
+ assigned: { changed: number; skipped: Array<{ code: string; index: number | null; reason: 'other_range' | 'has_proof' | 'assigned_elsewhere'; collectionId?: string }> }
5917
+ movedFrom: string | null
5918
+ }
5919
+ ```
5920
+
5853
5921
  **ImportClaimSetRangeUidsRequest** (interface)
5854
5922
  ```typescript
5855
5923
  interface ImportClaimSetRangeUidsRequest {
@@ -5939,6 +6007,7 @@ interface PartnerTagHit {
5939
6007
  set: PartnerClaimSet | null
5940
6008
  tag: ClaimSetRangeTag & { url: string | null }
5941
6009
  range: ClaimSetRange | null
6010
+ group: ClaimSetRange | null
5942
6011
  }
5943
6012
  ```
5944
6013
 
@@ -5958,10 +6027,56 @@ type ClaimSetScope = { collectionId: string }
5958
6027
 
5959
6028
  **ClaimSetTagType** = `'ntag424' | 'ntag21x' | 'qr'`
5960
6029
 
5961
- **ClaimSetRangeStatus** = `'minting' | 'minted' | 'sent' | 'received' | 'assigned'`
6030
+ **ClaimSetRangeKind** = `'range' | 'open' | 'group'`
6031
+
6032
+ **ClaimSetRangeStatus** = `'minting' | 'minted' | 'sent' | 'received' | 'assigned' | 'open' | 'group'`
6033
+
6034
+ **CloseClaimSetRangeResponse** = `ClaimSetRange | { id: string`
5962
6035
 
5963
6036
  **ClaimSetImportResult** = ``
5964
6037
 
6038
+ ### clientLog
6039
+
6040
+ **ClientErrorReport** (interface)
6041
+ ```typescript
6042
+ interface ClientErrorReport {
6043
+ app?: string
6044
+ appVersion?: string
6045
+ collectionId?: string
6046
+ operation?: string
6047
+ severity?: 'debug' | 'info' | 'warning' | 'error' | 'critical'
6048
+ errorCode?: string
6049
+ errorType?: string
6050
+ message?: string
6051
+ stack?: string
6052
+ entityType?: string
6053
+ entityId?: string
6054
+ labels?: Record<string, unknown>
6055
+ context?: Record<string, unknown>
6056
+ refId?: string
6057
+ correlationId?: string
6058
+ }
6059
+ ```
6060
+
6061
+ **ClientErrorEvent** (interface)
6062
+ ```typescript
6063
+ interface ClientErrorEvent {
6064
+ id: string
6065
+ time: string
6066
+ event_category: string
6067
+ event_action: string | null
6068
+ log_level: string
6069
+ error_code: string | null
6070
+ error_message: string | null
6071
+ service_name: string | null
6072
+ service_version: string | null
6073
+ collection_id: string | null
6074
+ user_id: string | null
6075
+ labels: Record<string, unknown> | null
6076
+ context: Record<string, unknown> | null
6077
+ }
6078
+ ```
6079
+
5965
6080
  ### collection
5966
6081
 
5967
6082
  **Collection** (interface)
@@ -12464,6 +12579,15 @@ Name, notes, order details, or a stage move (sent to the printer, received).
12464
12579
  **resume**(scope: ClaimSetScope, claimSetId: string, rangeId: string) → `Promise<ClaimSetRange>`
12465
12580
  Finish minting a range that was interrupted.
12466
12581
 
12582
+ **close**(scope: ClaimSetScope, claimSetId: string, rangeId: string) → `Promise<CloseClaimSetRangeResponse>`
12583
+ Close an open range: it becomes a range with the quantity it reached (removed if nothing was minted).
12584
+
12585
+ **addMember**(scope: ClaimSetScope, claimSetId: string, groupId: string, params: AddClaimSetRangeMemberRequest) → `Promise<AddClaimSetRangeMemberResponse>`
12586
+ Put an existing tag of the set into a group (by code or chip UID), assigning it if a collection is given.
12587
+
12588
+ **removeMember**(scope: ClaimSetScope, claimSetId: string, groupId: string, code: string) → `Promise<`
12589
+ Take a tag out of a group. Its assignment is left as it is.
12590
+
12467
12591
  **tags**(scope: ClaimSetScope, claimSetId: string, rangeId: string,
12468
12592
  options: { from?: number; limit?: number; keys?: boolean } = {}) → `Promise<ClaimSetRangeTagsPage>`
12469
12593
  The range's tags in index order, a page at a time (keys: owners/admins only).
@@ -12790,6 +12914,10 @@ Update a crate for a collection (admin only). ```typescript const updated = awai
12790
12914
  crateId: string) → `Promise<DeleteCrateResponse>`
12791
12915
  Delete a crate for a collection (admin only). This performs a soft delete. ```typescript await crate.remove('coll_123', 'crate_abc123') ```
12792
12916
 
12917
+ ### errors
12918
+
12919
+ **report**(event: ClientErrorReport) → `Promise<`
12920
+
12793
12921
  ### facets
12794
12922
 
12795
12923
  **list**(collectionId: string,
@@ -13422,6 +13550,9 @@ A new claim set for the partner, on one of its tap domains (owners and admins).
13422
13550
  **lookupTags**(partnerId: string, query: { uid?: string; code?: string }) → `Promise<PartnerTagHit[]>`
13423
13551
  Find a tag across the partner's sets by its chip UID (8 or 14 hex) or its code (printed or bare). Never returns keys.
13424
13552
 
13553
+ **tagAttempts**(partnerId: string, uid: string) → `Promise<ClientErrorEvent[]>`
13554
+ What phones reported about one chip (errors.report with entityType 'tag'), newest first: a tag that failed to program or register, with the host's NFC log in context. For a chip in the partner's sets, or attempts that named one of them.
13555
+
13425
13556
  ### podcast
13426
13557
 
13427
13558
  **generate**(collectionId: string,
@@ -807,6 +807,29 @@ await app.records.upsert(collectionId, appId, {
807
807
  // customId is stored for later reverse-lookup via ?customId=&sourceSystem=
808
808
  ```
809
809
 
810
+ ### Totals over data (sum, average, min, max)
811
+
812
+ Records also total the numbers in their `data`, grouped by top-level data keys, with data filters and
813
+ numeric ranges. Stock per location, forecast per SKU:
814
+
815
+ ```typescript
816
+ const stock = await app.records.aggregate(collectionId, appId, {
817
+ groupBy: ['data.location'], // up to 5: columns or 'data.<key>'
818
+ metrics: ['count', 'sum:qty', 'min:qty'], // count; sum: / avg: / min: / max: + a data key
819
+ filters: {
820
+ record_type: 'bin',
821
+ data: { warehouse: ['north', 'south'] }, // exact match, one value or any of several
822
+ dataRange: { qty: { gt: 0 } }, // gte / gt / lte / lt, numbers only
823
+ updated_at: { gte: '2026-11-01T00:00:00Z' },
824
+ },
825
+ }, true)
826
+ // stock.groups → [{ 'data.location': 'A1', count: 12, sum_qty: 340, min_qty: 2 }, …]
827
+ ```
828
+
829
+ Values that aren't numbers are left out of sums and averages. To CHANGE numbers safely (stock moves,
830
+ counts), use a server function: `ctx.sl.appRecords.adjust` and `ctx.sl.transaction`
831
+ ([server-functions.md](server-functions.md)).
832
+
810
833
  ### Counts by Record Type
811
834
 
812
835
  `aggregate()` returns counts grouped by `record_type` in a single round-trip — no separate endpoint needed: