@proveanything/smartlinks 2.1.11 → 2.1.13

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.
@@ -98,6 +98,40 @@ export type RefreshErrorCode = 'MISSING_REFRESH_TOKEN' | 'INVALID_REFRESH_TOKEN'
98
98
  * Options for {@link authKit.appleLogin}. All fields are optional — only the
99
99
  * `identityToken` (passed as a positional argument) is required by the server.
100
100
  */
101
+ /** Options for {@link authKit.microsoftLogin}. */
102
+ export interface MicrosoftLoginOptions {
103
+ /**
104
+ * The site the session will be handed to, when signing in through a popup page on another address
105
+ * (the SmartLinks Microsoft sign-in page does this). The server refuses unless it's one of the login
106
+ * setup's own sites (`MICROSOFT_ORIGIN_NOT_ALLOWED`). Leave it out when signing in on the site itself.
107
+ */
108
+ returnOrigin?: string;
109
+ /** Skip the step-up MFA challenge on a recognised device. */
110
+ trustedDeviceToken?: string;
111
+ }
112
+ /**
113
+ * Who can sign in through a login setup, whatever the method (password, magic link, phone, WhatsApp,
114
+ * Google, Apple, LinkedIn, Microsoft):
115
+ * - `anyone` (also: no `access`) — anyone, as before;
116
+ * - `organisation` — people from the listed organisations: a VERIFIED email at one of `domains`, one of
117
+ * the listed `emails`, or signing in with Microsoft from the domain's own Microsoft organisation. A
118
+ * Microsoft email alone doesn't count (an organisation can give its users any address), so on save
119
+ * the server looks up each domain's Microsoft organisation itself — nobody types a tenant id.
120
+ *
121
+ * Checked every time a session is issued (tightening it shuts people out from their next sign-in);
122
+ * refused with `NOT_IN_ORGANISATION` (403). Registration refuses outside addresses up front and never
123
+ * signs an unverified address straight in. Accounts with no email (phone / WhatsApp only) can't show
124
+ * they're from an organisation. The public config returns only `{ mode, domains }`.
125
+ */
126
+ export interface AuthKitAccess {
127
+ mode: 'anyone' | 'organisation';
128
+ /** e.g. `['acme.com']` (no `@`; exact domains — list subsidiaries' domains too). Up to 50. */
129
+ domains?: string[];
130
+ /** Particular people allowed besides the organisations, e.g. a contractor. Up to 50. Never public. */
131
+ emails?: string[];
132
+ /** Server-filled on save: `{ 'acme.com': '<Microsoft tenant id>' }` for domains with a Microsoft organisation. Read-only. */
133
+ microsoftTenants?: Record<string, string>;
134
+ }
101
135
  export interface AppleLoginOptions {
102
136
  /** Apple authorization code. Accepted but ignored by the server for now (reserved for future server-side token exchange). */
103
137
  authorizationCode?: string;
@@ -128,7 +162,7 @@ export interface AppleLoginOptions {
128
162
  * `ACCOUNT_EXISTS_UNVERIFIED` case also carries `requiresEmailVerification: true`
129
163
  * in `SmartlinksApiError.details`.
130
164
  */
131
- export type AuthKitErrorCode = 'MISSING_APPLE_TOKEN' | 'APPLE_AUTH_NOT_CONFIGURED' | 'INVALID_APPLE_TOKEN' | 'ACCOUNT_EXISTS_UNVERIFIED' | 'APPLE_AUTH_FAILED' | 'SIGN_UP_CLOSED';
165
+ export type AuthKitErrorCode = 'MISSING_APPLE_TOKEN' | 'APPLE_AUTH_NOT_CONFIGURED' | 'INVALID_APPLE_TOKEN' | 'ACCOUNT_EXISTS_UNVERIFIED' | 'APPLE_AUTH_FAILED' | 'SIGN_UP_CLOSED' | 'NOT_IN_ORGANISATION' | 'MISSING_MICROSOFT_TOKEN' | 'MICROSOFT_AUTH_NOT_CONFIGURED' | 'INVALID_MICROSOFT_TOKEN' | 'MICROSOFT_ORIGIN_NOT_ALLOWED' | 'MICROSOFT_AUTH_FAILED';
132
166
  /** A second-step method: a code by email or text message, or from an authenticator app. */
133
167
  export type MfaFactor = 'email' | 'sms' | 'totp';
134
168
  /**
@@ -445,6 +479,16 @@ export interface AuthKitConfig {
445
479
  * Setting `invite` needs own logins first (`OWN_LOGINS_REQUIRED`, 409).
446
480
  */
447
481
  signUp?: 'open' | 'invite';
482
+ /** Who can sign in — see {@link AuthKitAccess}. Absent = anyone. */
483
+ access?: AuthKitAccess;
484
+ /**
485
+ * Sign in with Microsoft: add `'microsoft'` to `enabledProviders`. SmartLinks' own Microsoft app serves
486
+ * every organisation and personal Microsoft accounts, so nothing else is needed. Set this only to show
487
+ * your OWN name on Microsoft's sign-in screen: the Application (client) ID of your own multitenant app
488
+ * registration (redirect URI: https://smartlinks.app/apps/account/stable/microsoft-proxy.html). On the
489
+ * public config it's the effective id (yours, else SmartLinks').
490
+ */
491
+ microsoftClientId?: string;
448
492
  }
449
493
  /** Body of {@link authKit.invite}. */
450
494
  export interface AuthKitInviteRequest {
@@ -301,6 +301,16 @@ export interface PartnerClaimSet {
301
301
  deleted: boolean;
302
302
  createdAt: string | null;
303
303
  }
304
+ /** One hit from partner.lookupTags */
305
+ export interface PartnerTagHit {
306
+ namespace: string;
307
+ claimSetId: string;
308
+ set: PartnerClaimSet | null;
309
+ tag: ClaimSetRangeTag & {
310
+ url: string | null;
311
+ };
312
+ range: ClaimSetRange | null;
313
+ }
304
314
  export interface CreatePartnerClaimSetRequest {
305
315
  name: string;
306
316
  tagType: ClaimSetTagType;
@@ -14,6 +14,10 @@ export interface NfcValidateRequest {
14
14
  claimSetId: string;
15
15
  codeId: string;
16
16
  mirror?: string;
17
+ /** NTAG 424 DNA: the encrypted PICC block from the tag's URL (`?p=`, 32 hex) */
18
+ d?: string;
19
+ /** NTAG 424 DNA: the tag's SDM MAC from its URL (`&c=`, 16 hex). Verified by the server when present; always forward it. */
20
+ c?: string;
17
21
  userId?: string;
18
22
  }
19
23
  export interface NfcValidateResponse {
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.1.11 | Generated: 2026-11-08T09:25:58.482Z
3
+ Version: 2.1.13 | Generated: 2026-11-09T11:51:55.948Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -4624,6 +4624,24 @@ interface LogoutResponse {
4624
4624
  }
4625
4625
  ```
4626
4626
 
4627
+ **MicrosoftLoginOptions** (interface)
4628
+ ```typescript
4629
+ interface MicrosoftLoginOptions {
4630
+ returnOrigin?: string
4631
+ trustedDeviceToken?: string
4632
+ }
4633
+ ```
4634
+
4635
+ **AuthKitAccess** (interface)
4636
+ ```typescript
4637
+ interface AuthKitAccess {
4638
+ mode: 'anyone' | 'organisation'
4639
+ domains?: string[]
4640
+ emails?: string[]
4641
+ microsoftTenants?: Record<string, string>
4642
+ }
4643
+ ```
4644
+
4627
4645
  **AppleLoginOptions** (interface)
4628
4646
  ```typescript
4629
4647
  interface AppleLoginOptions {
@@ -4953,6 +4971,8 @@ interface AuthKitConfig {
4953
4971
  collectionIds?: string[]
4954
4972
  identity?: AuthKitIdentity
4955
4973
  signUp?: 'open' | 'invite'
4974
+ access?: AuthKitAccess
4975
+ microsoftClientId?: string
4956
4976
  }
4957
4977
  ```
4958
4978
 
@@ -5894,6 +5914,17 @@ interface PartnerClaimSet {
5894
5914
  }
5895
5915
  ```
5896
5916
 
5917
+ **PartnerTagHit** (interface)
5918
+ ```typescript
5919
+ interface PartnerTagHit {
5920
+ namespace: string
5921
+ claimSetId: string
5922
+ set: PartnerClaimSet | null
5923
+ tag: ClaimSetRangeTag & { url: string | null }
5924
+ range: ClaimSetRange | null
5925
+ }
5926
+ ```
5927
+
5897
5928
  **CreatePartnerClaimSetRequest** (interface)
5898
5929
  ```typescript
5899
5930
  interface CreatePartnerClaimSetRequest {
@@ -9330,6 +9361,8 @@ interface NfcValidateRequest {
9330
9361
  claimSetId: string
9331
9362
  codeId: string
9332
9363
  mirror?: string
9364
+ d?: string
9365
+ c?: string
9333
9366
  userId?: string
9334
9367
  }
9335
9368
  ```
@@ -11924,6 +11957,9 @@ Sign in with LinkedIn (OpenID Connect) via a server-side authorization code (pub
11924
11957
  **appleLogin**(clientId: string, identityToken: string, opts?: AppleLoginOptions) → `Promise<AuthLoginResponse>`
11925
11958
  Sign in with Apple via an Apple identity token (public). Mirrors {@link googleLogin}. On success the returned bearer token is stored automatically and the cache is invalidated. Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`): - `MISSING_APPLE_TOKEN` (400), `APPLE_AUTH_NOT_CONFIGURED` (400), `INVALID_APPLE_TOKEN` (401), `APPLE_AUTH_FAILED` (500) - `ACCOUNT_EXISTS_UNVERIFIED` (409) — an unverified account already owns this email; the server refuses to silently link. `err.details.requiresEmailVerification` is `true`. Recoverable: the user should sign in with their password (or reset it), then link Apple from settings. **The same 409 can now come back from {@link googleLogin}** under the shared verified-to-verified linking policy. Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. Pass `opts.trustedDeviceToken` to skip the challenge on a recognized device.
11926
11959
 
11960
+ **microsoftLogin**(clientId: string, idToken: string, opts?: MicrosoftLoginOptions) → `Promise<AuthLoginResponse>`
11961
+ Sign in with Microsoft — work or school accounts from any organisation, and personal Microsoft accounts — via a Microsoft ID token (public). Mirrors {@link appleLogin}: on success the bearer token is stored and the cache invalidated. Get the ID token from Microsoft's sign-in (response_type `id_token`, scope `openid profile email`) against the app in the public config's `microsoftClientId`. A site on its own domain can't be a registered address of that app, so the SmartLinks sign-in UI opens SmartLinks' Microsoft sign-in page in a popup, which calls this with `opts.returnOrigin` and hands the result back. Notable error codes: `MISSING_MICROSOFT_TOKEN` (400), `MICROSOFT_AUTH_NOT_CONFIGURED` (400), `INVALID_MICROSOFT_TOKEN` (401), `MICROSOFT_ORIGIN_NOT_ALLOWED` (403), `NOT_IN_ORGANISATION` (403 — the login setup's {@link AuthKitAccess} rule), `ACCOUNT_EXISTS_UNVERIFIED` (409), `MICROSOFT_AUTH_FAILED` (500). Gated by step-up MFA like every sign-in.
11962
+
11927
11963
  **refreshToken**(clientId: string, refreshToken: string) → `Promise<RefreshResponse>`
11928
11964
  Exchange a refresh token for a fresh access token (public — the refresh token IS the credential). **Native sessions only**; refresh tokens are issued only when the host opted in via `initializeApi({ platform: 'native' })`. On success the new access token is stored automatically (`setBearerToken`). The returned `refreshToken` is **rotated** — the caller must persist it and discard the old one before refreshing again. ⚠️ **Single-use, no retry, serialize calls.** This method issues exactly one request and never retries: replaying a consumed refresh token triggers `REFRESH_TOKEN_REUSE_DETECTED` (the whole session family is revoked). The caller is responsible for ensuring only one refresh is in flight at a time (e.g. across tabs or resume events). Errors (thrown as `SmartlinksApiError`, read via `err.errorCode`): `MISSING_REFRESH_TOKEN` (400), `INVALID_REFRESH_TOKEN` (401), `REFRESH_TOKEN_REUSE_DETECTED` (401) — the last two mean a hard logout.
11929
11965
 
@@ -12327,21 +12363,9 @@ The range's tags in index order, a page at a time (keys: owners/admins only).
12327
12363
  The next tag to program on a phone (lowest index without a chip), with its URL and key.
12328
12364
 
12329
12365
  **recordTag**(scope: ClaimSetScope, claimSetId: string, rangeId: string, code: string,
12330
- params: { uid: string; data?: Record<string, unknown> }) → `Promise<`
12366
+ params: { uid: string; data?: Record<string, unknown>; /** A plain (NTAG 21x) → `void`
12331
12367
  Record the chip a phone just programmed with this code.
12332
12368
 
12333
- **exportFile**(scope: ClaimSetScope, claimSetId: string, rangeId: string) → `Promise<ClaimSetRangeFile>`
12334
- The printer file (CSV text in `csv`). Carries the tags' keys: don't keep it around.
12335
-
12336
- **instructions**(scope: ClaimSetScope, claimSetId: string, rangeId: string, format: 'pdf' | 'md' = 'pdf') → `Promise<ClaimSetRangeFile>`
12337
- The manufacturer's instruction sheet: PDF (base64) or Markdown.
12338
-
12339
- **importUids**(scope: ClaimSetScope, claimSetId: string, rangeId: string, params: ImportClaimSetRangeUidsRequest) → `Promise<ImportClaimSetRangeUidsResponse>`
12340
- The manufacturer's file back: check it (commit false), then write the chip UIDs (commit true).
12341
-
12342
- **assign**(scope: ClaimSetScope, claimSetId: string, rangeId: string, params: AssignClaimSetRangeRequest) → `Promise<AssignClaimSetRangeResponse>`
12343
- Point the range (or from–to within it) at a collection / product / batch, or clear it.
12344
-
12345
12369
  ### collection
12346
12370
 
12347
12371
  **get**(collectionId: string, admin?: boolean) → `Promise<CollectionResponse>`
@@ -13270,6 +13294,9 @@ One of the partner's claim sets.
13270
13294
  **createClaimSet**(partnerId: string, params: CreatePartnerClaimSetRequest) → `Promise<PartnerClaimSet>`
13271
13295
  A new claim set for the partner, on one of its tap domains (owners and admins).
13272
13296
 
13297
+ **lookupTags**(partnerId: string, query: { uid?: string; code?: string }) → `Promise<PartnerTagHit[]>`
13298
+ 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.
13299
+
13273
13300
  ### podcast
13274
13301
 
13275
13302
  **generate**(collectionId: string,
package/docs/auth-kit.md CHANGED
@@ -259,6 +259,56 @@ try {
259
259
  > ⚠️ This is a behaviour change for `googleLogin`, which previously merged silently in
260
260
  > this case. Handle the 409 for both methods.
261
261
 
262
+ ### Sign in with Microsoft
263
+
264
+ Work or school accounts from **any** organisation, and personal Microsoft accounts (Outlook, Hotmail,
265
+ Live). Add `'microsoft'` to `enabledProviders` — that's all: SmartLinks' own Microsoft app serves every
266
+ organisation, so neither you nor your users' IT registers anything.
267
+
268
+ ```ts
269
+ const session = await authKit.microsoftLogin(clientId, microsoftIdToken, { returnOrigin });
270
+ ```
271
+
272
+ - **In the SmartLinks sign-in UI** the Microsoft button opens SmartLinks' Microsoft sign-in page
273
+ (`https://smartlinks.app/apps/account/stable/microsoft-proxy.html`) in a popup — the one address
274
+ registered with Microsoft — which signs in and hands the session back to your site. Your site must be
275
+ one of the login setup's own (its collections' domains or site addresses, or `allowedRedirectDomains`),
276
+ else `MICROSOFT_ORIGIN_NOT_ALLOWED` (403).
277
+ - **Your own UI:** get an ID token from Microsoft (`response_type=id_token`, scope `openid profile email`,
278
+ authority `https://login.microsoftonline.com/common`) for the app in the public config's
279
+ `microsoftClientId`, and pass it here.
280
+ - **Some companies' IT approves new apps for their staff.** The first person from such a company sees
281
+ "needs admin approval"; their IT approves SmartLinks once for the organisation and everyone can sign in.
282
+ - **Your own name on Microsoft's screen (optional):** register your own *multitenant* app in Microsoft
283
+ Entra (Supported account types: any organisation + personal accounts; Web redirect URI as above, with
284
+ "ID tokens" ticked; token configuration: optional claim `xms_edov` on the ID token) and set its
285
+ Application (client) ID as `microsoftClientId`.
286
+ - A Microsoft account's email is trusted only when Microsoft vouches that its organisation owns the
287
+ domain (or for personal accounts): an organisation can give its users any address. Linking to an
288
+ existing account by email follows the same verified-to-verified rule as Google and Apple.
289
+
290
+ ### Who can sign in (`access`)
291
+
292
+ Restrict a login setup to one organisation — no keys, no IT:
293
+
294
+ ```ts
295
+ await authKit.update(collectionId, clientId, {
296
+ access: { mode: 'organisation', domains: ['acme.com'], emails: ['contractor@gmail.com'] },
297
+ });
298
+ ```
299
+
300
+ - Someone is from acme.com when their **verified** email is at acme.com, they're one of `emails`, or they
301
+ signed in with Microsoft from acme's own Microsoft organisation (looked up from the domain when you
302
+ save — `microsoftTenants` is filled in for you).
303
+ - Checked on **every** sign-in method each time a session is issued: everyone else gets
304
+ `NOT_IN_ORGANISATION` (403) with a message naming the domain. Tighten the rule and people already in
305
+ are refused from their next sign-in.
306
+ - `register` refuses outside addresses before making anything, and never signs an unverified address
307
+ straight in (even in `immediate` mode) — it must be verified first.
308
+ - Phone-only and WhatsApp-only accounts have no email to prove an organisation, so they can't get in.
309
+ - The public config (`load`) returns `access: { mode, domains }` so your page can say who it's for; the
310
+ `emails` list is never public. `{ mode: 'anyone' }` (or no `access`) lets everyone in, as before.
311
+
262
312
  ### Refresh tokens (native sessions)
263
313
 
264
314
  Native/Capacitor hosts can hold long-lived sessions via refresh tokens. Opt in **once**
@@ -1,75 +1,77 @@
1
- # NFC tag ranges and partners
2
-
3
- For partners and enterprise clients who run their own tag programmes: mint a batch of tags, send them
4
- to a manufacturer with instructions, get the chip UIDs back, then put the tags to work for a client or
5
- product. Ordinary business collections don't use this.
6
-
7
- ## The words
8
-
9
- | Word | Means |
10
- |---|---|
11
- | **Claim set** | A pool of codes on one tap domain and one tag type (NTAG 424 DNA, NTAG 213/215/216, or QR). Most partners need one. |
12
- | **Range** | A named, contiguous block of tags inside a set: "Tap to donate - February", 100 tags, indexes 201-300. |
13
- | **Index** | A tag's place in its set. Ranges never overlap and indexes are never reused. |
14
- | **Partner** | An organisation above collections that owns sets and assigns their tags into collections its people administer. Set up by SmartLinks staff. |
15
-
16
- ## Who can do what
17
-
18
- `scope` is `{ partnerId }` for a partner's sets, or `{ collectionId }` for a collection's own sets.
19
-
20
- | Role | Can |
21
- |---|---|
22
- | Partner owner / admin, or a collection admin | Make sets, mint ranges, download the printer file and instructions, import UIDs, program tags, assign |
23
- | Partner assigner | See ranges and tags (never keys), assign |
24
-
25
- Assigning always needs you to be an admin of the collection the tags go to.
26
-
27
- ## The flow
28
-
29
- ```ts
30
- import { partner, claimSet } from '@proveanything/smartlinks'
31
-
32
- const [p] = await partner.list() // the partners you belong to
33
- const scope = { partnerId: p.id }
34
- const [set] = await partner.claimSets(p.id) // or partner.createClaimSet(p.id, { name, tagType: 'ntag424', tapDomain })
35
-
36
- // 1. Mint
37
- const range = await claimSet.ranges.create(scope, set.id, { name: 'Tap to donate - February', quantity: 100 })
38
-
39
- // 2. Send to print: the CSV (carries each tag's key) and the instruction sheet
40
- const file = await claimSet.ranges.exportFile(scope, set.id, range.id) // { filename, csv }
41
- const sheet = await claimSet.ranges.instructions(scope, set.id, range.id, 'pdf') // { filename, base64 }
42
- await claimSet.ranges.update(scope, set.id, range.id, { stage: 'sent', manufacturer: 'Print Co', orderRef: 'PO-123' })
43
-
44
- // 3. Receive: the manufacturer sends back the same file with UID filled in
45
- const check = await claimSet.ranges.importUids(scope, set.id, range.id, { csv: returnedCsv })
46
- // check.rows: ok / index_mismatch / bad_uid / uid_in_use / … ; nothing written yet
47
- await claimSet.ranges.importUids(scope, set.id, range.id, { csv: returnedCsv, commit: true })
48
-
49
- // 4. Assign all of it, or from-to
50
- await claimSet.ranges.assign(scope, set.id, range.id, { collectionId: 'reading-fc', productId: 'scarf', start: 201, end: 250 })
51
- ```
52
-
53
- **Programming on a phone instead of a manufacturer** (Sidekick): `ranges.next()` gives the lowest
54
- tag without a chip, with its URL and key; write it, then `ranges.recordTag(scope, set.id, range.id, code, { uid })`.
55
-
56
- ## The printer file
57
-
58
- One row per tag, in index order. The manufacturer fills in `UID` and sends the same file back; rows are
59
- matched by `Code`, and `Index` is checked.
60
-
61
- | Tag type | Columns |
62
- |---|---|
63
- | NTAG 424 DNA | `Index, Code, URL, PiccOffset, Key, UID` |
64
- | NTAG 213/215/216 | `Index, Code, URL, MirrorOffset, MirrorPage, MirrorByte, UID` |
65
- | QR | `Index, Code, URL` |
66
-
67
- `URL` is `https://{tap domain}/t/{set}/{code}` plus the placeholder the chip fills on every read
68
- (`?p=` + 32 zeros for 424 SUN, `?m=00000000000000x000000` for the 21x UID and counter mirror). The
69
- offsets are byte positions in the NDEF data, assuming the `https://` URI prefix code and a short record.
70
-
71
- ## Notes
72
-
73
- - Range responses are never cached by the SDK: tags change as they're programmed and several carry keys.
74
- - A tag bound to a proof (minted or claimed) is never moved or cleared by `assign`; it's reported in `skipped`.
75
- - `status` is the stage (`minted`, `sent`, `received`), or `assigned` once every tag is assigned.
1
+ # NFC tag ranges and partners
2
+
3
+ For partners and enterprise clients who run their own tag programmes: mint a batch of tags, send them
4
+ to a manufacturer with instructions, get the chip UIDs back, then put the tags to work for a client or
5
+ product. Ordinary business collections don't use this.
6
+
7
+ ## The words
8
+
9
+ | Word | Means |
10
+ |---|---|
11
+ | **Claim set** | A pool of codes on one tap domain and one tag type (NTAG 424 DNA, NTAG 213/215/216, or QR). Most partners need one. |
12
+ | **Range** | A named, contiguous block of tags inside a set: "Tap to donate - February", 100 tags, indexes 201-300. |
13
+ | **Index** | A tag's place in its set. Ranges never overlap and indexes are never reused. |
14
+ | **Partner** | An organisation above collections that owns sets and assigns their tags into collections its people administer. Set up by SmartLinks staff. |
15
+
16
+ ## Who can do what
17
+
18
+ `scope` is `{ partnerId }` for a partner's sets, or `{ collectionId }` for a collection's own sets.
19
+
20
+ | Role | Can |
21
+ |---|---|
22
+ | Partner owner / admin, or a collection admin | Make sets, mint ranges, download the printer file and instructions, import UIDs, program tags, assign |
23
+ | Partner assigner | See ranges and tags (never keys), assign |
24
+
25
+ Assigning always needs you to be an admin of the collection the tags go to.
26
+
27
+ ## The flow
28
+
29
+ ```ts
30
+ import { partner, claimSet } from '@proveanything/smartlinks'
31
+
32
+ const [p] = await partner.list() // the partners you belong to
33
+ const scope = { partnerId: p.id }
34
+ const [set] = await partner.claimSets(p.id) // or partner.createClaimSet(p.id, { name, tagType: 'ntag424', tapDomain })
35
+
36
+ // 1. Mint
37
+ const range = await claimSet.ranges.create(scope, set.id, { name: 'Tap to donate - February', quantity: 100 })
38
+
39
+ // 2. Send to print: the CSV (carries each tag's key) and the instruction sheet
40
+ const file = await claimSet.ranges.exportFile(scope, set.id, range.id) // { filename, csv }
41
+ const sheet = await claimSet.ranges.instructions(scope, set.id, range.id, 'pdf') // { filename, base64 }
42
+ await claimSet.ranges.update(scope, set.id, range.id, { stage: 'sent', manufacturer: 'Print Co', orderRef: 'PO-123' })
43
+
44
+ // 3. Receive: the manufacturer sends back the same file with UID filled in
45
+ const check = await claimSet.ranges.importUids(scope, set.id, range.id, { csv: returnedCsv })
46
+ // check.rows: ok / index_mismatch / bad_uid / uid_in_use / … ; nothing written yet
47
+ await claimSet.ranges.importUids(scope, set.id, range.id, { csv: returnedCsv, commit: true })
48
+
49
+ // 4. Assign all of it, or from-to
50
+ await claimSet.ranges.assign(scope, set.id, range.id, { collectionId: 'reading-fc', productId: 'scarf', start: 201, end: 250 })
51
+ ```
52
+
53
+ **Programming on a phone instead of a manufacturer** (Sidekick): `ranges.next()` gives the lowest
54
+ tag without a chip, with its URL and key; write it, then `ranges.recordTag(scope, set.id, range.id, code, { uid })` (add `password` for a plain tag's write password).
55
+
56
+ To find where a chip or code went, `partner.lookupTags(partner.id, { uid })` or `{ code }` searches every set the partner owns and returns each hit's set, tag and range, never a key.
57
+
58
+ ## The printer file
59
+
60
+ One row per tag, in index order. The manufacturer fills in `UID` and sends the same file back; rows are
61
+ matched by `Code`, and `Index` is checked.
62
+
63
+ | Tag type | Columns |
64
+ |---|---|
65
+ | NTAG 424 DNA | `Index, Code, URL, PiccOffset, Key, UID` |
66
+ | NTAG 213/215/216 | `Index, Code, URL, MirrorOffset, MirrorPage, MirrorByte, UID` |
67
+ | QR | `Index, Code, URL` |
68
+
69
+ `URL` is `https://{tap domain}/t/{set}/{code}` plus the placeholder the chip fills on every read
70
+ (`?p=` + 32 zeros for 424 SUN, `?m=00000000000000x000000` for the 21x UID and counter mirror). The
71
+ offsets are byte positions in the NDEF data, assuming the `https://` URI prefix code and a short record.
72
+
73
+ ## Notes
74
+
75
+ - Range responses are never cached by the SDK: tags change as they're programmed and several carry keys.
76
+ - A tag bound to a proof (minted or claimed) is never moved or cleared by `assign`; it's reported in `skipped`.
77
+ - `status` is the stage (`minted`, `sent`, `received`), or `assigned` once every tag is assigned.
package/openapi.yaml CHANGED
@@ -9141,6 +9141,45 @@ paths:
9141
9141
  description: Unauthorized
9142
9142
  404:
9143
9143
  description: Not found
9144
+ /admin/partner/{partnerId}/tags/lookup:
9145
+ get:
9146
+ tags:
9147
+ - partner
9148
+ summary: "Find a tag across the partner's sets by its chip UID (8 or 14 hex) or its code (printed or bare)."
9149
+ operationId: partner_lookupTags
9150
+ security:
9151
+ - bearerAuth: []
9152
+ parameters:
9153
+ - name: partnerId
9154
+ in: path
9155
+ required: true
9156
+ schema:
9157
+ type: string
9158
+ - name: uid
9159
+ in: query
9160
+ required: false
9161
+ schema:
9162
+ type: string
9163
+ - name: code
9164
+ in: query
9165
+ required: false
9166
+ schema:
9167
+ type: string
9168
+ responses:
9169
+ 200:
9170
+ description: Success
9171
+ content:
9172
+ application/json:
9173
+ schema:
9174
+ type: array
9175
+ items:
9176
+ $ref: "#/components/schemas/PartnerTagHit"
9177
+ 400:
9178
+ description: Bad request
9179
+ 401:
9180
+ description: Unauthorized
9181
+ 404:
9182
+ description: Not found
9144
9183
  /authKit/{authKitId}/config:
9145
9184
  get:
9146
9185
  tags:
@@ -9602,6 +9641,38 @@ paths:
9602
9641
  description: Unauthorized
9603
9642
  404:
9604
9643
  description: Not found
9644
+ /authkit/{clientId}/auth/microsoft:
9645
+ post:
9646
+ tags:
9647
+ - authKit
9648
+ summary: authKit.microsoftLogin
9649
+ operationId: authKit_microsoftLogin
9650
+ security: []
9651
+ parameters:
9652
+ - name: clientId
9653
+ in: path
9654
+ required: true
9655
+ schema:
9656
+ type: string
9657
+ responses:
9658
+ 200:
9659
+ description: Success
9660
+ content:
9661
+ application/json:
9662
+ schema:
9663
+ $ref: "#/components/schemas/AuthLoginResponse"
9664
+ 400:
9665
+ description: Bad request
9666
+ 401:
9667
+ description: Unauthorized
9668
+ 404:
9669
+ description: Not found
9670
+ requestBody:
9671
+ required: true
9672
+ content:
9673
+ application/json:
9674
+ schema:
9675
+ $ref: "#/components/schemas/MicrosoftLoginOptions"
9605
9676
  /authkit/{clientId}/auth/phone/send-code:
9606
9677
  post:
9607
9678
  tags:
@@ -23894,6 +23965,35 @@ components:
23894
23965
  additionalProperties: true
23895
23966
  required:
23896
23967
  - success
23968
+ MicrosoftLoginOptions:
23969
+ type: object
23970
+ properties:
23971
+ returnOrigin:
23972
+ type: string
23973
+ trustedDeviceToken:
23974
+ type: string
23975
+ AuthKitAccess:
23976
+ type: object
23977
+ properties:
23978
+ mode:
23979
+ type: string
23980
+ enum:
23981
+ - anyone
23982
+ - organisation
23983
+ domains:
23984
+ type: array
23985
+ items:
23986
+ type: string
23987
+ emails:
23988
+ type: array
23989
+ items:
23990
+ type: string
23991
+ microsoftTenants:
23992
+ type: object
23993
+ additionalProperties:
23994
+ type: string
23995
+ required:
23996
+ - mode
23897
23997
  AppleLoginOptions:
23898
23998
  type: object
23899
23999
  properties:
@@ -24410,6 +24510,10 @@ components:
24410
24510
  enum:
24411
24511
  - open
24412
24512
  - invite
24513
+ access:
24514
+ $ref: "#/components/schemas/AuthKitAccess"
24515
+ microsoftClientId:
24516
+ type: string
24413
24517
  required:
24414
24518
  - id
24415
24519
  AuthKitInviteRequest:
@@ -24542,6 +24646,7 @@ components:
24542
24646
  - ACCOUNT_EXISTS_UNVERIFIED
24543
24647
  - APPLE_AUTH_FAILED
24544
24648
  - SIGN_UP_CLOSED
24649
+ - NOT_IN_ORGANISATION
24545
24650
  MfaFactor:
24546
24651
  type: string
24547
24652
  enum:
@@ -26120,6 +26225,25 @@ components:
26120
26225
  - codes
26121
26226
  - deleted
26122
26227
  - createdAt
26228
+ PartnerTagHit:
26229
+ type: object
26230
+ properties:
26231
+ namespace:
26232
+ type: string
26233
+ claimSetId:
26234
+ type: string
26235
+ set:
26236
+ $ref: "#/components/schemas/PartnerClaimSet"
26237
+ tag:
26238
+ $ref: "#/components/schemas/ClaimSetRangeTag"
26239
+ range:
26240
+ $ref: "#/components/schemas/ClaimSetRange"
26241
+ required:
26242
+ - namespace
26243
+ - claimSetId
26244
+ - set
26245
+ - tag
26246
+ - range
26123
26247
  CreatePartnerClaimSetRequest:
26124
26248
  type: object
26125
26249
  properties:
@@ -31324,6 +31448,10 @@ components:
31324
31448
  type: string
31325
31449
  mirror:
31326
31450
  type: string
31451
+ d:
31452
+ type: string
31453
+ c:
31454
+ type: string
31327
31455
  userId:
31328
31456
  type: string
31329
31457
  required: