@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.
- package/dist/api/authKit.d.ts +17 -1
- package/dist/api/authKit.js +24 -0
- package/dist/api/claimSet.d.ts +2 -1
- package/dist/api/partner.d.ts +6 -1
- package/dist/api/partner.js +10 -0
- package/dist/docs/API_SUMMARY.md +41 -14
- package/dist/docs/auth-kit.md +50 -0
- package/dist/docs/tag-ranges.md +77 -75
- package/dist/openapi.yaml +128 -0
- package/dist/types/authKit.d.ts +45 -1
- package/dist/types/claimSet.d.ts +10 -0
- package/dist/types/nfc.d.ts +4 -0
- package/docs/API_SUMMARY.md +41 -14
- package/docs/auth-kit.md +50 -0
- package/docs/tag-ranges.md +77 -75
- package/openapi.yaml +128 -0
- package/package.json +87 -87
package/dist/types/authKit.d.ts
CHANGED
|
@@ -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 {
|
package/dist/types/claimSet.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/nfc.d.ts
CHANGED
|
@@ -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 {
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.1.
|
|
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
|
|
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**
|
package/docs/tag-ranges.md
CHANGED
|
@@ -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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
|
64
|
-
|
|
65
|
-
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
-
|
|
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:
|