@proveanything/smartlinks 1.15.6 → 1.15.10

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.
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.15.6 | Generated: 2026-07-19T08:21:33.985Z
3
+ Version: 1.15.10 | Generated: 2026-07-19T13:35:07.171Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -1447,7 +1447,6 @@ interface AppConfigSettings {
1447
1447
  addOns?: string[]
1448
1448
  requestedBasePlanId?: string
1449
1449
  itemRecordMode?: 'registered' | 'owned' | null
1450
- virtualItemsEnabled?: boolean
1451
1450
  system?: SystemBlock
1452
1451
  }
1453
1452
  ```
@@ -3698,8 +3697,6 @@ interface Collection {
3698
3697
  secondaryColor?: string
3699
3698
  portalUrl?: string // URL for the collection's portal (if applicable)
3700
3699
  allowAutoGenerateClaims?: boolean
3701
- variants: boolean // does this collection support variants?
3702
- batches: boolean // does this collection support batches?
3703
3700
  defaultAuthKitId: string // default auth kit for this collection, used for auth
3704
3701
  }
3705
3702
  ```
@@ -5881,6 +5878,23 @@ interface InteractionEventContext {
5881
5878
 
5882
5879
  **EffectConfig** = ``
5883
5880
 
5881
+ ### itemContext
5882
+
5883
+ **ItemContext** (interface)
5884
+ ```typescript
5885
+ interface ItemContext {
5886
+ isAuthentic: boolean
5887
+ status: ItemContextStatus
5888
+ source: 'nfc' | 'serial'
5889
+ errorMessage?: string
5890
+ isRescan?: boolean
5891
+ tag?: TagContext
5892
+ checkedAt: number
5893
+ }
5894
+ ```
5895
+
5896
+ **ItemContextStatus** = ``
5897
+
5884
5898
  ### jobs
5885
5899
 
5886
5900
  **Job** (interface)
@@ -6411,6 +6425,24 @@ interface NfcClaimTagRequest {
6411
6425
  }
6412
6426
  ```
6413
6427
 
6428
+ **TagContext** (interface)
6429
+ ```typescript
6430
+ interface TagContext {
6431
+ status: TagStatus
6432
+ tagId?: string
6433
+ claimSetId?: string
6434
+ codeId?: string
6435
+ count?: number
6436
+ previousCount?: number
6437
+ data?: Record<string, unknown>
6438
+ source: 'nfc-validate' | 'nfc-lookup' | 'tag-index' | 'none'
6439
+ validatedAt: number
6440
+ errorMessage?: string
6441
+ }
6442
+ ```
6443
+
6444
+ **TagStatus** = `'valid' | 'rescan' | 'invalid' | 'error'`
6445
+
6414
6446
  ### order
6415
6447
 
6416
6448
  **OrderItem** (interface)
@@ -7695,6 +7727,15 @@ interface SmartLinksWidgetProps {
7695
7727
  * accepted for backward compatibility.
7696
7728
  onNavigate?: (request: NavigationRequest | string) => void
7697
7729
  publicPortalUrl?: string
7730
+ * Authenticity context for the specific item (proof) the URL points at,
7731
+ * resolved via an NFC tap or a serial proof URL. `undefined` for
7732
+ * collection- and product-only URLs, where there's no item to verify.
7733
+ * See docs/item-context.md.
7734
+ itemContext?: ItemContext
7735
+ * @deprecated Use `itemContext.tag` instead. Kept for one release for
7736
+ * backward compatibility with scanner-aware apps that read raw NFC/SUN
7737
+ * data directly. See docs/item-context.md.
7738
+ tag?: TagContext
7698
7739
  size?: 'compact' | 'standard' | 'large'
7699
7740
  lang?: string
7700
7741
  translations?: Record<string, string>
@@ -7864,6 +7905,7 @@ interface ConditionParams {
7864
7905
  product?: ProductInfo
7865
7906
  proof?: ProofInfo
7866
7907
  collection?: CollectionInfo
7908
+ itemContext?: ItemContext
7867
7909
  stats?: StatsInfo
7868
7910
  fetchCondition?: (collectionId: string, conditionId: string) => Promise<ConditionSet | null>
7869
7911
  getLocation?: () => Promise<{ latitude: number; longitude: number }>
@@ -36,7 +36,6 @@ export interface AppConfigSettings {
36
36
 
37
37
  /** How the account handles individual physical items. */
38
38
  itemRecordMode?: 'registered' | 'owned' | null;
39
- virtualItemsEnabled?: boolean;
40
39
 
41
40
  // ── SYSTEM-OWNED, PUBLIC (reconcile writes; client read-only) ─────
42
41
  /** Resolved entitlement truth. Read this to gate features. */
@@ -245,6 +244,25 @@ Always resolve flags through `appConfiguration.isFeatureEnabled(collectionId, fl
245
244
  if you already have a `system` block from elsewhere — never read
246
245
  `cfg.system.features[flag]` directly.
247
246
 
247
+ #### 4.4.1 Common feature flags
248
+
249
+ `features` is an open map — any microapp can define its own key — but
250
+ these are the major, cross-cutting flags most integrations will care
251
+ about:
252
+
253
+ | Flag | Gates |
254
+ | -------------- | ---------------------------------------------------------------------- |
255
+ | `analytics` | Analytics surfaces (dashboards, reporting, event exploration). |
256
+ | `crm` | Customer interaction / CRM — contact records, broadcasts, segments. Sub-features hang off the `crm.*` dotted prefix, e.g. `crm.broadcasts`, `crm.segments`. |
257
+ | `hub` | The Hub module. |
258
+ | `portal` | QR code scan portals. |
259
+ | `virtualItems` | Virtual items — algorithmic per-item IDs with no persistent row (battery serials, scan-to-collect points, bulk QR sheets). Replaces the old root-level `virtualItemsEnabled` boolean, which has been removed (§8) — resolve this like any other feature flag, not as a separate config key. Independent of `itemRecordMode`. |
260
+ | `batches` | Batch support. Used to be the `Collection.batches` boolean; that field has been removed — this flag is now the only source of truth. |
261
+ | `variants` | Variant support. Used to be the `Collection.variants` boolean; that field has been removed — this flag is now the only source of truth. |
262
+
263
+ Same resolution rule as any flag (§4.4) — don't read these off
264
+ `cfg.system.features` directly, always go through `isFeatureEnabled()`.
265
+
248
266
  ### 4.5 `meters`
249
267
 
250
268
  Map keyed by add-on key. Apps reporting usage should call the
@@ -407,18 +425,14 @@ with a stale local copy.
407
425
  Clients MUST NOT write directly to `system` or `systemPrivate`.
408
426
  `entitlements-reconcile` merges user-owned fields through untouched.
409
427
 
410
- ## 8. Item handling (`itemRecordMode`, `virtualItemsEnabled`)
428
+ ## 8. Item handling (`itemRecordMode`)
411
429
 
412
- Two **independent** root-level, user-owned keys describing how the
413
- account treats individual physical items. Reconcile never touches
414
- them.
430
+ A root-level, user-owned key describing how the account treats
431
+ individual physical items. Reconcile never touches it.
415
432
 
416
- Three axes (do NOT conflate):
433
+ Two axes (do NOT conflate):
417
434
 
418
- 1. **Virtual items** — `virtualItemsEnabled: boolean`. Algorithmic
419
- IDs, no per-item row. Battery serials, scan-to-collect points,
420
- bulk QR sheets. Can be on with `itemRecordMode` off.
421
- 2. **Item records** — `itemRecordMode`. Materialise a persistent row
435
+ 1. **Item records** — `itemRecordMode`. Materialise a persistent row
422
436
  per physical item. `null` / missing = **disabled** (no separate
423
437
  `"none"` enum — reads collapse to `mode === "off"` via the
424
438
  `useItemRecordMode()` hook).
@@ -426,7 +440,15 @@ Three axes (do NOT conflate):
426
440
  provenance without a customer relationship.
427
441
  - `"owned"` — tag row + owner + interaction history. Requires
428
442
  `basePlanId >= "engage"`. Enables claim / transfer / CRM.
429
- 3. **CRM** — downstream consequence of `owned`; not a peer flag.
443
+ 2. **CRM** — downstream consequence of `owned`; not a peer flag.
444
+
445
+ **Virtual items are not part of this key.** Algorithmic IDs with no
446
+ per-item row (battery serials, scan-to-collect points, bulk QR sheets)
447
+ used to be a separate root-level `virtualItemsEnabled: boolean` — that
448
+ field has been removed. Virtual items are now gated by the `virtualItems`
449
+ feature flag under `system.features` (§4.4.1), same as every other
450
+ feature — resolve it with `isFeatureEnabled(collectionId, 'virtualItems')`.
451
+ It's independent of `itemRecordMode` and can be on with item records off.
430
452
 
431
453
  **Billing:** the usage ledger stamps `tier` at event time from
432
454
  `itemRecordMode`. Writes with `mode === null` MUST be rejected. Mode
@@ -447,46 +469,3 @@ CRM visibility), not just a capability flag.
447
469
  features yet.
448
470
  - `systemOverrides` (old root-level key) → `systemPrivate.overrides`.
449
471
  One-off backfill moves the block and re-runs reconcile.
450
-
451
- ## 10. Change log
452
-
453
- - **2026-07-17**: Initial contract published; `appConfig.system` becomes
454
- canonical entitlements source of truth. `entitlements` group deprecated.
455
- - **2026-07-17**: Added `systemOverrides` for additive comps; reconcile
456
- records `system.appliedOverrides`.
457
- - **2026-07-18**: Renamed `systemOverrides` → `systemPrivate.overrides`
458
- and formalised the three-tier access model (user / system /
459
- systemPrivate). API strips `systemPrivate` for non-admin readers.
460
- Added `system.accountType` derived from
461
- `systemPrivate.overrides.accountType`. Added top-of-doc TypeScript
462
- definition.
463
- - **2026-07-18**: Added SDK helpers `appConfiguration.getAppConfig()`
464
- (typed + cached), `isFeatureEnabled()` (async, cache-backed feature
465
- gate), and `isFeatureEnabledSync()` (cache-only, for callers that can't
466
- await). Renamed the exported TypeScript type to `AppConfigSettings`
467
- to avoid a name collision with the existing per-module `AppConfig`
468
- type in `src/types/collection.ts`.
469
- - **2026-07-18**: `/public/collection/:id/app/config` (`collection.getAppsConfig()`)
470
- now returns the `appConfig` entitlements data (`system`, `addOns`,
471
- `requestedBasePlanId`, `itemRecordMode`, `virtualItemsEnabled`) merged
472
- alongside its existing app-catalog `apps[]`. `appConfiguration.getAppConfig()`
473
- was repointed at this endpoint (dropping its `admin` option — this endpoint
474
- is public-only); the settings-group endpoint (`appConfiguration.getConfig({ appId: 'appConfig' })`)
475
- is still the only way to reach `systemPrivate`. See §3.1 for why the two
476
- endpoints' `apps[]` fields are shaped differently and shouldn't be conflated.
477
- - **2026-07-18**: `system.features` changed from "only truthy flags appear"
478
- to explicit `Record<string, boolean>` overrides. Added the `accountType`
479
- default rule (§4.4): enterprise accounts default every flag to on unless
480
- explicitly `false`; standard accounts default off unless explicitly `true`.
481
- Added `appConfiguration.resolveFeature(system, flag)` — the pure resolver
482
- behind `isFeatureEnabled()`/`isFeatureEnabledSync()`, exposed only for the
483
- rare case of already holding a `system` block from somewhere other than
484
- `getAppConfig()`. There is no separate admin feature-check path — `system`
485
- is public data on every read, so admin surfaces should call
486
- `isFeatureEnabled()` like everything else. Reading `cfg.system.features[flag]`
487
- directly is no longer correct for enterprise accounts — always resolve
488
- through one of these three.
489
- - **2026-07-19**: Corrected flag naming convention (§4.4): flags are
490
- camelCase (e.g. `customDomain`), not snake_case, and may use a dotted
491
- grouping prefix for a topic/module (e.g. `crm.broadcasts`). Updated all
492
- examples in this doc accordingly.
@@ -231,6 +231,8 @@ All standard widget props apply:
231
231
  | `productId` | string | ❌ | Product context |
232
232
  | `proofId` | string | ❌ | Proof context |
233
233
  | `user` | object | ❌ | Current user info |
234
+ | `itemContext` | `ItemContext` | ❌ | Authenticity context for the specific item (proof) the URL points at; `undefined` for collection/product-only URLs — see [item-context.md](item-context.md) |
235
+ | `tag` | `TagContext` | ❌ | **Deprecated** — use `itemContext.tag` instead. Kept for one release. |
234
236
  | `onNavigate` | function | ❌ | Navigation callback (accepts `NavigationRequest` or legacy string) |
235
237
  | `size` | string | ❌ | `"compact"`, `"standard"`, or `"large"` |
236
238
  | `lang` | string | ❌ | Language code (e.g., `"en"`) |
@@ -0,0 +1,184 @@
1
+ # Item Context (container prop)
2
+
3
+ > **Copy this file into `node_modules/@proveanything/smartlinks/docs/item-context.md`** in the published SDK package.
4
+
5
+ When the URL points at a specific item — either a **serial proof URL** or an
6
+ **NFC tap** — the portal derives an `ItemContext` describing what it found
7
+ and hands it to the container as the **`itemContext`** prop.
8
+
9
+ For collection- and product-only URLs there is no item to describe, so
10
+ `itemContext` is `undefined`. Sub-apps that don't care about authenticity
11
+ can simply ignore the prop.
12
+
13
+ The prop is named `itemContext` (not `item`) to make clear it's *context
14
+ about the item*, not the item record itself. The underlying record is
15
+ passed separately via `proof`, `product` and `collection` props.
16
+
17
+ ```tsx
18
+ export default function MyContainer({ itemContext, collection, product, proof }) {
19
+ if (!itemContext) {
20
+ // Collection or product view — nothing to verify.
21
+ return <BrowseView collection={collection} product={product} />;
22
+ }
23
+ if (itemContext.isAuthentic) {
24
+ return <RealContent proof={proof} />;
25
+ }
26
+ if (itemContext.status === 'not-found' || itemContext.status === 'invalid') {
27
+ return <NotGenuine message={itemContext.errorMessage} />;
28
+ }
29
+ // status === 'error' — transport failure
30
+ return <TransientError message={itemContext.errorMessage} />;
31
+ }
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Shape
37
+
38
+ ```ts
39
+ interface ItemContext {
40
+ /** True only for `valid` and `rescan`. Read this for the simple case. */
41
+ isAuthentic: boolean;
42
+
43
+ status:
44
+ | 'valid' // genuine
45
+ | 'rescan' // genuine, but this exact NFC tap has been seen before
46
+ | 'invalid' // SDK rejected the NFC payload (bad crypto)
47
+ | 'not-found' // identifier well-formed but 404
48
+ | 'error'; // transport / network failure — cannot tell
49
+
50
+ source: 'nfc' | 'serial';
51
+
52
+ /** Set for `invalid` / `not-found` / `error`. Safe to display. */
53
+ errorMessage?: string;
54
+
55
+ /** True when SDK reports this exact NFC tap was seen before. */
56
+ isRescan?: boolean;
57
+
58
+ /** Full NFC payload (SUN counter, tagId, claimSetId, codeId, mirror…). Present only when source === 'nfc'. */
59
+ tag?: TagContext;
60
+
61
+ /** Epoch ms when the check was derived. */
62
+ checkedAt: number;
63
+ }
64
+ ```
65
+
66
+ Also exported from `@proveanything/smartlinks` as `ItemContext` /
67
+ `ItemContextStatus` (`src/types/itemContext.ts`). The nested `TagContext`
68
+ shape is unchanged (`src/types/nfc.ts`).
69
+
70
+ ## When `itemContext` is present
71
+
72
+ | URL points at | itemContext |
73
+ | ---------------------------------------------- | -------------------- |
74
+ | Collection root | `undefined` |
75
+ | Product page (no proof) | `undefined` |
76
+ | Serial proof URL (any outcome) | present, source `serial` |
77
+ | NFC tap (any outcome) | present, source `nfc` |
78
+
79
+ ## Status matrix (when present)
80
+
81
+ | Situation | source | status | isAuthentic |
82
+ | ----------------------------------------------------- | -------- | ------------- | :---------: |
83
+ | Fresh NFC tap, SUN counter validated | `nfc` | `valid` | ✅ |
84
+ | Same NFC tap replayed / refresh | `nfc` | `rescan` | ✅ |
85
+ | NFC payload rejected (bad crypto / bad params) | `nfc` | `invalid` | ❌ |
86
+ | Serial proof URL, proof fetched OK | `serial` | `valid` | ✅ |
87
+ | Serial proof URL, proof returned 404 | `serial` | `not-found` | ❌ |
88
+ | Network / 5xx while resolving proof or NFC | (either) | `error` | ❌ |
89
+
90
+ `isAuthentic === (status === 'valid' || status === 'rescan')`.
91
+
92
+ ## How it's delivered
93
+
94
+ **Direct-mounted containers** (default public mode):
95
+
96
+ ```tsx
97
+ type Props = {
98
+ collection: Collection;
99
+ product?: Product;
100
+ proof?: Proof;
101
+ itemContext?: ItemContext; // ← only when URL targets a specific item
102
+ // …plus the normal navigation / theme / auth props
103
+ };
104
+ ```
105
+
106
+ NFC-aware apps that need the raw SUN / tag payload read `itemContext.tag`.
107
+
108
+ **Iframe containers**: the portal serialises `ItemContext` to JSON, base64-
109
+ encodes it (UTF-8 safe) and appends it as an `itemContext` query param on
110
+ the iframe URL — omitted entirely when there is no item context:
111
+
112
+ ```ts
113
+ function readItemContext(): ItemContext | undefined {
114
+ const raw = new URLSearchParams(location.search).get('itemContext');
115
+ if (!raw) return undefined;
116
+ try { return JSON.parse(decodeURIComponent(escape(atob(raw)))); }
117
+ catch { return undefined; }
118
+ }
119
+ ```
120
+
121
+ ## Guarantees
122
+
123
+ - `itemContext` is present on every container mount whose URL targeted an
124
+ item (proof or NFC tap). Absent otherwise.
125
+ - `SL.nfc.validate` is called at most once per `(claimSetId, codeId)` per
126
+ browser session — SUN counters are never double-consumed on refresh.
127
+ - SSR uses `SL.nfc.lookupTag` (non-consuming) only.
128
+ - `isAuthentic === true` implies the SDK either validated an NFC payload
129
+ or returned a full proof document. It is never inferred from URL shape.
130
+ - The portal will **not** render its full-page error surface for a 404 on
131
+ a proof — that surfaces as `status: 'not-found'` on `itemContext`, and
132
+ the container is still mounted so it can present the message its own way.
133
+
134
+ ## What containers should do
135
+
136
+ | Status | Recommended UI |
137
+ | ------------- | ------------------------------------------------------------------ |
138
+ | `valid` | Full experience. Show ownership, actions, etc. |
139
+ | `rescan` | Same as `valid`, optionally with a subtle "seen before" hint. |
140
+ | `invalid` | Clear "this doesn't look genuine" message. Do NOT reveal data. |
141
+ | `not-found` | "We don't recognise this item." Offer a link home. |
142
+ | `error` | Neutral "couldn't verify right now." Offer retry. |
143
+ | _absent_ | Standard browse UI. Don't mention authenticity at all. |
144
+
145
+ ## Gating UX with conditions
146
+
147
+ Sub-apps should standardize on passing `itemContext` into `validateCondition()`
148
+ and gating with `itemStatus` — the same condition type already used for
149
+ claim/virtual checks, extended with authenticity `statusType`s that read
150
+ `itemContext` instead of `proof`:
151
+
152
+ ```ts
153
+ import { validateCondition } from '@proveanything/smartlinks';
154
+
155
+ const showFakeWarning = await validateCondition({
156
+ condition: {
157
+ conditions: [{ type: 'itemStatus', statusType: 'invalidProof' }],
158
+ },
159
+ itemContext, // the prop delivered to the container
160
+ });
161
+ ```
162
+
163
+ - `isAuthentic` — passes when `itemContext.isAuthentic` is true (`valid`/`rescan`)
164
+ - `notAuthentic` — passes when an `itemContext` was resolved but isn't authentic (`invalid`/`not-found`/`error`)
165
+ - `invalidProof` — passes specifically when resolution was attempted and came back `invalid` or `not-found` — the "someone scanned a fake tag / typed a bad serial" case, as distinct from `noProof` ("nothing was on the URL to check at all")
166
+ - `isFirstScan` — the single "this is good, show the full experience" check: authentic AND this is the first time it's been seen (`status === 'valid'`)
167
+ - `isRescan` — authentic but a duplicate/replayed tap (`status === 'rescan'`) — use this to suppress "first scan" celebration UX without treating the tag as fake; `isAuthentic` alone is true for both `isFirstScan` and `isRescan` cases
168
+
169
+ ## Deprecated
170
+
171
+ The old top-level `tag` prop (`TagContext`) is still forwarded for one
172
+ release for backward compatibility with scanner-aware apps that read raw
173
+ SUN data. New code should read `itemContext.tag` and treat `itemContext`
174
+ as the source of truth.
175
+
176
+ ## Note on terminology
177
+
178
+ Today the SDK still uses `proof` for a specific item record (a serialised
179
+ instance of a product). "Item record" is under consideration as a clearer
180
+ name — no API changes are planned imminently, but a future SDK version may
181
+ support both `proof`- and `item record`-shaped names for the same
182
+ functions/types side by side. `itemContext` is deliberately future-proof:
183
+ it describes context about whatever the URL points at, regardless of what
184
+ we ultimately call the record itself.
@@ -485,9 +485,12 @@ await utils.validateCondition({
485
485
  ```
486
486
 
487
487
  #### Item Status Conditions
488
- Check proof/item status:
488
+ Check proof/item status — claim/ownership state, and authenticity of the
489
+ item (proof) the URL points at (via NFC tap or serial proof URL — see
490
+ [item-context.md](item-context.md)):
489
491
 
490
492
  ```typescript
493
+ // Claim/ownership — unchanged, reads `proof`
491
494
  await utils.validateCondition({
492
495
  condition: {
493
496
  type: 'and',
@@ -498,9 +501,38 @@ await utils.validateCondition({
498
501
  },
499
502
  proof: { claimable: true }
500
503
  })
504
+
505
+ // Authenticity — reads `itemContext`
506
+ await utils.validateCondition({
507
+ condition: {
508
+ type: 'and',
509
+ conditions: [{
510
+ type: 'itemStatus',
511
+ statusType: 'invalidProof'
512
+ }]
513
+ },
514
+ itemContext: { isAuthentic: false, status: 'invalid', source: 'nfc', checkedAt: Date.now() }
515
+ })
516
+
517
+ // Fresh scan vs. duplicate/replayed scan — both authentic, different UX
518
+ await utils.validateCondition({
519
+ condition: {
520
+ type: 'and',
521
+ conditions: [{
522
+ type: 'itemStatus',
523
+ statusType: 'isRescan'
524
+ }]
525
+ },
526
+ itemContext: { isAuthentic: true, status: 'rescan', source: 'nfc', checkedAt: Date.now() }
527
+ })
501
528
  ```
502
529
 
503
- Status types: `'isClaimable'`, `'notClaimable'`, `'noProof'`, `'hasProof'`, `'isVirtual'`, `'notVirtual'`
530
+ Status types:
531
+ - Claim/ownership (reads `proof`): `'isClaimable'`, `'notClaimable'`, `'isVirtual'`, `'notVirtual'`
532
+ - Presence (reads `proof`): `'hasProof'`, `'noProof'` — `noProof` only means *nothing was attempted*, not that an attempt failed
533
+ - Authenticity (reads `itemContext`): `'isAuthentic'`, `'notAuthentic'`, `'invalidProof'`, `'isFirstScan'`, `'isRescan'`
534
+ - `invalidProof` is specifically "an identifier was passed and resolution came back `invalid`/`not-found`", distinct from `noProof`'s "nothing on the URL to check at all"
535
+ - `isAuthentic` is true for both a fresh tap and a rescan. Use `isFirstScan` (authentic AND `status === 'valid'`) as the single "this is good, show the full experience" check, and `isRescan` (authentic but `status === 'rescan'`) to suppress "first scan" celebration UX without treating the tag as fake
504
536
 
505
537
  #### Version Conditions
506
538
  For A/B testing or versioned content:
@@ -720,6 +752,7 @@ See [examples/utils-demo.ts](../examples/utils-demo.ts) for comprehensive exampl
720
752
  - **[API Summary](API_SUMMARY.md)** - Complete SDK reference with all namespaces and functions
721
753
  - **[QR Codes](API_SUMMARY.md#qr)** - QR code lookup functions that work with generated paths
722
754
  - **[NFC](API_SUMMARY.md#nfc)** - NFC tag claiming and validation
755
+ - **[Item Context](item-context.md)** - `itemContext` prop delivered to containers describing the item (proof) the URL points at and whether it's authentic
723
756
  - **[Collections](API_SUMMARY.md#collection)** - Collection management functions
724
757
  - **[Products](API_SUMMARY.md#product)** - Product CRUD operations
725
758
  - **[Batches](API_SUMMARY.md#batch)** - Batch management
@@ -159,6 +159,13 @@ interface SmartLinksWidgetProps {
159
159
  // Base URL to the full public portal for deep linking
160
160
  publicPortalUrl?: string;
161
161
 
162
+ // Authenticity context for the specific item (proof) the URL points at.
163
+ // undefined for collection- and product-only URLs.
164
+ itemContext?: ItemContext;
165
+
166
+ // @deprecated Use itemContext.tag instead — kept for one release
167
+ tag?: TagContext;
168
+
162
169
  // Size hint for responsive rendering
163
170
  size?: 'compact' | 'standard' | 'large';
164
171
 
@@ -180,6 +187,8 @@ interface SmartLinksWidgetProps {
180
187
  | `SL` | `typeof SL` | Pre-initialized SmartLinks SDK |
181
188
  | `onNavigate` | `function?` | Callback to navigate within parent app (accepts `NavigationRequest` or legacy string) |
182
189
  | `publicPortalUrl` | `string?` | Base URL to full portal for deep links |
190
+ | `itemContext` | `ItemContext?` | Authenticity context for the specific item (proof) the URL points at; `undefined` for collection/product-only URLs — see [item-context.md](item-context.md) |
191
+ | `tag` | `TagContext?` | **Deprecated** — use `itemContext.tag` instead. Kept for one release. |
183
192
  | `size` | `string?` | Size hint: 'compact', 'standard', or 'large' |
184
193
  | `lang` | `string?` | Language code (e.g., 'en', 'de', 'fr') |
185
194
  | `translations` | `object?` | Translation overrides |
package/dist/openapi.yaml CHANGED
@@ -15719,8 +15719,6 @@ components:
15719
15719
  enum:
15720
15720
  - registered
15721
15721
  - owned
15722
- virtualItemsEnabled:
15723
- type: boolean
15724
15722
  system:
15725
15723
  $ref: "#/components/schemas/SystemBlock"
15726
15724
  required:
@@ -19309,10 +19307,6 @@ components:
19309
19307
  type: string
19310
19308
  allowAutoGenerateClaims:
19311
19309
  type: boolean
19312
- variants:
19313
- type: boolean
19314
- batches:
19315
- type: boolean
19316
19310
  defaultAuthKitId:
19317
19311
  type: string
19318
19312
  required:
@@ -19328,8 +19322,6 @@ components:
19328
19322
  - supported
19329
19323
  - roles
19330
19324
  - shortId
19331
- - variants
19332
- - batches
19333
19325
  - defaultAuthKitId
19334
19326
  HubAvailabilityResponse:
19335
19327
  type: object
@@ -22211,6 +22203,31 @@ components:
22211
22203
  - metadata
22212
22204
  - broadcastId
22213
22205
  - journeyId
22206
+ ItemContext:
22207
+ type: object
22208
+ properties:
22209
+ isAuthentic:
22210
+ type: boolean
22211
+ status:
22212
+ $ref: "#/components/schemas/ItemContextStatus"
22213
+ source:
22214
+ type: string
22215
+ enum:
22216
+ - nfc
22217
+ - serial
22218
+ errorMessage:
22219
+ type: string
22220
+ isRescan:
22221
+ type: boolean
22222
+ tag:
22223
+ $ref: "#/components/schemas/TagContext"
22224
+ checkedAt:
22225
+ type: number
22226
+ required:
22227
+ - isAuthentic
22228
+ - status
22229
+ - source
22230
+ - checkedAt
22214
22231
  Job:
22215
22232
  type: object
22216
22233
  properties:
@@ -23042,6 +23059,39 @@ components:
23042
23059
  - claimSetId
23043
23060
  - codeId
23044
23061
  - data
23062
+ TagContext:
23063
+ type: object
23064
+ properties:
23065
+ status:
23066
+ $ref: "#/components/schemas/TagStatus"
23067
+ tagId:
23068
+ type: string
23069
+ claimSetId:
23070
+ type: string
23071
+ codeId:
23072
+ type: string
23073
+ count:
23074
+ type: number
23075
+ previousCount:
23076
+ type: number
23077
+ data:
23078
+ type: object
23079
+ additionalProperties: true
23080
+ source:
23081
+ type: string
23082
+ enum:
23083
+ - nfc-validate
23084
+ - nfc-lookup
23085
+ - tag-index
23086
+ - none
23087
+ validatedAt:
23088
+ type: number
23089
+ errorMessage:
23090
+ type: string
23091
+ required:
23092
+ - status
23093
+ - source
23094
+ - validatedAt
23045
23095
  OrderItem:
23046
23096
  type: object
23047
23097
  properties:
@@ -25219,6 +25269,10 @@ components:
25219
25269
  additionalProperties: true
25220
25270
  publicPortalUrl:
25221
25271
  type: string
25272
+ itemContext:
25273
+ $ref: "#/components/schemas/ItemContext"
25274
+ tag:
25275
+ $ref: "#/components/schemas/TagContext"
25222
25276
  size:
25223
25277
  type: string
25224
25278
  enum:
@@ -112,7 +112,6 @@ export interface AppConfigSettings {
112
112
  requestedBasePlanId?: string;
113
113
  /** How the account handles individual physical items. */
114
114
  itemRecordMode?: 'registered' | 'owned' | null;
115
- virtualItemsEnabled?: boolean;
116
115
  /** Resolved entitlement truth. Read this to gate features. */
117
116
  system?: SystemBlock;
118
117
  }
@@ -57,8 +57,6 @@ export interface Collection {
57
57
  portalUrl?: string;
58
58
  /** Allow users to claim products without providing a proof ID (auto-generates serial on-demand) */
59
59
  allowAutoGenerateClaims?: boolean;
60
- variants: boolean;
61
- batches: boolean;
62
60
  defaultAuthKitId: string;
63
61
  }
64
62
  export type CollectionResponse = Collection;
@@ -11,6 +11,7 @@ export * from "./claimSet";
11
11
  export * from "./auth";
12
12
  export * from "./comms";
13
13
  export * from "./nfc";
14
+ export * from "./itemContext";
14
15
  export * from "./contact";
15
16
  export * from "./broadcasts";
16
17
  export * from "./segments";
@@ -13,6 +13,7 @@ export * from "./claimSet";
13
13
  export * from "./auth";
14
14
  export * from "./comms";
15
15
  export * from "./nfc";
16
+ export * from "./itemContext";
16
17
  export * from "./contact";
17
18
  export * from "./broadcasts";
18
19
  export * from "./segments";
@@ -0,0 +1,30 @@
1
+ import type { TagContext } from './nfc';
2
+ /**
3
+ * Authenticity status for a specific item — a proof, resolved via an NFC
4
+ * tap or a serial proof URL. Collection- and product-only URLs have no
5
+ * item to describe, so there is no "nothing checkable" status here: absence
6
+ * of an `ItemContext` altogether is how that case is represented (see the
7
+ * `itemContext` prop on `SmartLinksWidgetProps`).
8
+ */
9
+ export type ItemContextStatus = 'valid' | 'rescan' | 'invalid' | 'not-found' | 'error';
10
+ /**
11
+ * Describes a specific item the current URL points at and whether it's
12
+ * authentic — delivered to containers as the `itemContext` prop.
13
+ * Supersedes the NFC-only `TagContext`: still available, nested at
14
+ * `itemContext.tag`, and for one release also as a deprecated top-level
15
+ * `tag` prop. See docs/item-context.md.
16
+ */
17
+ export interface ItemContext {
18
+ /** True only for `valid` and `rescan`. Read this for the simple case. */
19
+ isAuthentic: boolean;
20
+ status: ItemContextStatus;
21
+ source: 'nfc' | 'serial';
22
+ /** Set for `invalid` / `not-found` / `error`. Safe to display. */
23
+ errorMessage?: string;
24
+ /** True when the SDK reports this exact NFC tap was seen before. */
25
+ isRescan?: boolean;
26
+ /** Full NFC payload (SUN counter, tagId, claimSetId, codeId, mirror…). Present only when `source === 'nfc'`. */
27
+ tag?: TagContext;
28
+ /** Epoch ms when the check was derived. */
29
+ checkedAt: number;
30
+ }