@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.
@@ -1,4 +1,4 @@
1
- import type { AuthLoginResponse, AppleLoginOptions, RefreshResponse, LogoutResponse, PhoneSendCodeResponse, PhoneVerifyResponse, PasswordResetRequestResponse, VerifyResetTokenResponse, PasswordResetCompleteResponse, EmailVerificationActionResponse, EmailVerifyTokenResponse, AuthKitConfig, AuthKitConfigInput, AuthKitSetIdentityResponse, AuthKitInviteRequest, AuthKitInviteResponse, MagicLinkSendResponse, MagicLinkVerifyResponse, UserProfile, UpdateProfileResponse, ProfileUpdateData, SuccessResponse, SendWhatsAppRequest, SendWhatsAppResponse, ExchangeWhatsAppSessionResponse, VerifyWhatsAppResponse, WhatsAppStatusResponse, SendSmsVerifyRequest, SendSmsVerifyResponse, VerifySmsResponse, UpsertContactRequest, UpsertContactResponse, MfaChallengeSendResponse, MfaFinalizeResponse, MfaEnrollSendResponse, MfaTotpEnrollResponse, MfaEnrolledResponse, MfaFactor, MfaFactorsResponse, TrustedDevice } from "../types/authKit.js";
1
+ import type { AuthLoginResponse, AppleLoginOptions, MicrosoftLoginOptions, RefreshResponse, LogoutResponse, PhoneSendCodeResponse, PhoneVerifyResponse, PasswordResetRequestResponse, VerifyResetTokenResponse, PasswordResetCompleteResponse, EmailVerificationActionResponse, EmailVerifyTokenResponse, AuthKitConfig, AuthKitConfigInput, AuthKitSetIdentityResponse, AuthKitInviteRequest, AuthKitInviteResponse, MagicLinkSendResponse, MagicLinkVerifyResponse, UserProfile, UpdateProfileResponse, ProfileUpdateData, SuccessResponse, SendWhatsAppRequest, SendWhatsAppResponse, ExchangeWhatsAppSessionResponse, VerifyWhatsAppResponse, WhatsAppStatusResponse, SendSmsVerifyRequest, SendSmsVerifyResponse, VerifySmsResponse, UpsertContactRequest, UpsertContactResponse, MfaChallengeSendResponse, MfaFinalizeResponse, MfaEnrollSendResponse, MfaTotpEnrollResponse, MfaEnrolledResponse, MfaFactor, MfaFactorsResponse, TrustedDevice } from "../types/authKit.js";
2
2
  /**
3
3
  * Namespace containing helper functions for the new AuthKit API.
4
4
  * Legacy collection-based authKit helpers retained (marked as *Legacy*).
@@ -93,6 +93,22 @@ export declare namespace authKit {
93
93
  * @see AppleLoginOptions
94
94
  */
95
95
  function appleLogin(clientId: string, identityToken: string, opts?: AppleLoginOptions): Promise<AuthLoginResponse>;
96
+ /**
97
+ * Sign in with Microsoft — work or school accounts from any organisation, and personal Microsoft
98
+ * accounts — via a Microsoft ID token (public). Mirrors {@link appleLogin}: on success the bearer
99
+ * token is stored and the cache invalidated.
100
+ *
101
+ * Get the ID token from Microsoft's sign-in (response_type `id_token`, scope `openid profile email`)
102
+ * against the app in the public config's `microsoftClientId`. A site on its own domain can't be a
103
+ * registered address of that app, so the SmartLinks sign-in UI opens SmartLinks' Microsoft sign-in
104
+ * page in a popup, which calls this with `opts.returnOrigin` and hands the result back.
105
+ *
106
+ * Notable error codes: `MISSING_MICROSOFT_TOKEN` (400), `MICROSOFT_AUTH_NOT_CONFIGURED` (400),
107
+ * `INVALID_MICROSOFT_TOKEN` (401), `MICROSOFT_ORIGIN_NOT_ALLOWED` (403), `NOT_IN_ORGANISATION` (403 —
108
+ * the login setup's {@link AuthKitAccess} rule), `ACCOUNT_EXISTS_UNVERIFIED` (409),
109
+ * `MICROSOFT_AUTH_FAILED` (500). Gated by step-up MFA like every sign-in.
110
+ */
111
+ function microsoftLogin(clientId: string, idToken: string, opts?: MicrosoftLoginOptions): Promise<AuthLoginResponse>;
96
112
  /**
97
113
  * Exchange a refresh token for a fresh access token (public — the refresh token IS
98
114
  * the credential). **Native sessions only**; refresh tokens are issued only when the
@@ -139,6 +139,30 @@ export var authKit;
139
139
  return res;
140
140
  }
141
141
  authKit.appleLogin = appleLogin;
142
+ /**
143
+ * Sign in with Microsoft — work or school accounts from any organisation, and personal Microsoft
144
+ * accounts — via a Microsoft ID token (public). Mirrors {@link appleLogin}: on success the bearer
145
+ * token is stored and the cache invalidated.
146
+ *
147
+ * Get the ID token from Microsoft's sign-in (response_type `id_token`, scope `openid profile email`)
148
+ * against the app in the public config's `microsoftClientId`. A site on its own domain can't be a
149
+ * registered address of that app, so the SmartLinks sign-in UI opens SmartLinks' Microsoft sign-in
150
+ * page in a popup, which calls this with `opts.returnOrigin` and hands the result back.
151
+ *
152
+ * Notable error codes: `MISSING_MICROSOFT_TOKEN` (400), `MICROSOFT_AUTH_NOT_CONFIGURED` (400),
153
+ * `INVALID_MICROSOFT_TOKEN` (401), `MICROSOFT_ORIGIN_NOT_ALLOWED` (403), `NOT_IN_ORGANISATION` (403 —
154
+ * the login setup's {@link AuthKitAccess} rule), `ACCOUNT_EXISTS_UNVERIFIED` (409),
155
+ * `MICROSOFT_AUTH_FAILED` (500). Gated by step-up MFA like every sign-in.
156
+ */
157
+ async function microsoftLogin(clientId, idToken, opts) {
158
+ const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/microsoft`, Object.assign({ idToken }, opts));
159
+ if (res.token) {
160
+ setBearerToken(res.token);
161
+ invalidateCache();
162
+ }
163
+ return res;
164
+ }
165
+ authKit.microsoftLogin = microsoftLogin;
142
166
  /* ===================================
143
167
  * Native session refresh (public)
144
168
  * =================================== */
@@ -117,7 +117,8 @@ export declare namespace claimSet {
117
117
  /** Record the chip a phone just programmed with this code. */
118
118
  function recordTag(scope: ClaimSetScope, claimSetId: string, rangeId: string, code: string, params: {
119
119
  uid: string;
120
- data?: Record<string, unknown>;
120
+ data?: Record<string, unknown>; /** A plain (NTAG 21x) tag's write password, kept with the tag */
121
+ password?: string;
121
122
  }): Promise<{
122
123
  tag: ClaimSetRangeTag;
123
124
  range: ClaimSetRange;
@@ -1,4 +1,4 @@
1
- import { Partner, PartnerClaimSet, CreatePartnerClaimSetRequest } from "../types/index.js";
1
+ import { Partner, PartnerClaimSet, CreatePartnerClaimSetRequest, PartnerTagHit } from "../types/index.js";
2
2
  /**
3
3
  * Partners: organisations above collections (FanConnect, Zenith Coins, …) that own NFC claim sets and
4
4
  * assign their tags into collections their people administer. Partners and their members are set up by
@@ -16,4 +16,9 @@ export declare namespace partner {
16
16
  function getClaimSet(partnerId: string, claimSetId: string): Promise<PartnerClaimSet>;
17
17
  /** A new claim set for the partner, on one of its tap domains (owners and admins). */
18
18
  function createClaimSet(partnerId: string, params: CreatePartnerClaimSetRequest): Promise<PartnerClaimSet>;
19
+ /** 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. */
20
+ function lookupTags(partnerId: string, query: {
21
+ uid?: string;
22
+ code?: string;
23
+ }): Promise<PartnerTagHit[]>;
19
24
  }
@@ -32,4 +32,14 @@ export var partner;
32
32
  return post(`/admin/partner/${encodeURIComponent(partnerId)}/claimSet`, params);
33
33
  }
34
34
  partner.createClaimSet = createClaimSet;
35
+ /** 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. */
36
+ async function lookupTags(partnerId, query) {
37
+ const q = new URLSearchParams();
38
+ if (query.uid)
39
+ q.set('uid', query.uid);
40
+ if (query.code)
41
+ q.set('code', query.code);
42
+ return request(`/admin/partner/${encodeURIComponent(partnerId)}/tags/lookup?${q}`);
43
+ }
44
+ partner.lookupTags = lookupTags;
35
45
  })(partner || (partner = {}));
@@ -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,
@@ -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/dist/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: