@proveanything/smartlinks 1.15.13 → 1.15.15

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.
@@ -20,15 +20,28 @@ export declare namespace authKit {
20
20
  * fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
21
21
  */
22
22
  function login(clientId: string, email: string, password: string, trustedDeviceToken?: string): Promise<AuthLoginResponse>;
23
- /** Register a new user (public). */
23
+ /**
24
+ * Register a new user (public).
25
+ *
26
+ * Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's
27
+ * nothing to challenge against.
28
+ */
24
29
  function register(clientId: string, data: {
25
30
  email: string;
26
31
  password: string;
27
32
  displayName?: string;
28
33
  accountData?: Record<string, any>;
29
34
  }): Promise<AuthLoginResponse>;
30
- /** Google OAuth login via ID token (public). */
31
- function googleLogin(clientId: string, idToken: string): Promise<AuthLoginResponse>;
35
+ /**
36
+ * Google OAuth login via ID token (public).
37
+ *
38
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
39
+ *
40
+ * @param trustedDeviceToken - Optional. Pass a token previously returned by
41
+ * {@link mfaChallengeVerify}/{@link mfaRecoveryCode} (with `trustDevice: true`) to skip
42
+ * the challenge on this device, same as {@link login}.
43
+ */
44
+ function googleLogin(clientId: string, idToken: string, trustedDeviceToken?: string): Promise<AuthLoginResponse>;
32
45
  /** Google OAuth login via server-side authorization code (public). */
33
46
  function googleCodeLogin(clientId: string, code: string, redirectUri: string): Promise<AuthLoginResponse>;
34
47
  /**
@@ -46,6 +59,9 @@ export declare namespace authKit {
46
59
  * then link Apple from settings. **The same 409 can now come back from
47
60
  * {@link googleLogin}** under the shared verified-to-verified linking policy.
48
61
  *
62
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. Pass
63
+ * `opts.trustedDeviceToken` to skip the challenge on a recognized device.
64
+ *
49
65
  * @see AppleLoginOptions
50
66
  */
51
67
  function appleLogin(clientId: string, identityToken: string, opts?: AppleLoginOptions): Promise<AuthLoginResponse>;
@@ -84,20 +100,46 @@ export declare namespace authKit {
84
100
  redirectUrl: string;
85
101
  accountData?: Record<string, any>;
86
102
  }): Promise<MagicLinkSendResponse>;
87
- /** Verify a magic link token and authenticate/create the user (public). */
88
- function verifyMagicLink(clientId: string, token: string): Promise<MagicLinkVerifyResponse>;
103
+ /**
104
+ * Verify a magic link token and authenticate/create the user (public).
105
+ *
106
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
107
+ *
108
+ * @param trustedDeviceToken - Optional. See {@link login}.
109
+ */
110
+ function verifyMagicLink(clientId: string, token: string, trustedDeviceToken?: string): Promise<MagicLinkVerifyResponse>;
89
111
  /** Send phone verification code (public). */
90
112
  function sendPhoneCode(clientId: string, phoneNumber: string): Promise<PhoneSendCodeResponse>;
91
- /** Verify phone verification code (public). */
92
- function verifyPhoneCode(clientId: string, phoneNumber: string, code: string): Promise<PhoneVerifyResponse>;
113
+ /**
114
+ * Verify phone verification code (public).
115
+ *
116
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
117
+ *
118
+ * @param trustedDeviceToken - Optional. See {@link login}.
119
+ */
120
+ function verifyPhoneCode(clientId: string, phoneNumber: string, code: string, trustedDeviceToken?: string): Promise<PhoneVerifyResponse>;
93
121
  /** Send a WhatsApp verification deep-link (public). */
94
122
  function sendWhatsApp(clientId: string, body?: SendWhatsAppRequest): Promise<SendWhatsAppResponse>;
95
- /** Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback). */
123
+ /**
124
+ * Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
125
+ *
126
+ * Not gated by step-up MFA — this endpoint only confirms the code, it never issues a
127
+ * session/bearer token, so there is nothing to challenge. {@link exchangeWhatsAppSession}
128
+ * is the WhatsApp method that's gated.
129
+ */
96
130
  function verifyWhatsApp(clientId: string, token: string, phoneNumber: string): Promise<VerifyWhatsAppResponse>;
97
131
  /** Poll WhatsApp verification status for a token (public). */
98
132
  function getWhatsAppStatus(clientId: string, token: string): Promise<WhatsAppStatusResponse>;
99
- /** Exchange a verified WhatsApp token for an Auth Kit session (public). */
100
- function exchangeWhatsAppSession(clientId: string, token: string, sessionKey: string): Promise<ExchangeWhatsAppSessionResponse>;
133
+ /**
134
+ * Exchange a verified WhatsApp token for an Auth Kit session (public).
135
+ *
136
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. This is
137
+ * the WhatsApp method that needs `trustedDeviceToken`, not {@link verifyWhatsApp} (which
138
+ * never issues a session).
139
+ *
140
+ * @param trustedDeviceToken - Optional. See {@link login}.
141
+ */
142
+ function exchangeWhatsAppSession(clientId: string, token: string, sessionKey: string, trustedDeviceToken?: string): Promise<ExchangeWhatsAppSessionResponse>;
101
143
  /** Send an SMS click-to-verify link (public). */
102
144
  function sendSmsVerify(clientId: string, body: SendSmsVerifyRequest): Promise<SendSmsVerifyResponse>;
103
145
  /** Verify an SMS click-to-verify token via API (public). */
@@ -35,14 +35,27 @@ export var authKit;
35
35
  return res;
36
36
  }
37
37
  authKit.login = login;
38
- /** Register a new user (public). */
38
+ /**
39
+ * Register a new user (public).
40
+ *
41
+ * Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's
42
+ * nothing to challenge against.
43
+ */
39
44
  async function register(clientId, data) {
40
45
  return post(`/authkit/${encodeURIComponent(clientId)}/auth/register`, data);
41
46
  }
42
47
  authKit.register = register;
43
- /** Google OAuth login via ID token (public). */
44
- async function googleLogin(clientId, idToken) {
45
- const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/google`, { idToken });
48
+ /**
49
+ * Google OAuth login via ID token (public).
50
+ *
51
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
52
+ *
53
+ * @param trustedDeviceToken - Optional. Pass a token previously returned by
54
+ * {@link mfaChallengeVerify}/{@link mfaRecoveryCode} (with `trustDevice: true`) to skip
55
+ * the challenge on this device, same as {@link login}.
56
+ */
57
+ async function googleLogin(clientId, idToken, trustedDeviceToken) {
58
+ const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/google`, { idToken, trustedDeviceToken });
46
59
  if (res.token) {
47
60
  setBearerToken(res.token);
48
61
  invalidateCache();
@@ -75,6 +88,9 @@ export var authKit;
75
88
  * then link Apple from settings. **The same 409 can now come back from
76
89
  * {@link googleLogin}** under the shared verified-to-verified linking policy.
77
90
  *
91
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. Pass
92
+ * `opts.trustedDeviceToken` to skip the challenge on a recognized device.
93
+ *
78
94
  * @see AppleLoginOptions
79
95
  */
80
96
  async function appleLogin(clientId, identityToken, opts) {
@@ -138,9 +154,15 @@ export var authKit;
138
154
  return post(`/authkit/${encodeURIComponent(clientId)}/auth/magic-link/send`, data);
139
155
  }
140
156
  authKit.sendMagicLink = sendMagicLink;
141
- /** Verify a magic link token and authenticate/create the user (public). */
142
- async function verifyMagicLink(clientId, token) {
143
- const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/magic-link/verify`, { token });
157
+ /**
158
+ * Verify a magic link token and authenticate/create the user (public).
159
+ *
160
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
161
+ *
162
+ * @param trustedDeviceToken - Optional. See {@link login}.
163
+ */
164
+ async function verifyMagicLink(clientId, token, trustedDeviceToken) {
165
+ const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/magic-link/verify`, { token, trustedDeviceToken });
144
166
  if (res.token) {
145
167
  setBearerToken(res.token);
146
168
  invalidateCache();
@@ -153,9 +175,15 @@ export var authKit;
153
175
  return post(`/authkit/${encodeURIComponent(clientId)}/auth/phone/send-code`, { phoneNumber });
154
176
  }
155
177
  authKit.sendPhoneCode = sendPhoneCode;
156
- /** Verify phone verification code (public). */
157
- async function verifyPhoneCode(clientId, phoneNumber, code) {
158
- const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/phone/verify`, { phoneNumber, code });
178
+ /**
179
+ * Verify phone verification code (public).
180
+ *
181
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
182
+ *
183
+ * @param trustedDeviceToken - Optional. See {@link login}.
184
+ */
185
+ async function verifyPhoneCode(clientId, phoneNumber, code, trustedDeviceToken) {
186
+ const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/phone/verify`, { phoneNumber, code, trustedDeviceToken });
159
187
  setBearerToken(res.token);
160
188
  invalidateCache();
161
189
  return res;
@@ -166,7 +194,13 @@ export var authKit;
166
194
  return post(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/send`, body);
167
195
  }
168
196
  authKit.sendWhatsApp = sendWhatsApp;
169
- /** Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback). */
197
+ /**
198
+ * Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
199
+ *
200
+ * Not gated by step-up MFA — this endpoint only confirms the code, it never issues a
201
+ * session/bearer token, so there is nothing to challenge. {@link exchangeWhatsAppSession}
202
+ * is the WhatsApp method that's gated.
203
+ */
170
204
  async function verifyWhatsApp(clientId, token, phoneNumber) {
171
205
  return post(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/verify`, { token, phoneNumber });
172
206
  }
@@ -177,9 +211,17 @@ export var authKit;
177
211
  return request(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/status?token=${encodedToken}`);
178
212
  }
179
213
  authKit.getWhatsAppStatus = getWhatsAppStatus;
180
- /** Exchange a verified WhatsApp token for an Auth Kit session (public). */
181
- async function exchangeWhatsAppSession(clientId, token, sessionKey) {
182
- const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/exchange-session`, { token, sessionKey });
214
+ /**
215
+ * Exchange a verified WhatsApp token for an Auth Kit session (public).
216
+ *
217
+ * Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. This is
218
+ * the WhatsApp method that needs `trustedDeviceToken`, not {@link verifyWhatsApp} (which
219
+ * never issues a session).
220
+ *
221
+ * @param trustedDeviceToken - Optional. See {@link login}.
222
+ */
223
+ async function exchangeWhatsAppSession(clientId, token, sessionKey, trustedDeviceToken) {
224
+ const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/whatsapp/exchange-session`, { token, sessionKey, trustedDeviceToken });
183
225
  setBearerToken(res.token);
184
226
  invalidateCache();
185
227
  return res;
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.15.13 | Generated: 2026-07-27T08:21:16.504Z
3
+ Version: 1.15.15 | Generated: 2026-07-29T17:46:34.198Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -170,6 +170,9 @@ Replace or augment globally applied custom headers.
170
170
  **setBearerToken**(token: string | undefined) → `void`
171
171
  Allows setting the bearerToken at runtime (e.g. after login/logout). Clears the HTTP cache whenever the token actually changes so that stale user-scoped responses (e.g. /account/profile) are not served after a login or logout event.
172
172
 
173
+ **getBearerToken**() → `string | undefined`
174
+ Returns the bearer token currently held by the SDK, or `undefined` if none is set. In proxy mode, credentials are held by the parent frame, not the local SDK, so this returns `undefined` even when the caller is authenticated.
175
+
173
176
  **getBaseURL**() → `string | null`
174
177
  Get the currently configured API base URL. Returns null if initializeApi() has not been called yet.
175
178
 
@@ -3104,6 +3107,10 @@ interface AppleLoginOptions {
3104
3107
  * these again, and never inside the token. Forwarded so the server can persist the
3105
3108
  * display name on first account creation. Treated as untrusted (never used for identity).
3106
3109
  userInfo?: { email?: string; name?: string }
3110
+ * A previously-issued trusted-device token (from a prior MFA `challenge/verify`
3111
+ * or `challenge/recovery-code` response). If still valid, the server skips any
3112
+ * step-up challenge for this login. See `SDK_AUTHKIT_MFA_UPDATE.md` §3.
3113
+ trustedDeviceToken?: string
3107
3114
  }
3108
3115
  ```
3109
3116
 
@@ -3778,6 +3785,21 @@ interface Collection {
3778
3785
  portalUrl?: string // URL for the collection's portal (if applicable)
3779
3786
  allowAutoGenerateClaims?: boolean
3780
3787
  defaultAuthKitId: string // default auth kit for this collection, used for auth
3788
+ admin?: {
3789
+ * Redirect behavior for plain collection-level scans (a short link with
3790
+ * no product/serial code in the path, e.g. `https://.../c/shortId`).
3791
+ * Unset means such links always go to the normal collection page.
3792
+ redirect?: CollectionRedirectConfig
3793
+ }
3794
+ }
3795
+ ```
3796
+
3797
+ **CollectionRedirectConfig** (interface)
3798
+ ```typescript
3799
+ interface CollectionRedirectConfig {
3800
+ mode: 'fixed' | 'dynamic' | 'deep'
3801
+ url?: string
3802
+ template?: string
3781
3803
  }
3782
3804
  ```
3783
3805
 
@@ -8620,16 +8642,16 @@ Gets current account information for the logged in user. Returns user, owner, ac
8620
8642
  Login with email + password (public). When the client's MFA policy requires a step-up, the server returns **403 `MFA_REQUIRED`** instead of a session — `login()` throws a `SmartlinksApiError` with `err.errorResponse?.errorCode === 'MFA_REQUIRED'` and the challenge details in `err.details` (see {@link MfaRequiredDetails}). Route the caller to {@link mfaChallengeSend} on that error; this method's return type is unchanged. returned one (via {@link mfaChallengeVerify}/{@link mfaRecoveryCode} with `trustDevice: true`), pass it here to skip the challenge entirely as long as it's still valid. If it's revoked/expired, the server silently falls back to requiring a fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
8621
8643
 
8622
8644
  **register**(clientId: string, data: { email: string; password: string; displayName?: string; accountData?: Record<string, any> }) → `Promise<AuthLoginResponse>`
8623
- Register a new user (public).
8645
+ Register a new user (public). Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's nothing to challenge against.
8624
8646
 
8625
- **googleLogin**(clientId: string, idToken: string) → `Promise<AuthLoginResponse>`
8626
- Google OAuth login via ID token (public).
8647
+ **googleLogin**(clientId: string, idToken: string, trustedDeviceToken?: string) → `Promise<AuthLoginResponse>`
8648
+ Google OAuth login via ID token (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. {@link mfaChallengeVerify}/{@link mfaRecoveryCode} (with `trustDevice: true`) to skip the challenge on this device, same as {@link login}.
8627
8649
 
8628
8650
  **googleCodeLogin**(clientId: string, code: string, redirectUri: string) → `Promise<AuthLoginResponse>`
8629
8651
  Google OAuth login via server-side authorization code (public).
8630
8652
 
8631
8653
  **appleLogin**(clientId: string, identityToken: string, opts?: AppleLoginOptions) → `Promise<AuthLoginResponse>`
8632
- 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.
8654
+ 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.
8633
8655
 
8634
8656
  **refreshToken**(clientId: string, refreshToken: string) → `Promise<RefreshResponse>`
8635
8657
  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.
@@ -8640,26 +8662,26 @@ Revoke a refresh token's entire family server-side (that device's whole rotation
8640
8662
  **sendMagicLink**(clientId: string, data: { email: string; redirectUrl: string; accountData?: Record<string, any> }) → `Promise<MagicLinkSendResponse>`
8641
8663
  Send a magic link email to the user (public).
8642
8664
 
8643
- **verifyMagicLink**(clientId: string, token: string) → `Promise<MagicLinkVerifyResponse>`
8644
- Verify a magic link token and authenticate/create the user (public).
8665
+ **verifyMagicLink**(clientId: string, token: string, trustedDeviceToken?: string) → `Promise<MagicLinkVerifyResponse>`
8666
+ Verify a magic link token and authenticate/create the user (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
8645
8667
 
8646
8668
  **sendPhoneCode**(clientId: string, phoneNumber: string) → `Promise<PhoneSendCodeResponse>`
8647
8669
  Send phone verification code (public).
8648
8670
 
8649
- **verifyPhoneCode**(clientId: string, phoneNumber: string, code: string) → `Promise<PhoneVerifyResponse>`
8650
- Verify phone verification code (public).
8671
+ **verifyPhoneCode**(clientId: string, phoneNumber: string, code: string, trustedDeviceToken?: string) → `Promise<PhoneVerifyResponse>`
8672
+ Verify phone verification code (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
8651
8673
 
8652
8674
  **sendWhatsApp**(clientId: string, body: SendWhatsAppRequest = {}) → `Promise<SendWhatsAppResponse>`
8653
8675
  Send a WhatsApp verification deep-link (public).
8654
8676
 
8655
8677
  **verifyWhatsApp**(clientId: string, token: string, phoneNumber: string) → `Promise<VerifyWhatsAppResponse>`
8656
- Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
8678
+ Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback). Not gated by step-up MFA — this endpoint only confirms the code, it never issues a session/bearer token, so there is nothing to challenge. {@link exchangeWhatsAppSession} is the WhatsApp method that's gated.
8657
8679
 
8658
8680
  **getWhatsAppStatus**(clientId: string, token: string) → `Promise<WhatsAppStatusResponse>`
8659
8681
  Poll WhatsApp verification status for a token (public).
8660
8682
 
8661
- **exchangeWhatsAppSession**(clientId: string, token: string, sessionKey: string) → `Promise<ExchangeWhatsAppSessionResponse>`
8662
- Exchange a verified WhatsApp token for an Auth Kit session (public).
8683
+ **exchangeWhatsAppSession**(clientId: string, token: string, sessionKey: string, trustedDeviceToken?: string) → `Promise<ExchangeWhatsAppSessionResponse>`
8684
+ Exchange a verified WhatsApp token for an Auth Kit session (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. This is the WhatsApp method that needs `trustedDeviceToken`, not {@link verifyWhatsApp} (which never issues a session).
8663
8685
 
8664
8686
  **sendSmsVerify**(clientId: string, body: SendSmsVerifyRequest) → `Promise<SendSmsVerifyResponse>`
8665
8687
  Send an SMS click-to-verify link (public).
@@ -226,6 +226,7 @@ const session = await authKit.appleLogin(clientId, appleIdentityToken, {
226
226
  // All optional:
227
227
  nonce, // raw nonce, if you used nonce binding
228
228
  userInfo: { name, email }, // first authorization callback ONLY — Apple never resends it
229
+ trustedDeviceToken, // skip an MFA step-up challenge on a recognized device — see "Step-up MFA" below
229
230
  });
230
231
  // session.isNewUser and session.expiresAt (ms epoch) are populated by this endpoint.
231
232
  ```
@@ -307,16 +308,32 @@ clearPersistedTokens();
307
308
  ## Step-up MFA (Phase 1)
308
309
 
309
310
  Backend-only for now — **no admin-console UI and no challenge UI ship with this pass.**
310
- Factors: **email OTP, SMS OTP, recovery codes**. WhatsApp OTP, TOTP, and passkeys are
311
- deferred to a later phase; don't build against them yet.
311
+ Factors: **email OTP, SMS OTP, recovery codes**. WhatsApp OTP, TOTP, and passkeys as
312
+ *factors* are deferred to a later phase; don't build against them yet.
312
313
 
313
- The step-up gate is wired into **`login()` only** `register`, `verifyPhoneCode`, and
314
- `verifyMagicLink` never return `MFA_REQUIRED`, regardless of client config.
314
+ The step-up gate applies to **every login method that issues a session**:
315
315
 
316
- ### Handling `MFA_REQUIRED` from `login()`
317
-
318
- `login()`'s return type is unchanged. When the client's MFA policy requires a step-up,
319
- the server returns 403 instead of a session, and `login()` throws:
316
+ | Method | Gated? |
317
+ |---|---|
318
+ | `login()` | |
319
+ | `googleLogin()` | ✅ |
320
+ | `appleLogin()` | ✅ — pass `opts.trustedDeviceToken` |
321
+ | `verifyPhoneCode()` | ✅ |
322
+ | `verifyMagicLink()` | ✅ |
323
+ | `exchangeWhatsAppSession()` | ✅ — **this is the WhatsApp method that needs a trusted-device token, not `verifyWhatsApp()`** |
324
+ | `verifyWhatsApp()` | ❌ — only confirms the code, never issues a session, so there's nothing to gate |
325
+ | `register()` | ❌ — a brand-new user has no enrolled factors to challenge against |
326
+ | `googleCodeLogin()` | ❌ — no `/auth/google-code` route exists server-side |
327
+
328
+ All six gated methods accept an optional `trustedDeviceToken` param (the last positional
329
+ argument, or `opts.trustedDeviceToken` for `appleLogin()`) — same purpose everywhere: skip
330
+ the challenge on a device the user already verified.
331
+
332
+ ### Handling `MFA_REQUIRED`
333
+
334
+ Every gated method's return type is unchanged. When the client's MFA policy requires a
335
+ step-up, the server returns 403 instead of a session, and the method throws — identically
336
+ for all six:
320
337
 
321
338
  ```ts
322
339
  import { authKit, SmartlinksApiError } from '@proveanything/smartlinks';
@@ -362,10 +379,15 @@ above — same secure storage, same persist-before-next-call discipline:
362
379
  // 1) Persist trustedDeviceToken from a successful challenge (trustDevice: true)
363
380
  persistTrustedDeviceToken(session.trustedDeviceToken, session.trustedDeviceExpiresAt);
364
381
 
365
- // 2) Send it on every subsequent login() — if still valid, the challenge is skipped entirely
382
+ // 2) Send it on every subsequent call to ANY of the six gated methods — if still valid,
383
+ // the challenge is skipped entirely. It isn't tied to which method the user challenged
384
+ // through, so "remember this device" works no matter which login method they pick next.
366
385
  const session = await authKit.login(clientId, email, password, storedTrustedDeviceToken);
386
+ const session = await authKit.googleLogin(clientId, idToken, storedTrustedDeviceToken);
387
+ const session = await authKit.exchangeWhatsAppSession(clientId, waToken, sessionKey, storedTrustedDeviceToken);
388
+ // ...and so on for appleLogin (via opts), verifyPhoneCode, verifyMagicLink.
367
389
  // If it's revoked/expired, the server silently falls back to requiring a fresh challenge —
368
- // login() just throws MFA_REQUIRED again, no special-case handling needed.
390
+ // the method just throws MFA_REQUIRED again, no special-case handling needed.
369
391
 
370
392
  // 3) Let users audit/revoke devices from a Settings screen
371
393
  const { devices } = await authKit.listTrustedDevices(clientId);
@@ -404,7 +426,7 @@ await authKit.removeMfaFactor(clientId, 'sms', password);
404
426
 
405
427
  | `errorCode` | HTTP | Meaning / client action |
406
428
  |---|---|---|
407
- | `MFA_REQUIRED` | 403 | From `login()` only — see above |
429
+ | `MFA_REQUIRED` | 403 | From any of the six gated login methods — see above |
408
430
  | `INVALID_MFA_CODE` | 401 | Wrong code — let the user retry (attempts are capped) |
409
431
  | `MFA_TOO_MANY_ATTEMPTS` | 429 | 5 wrong attempts — the challenge is burned; restart from `login()` |
410
432
  | `MFA_FACTOR_NOT_ENROLLED` | 400 | Requested factor isn't enrolled — programming error if the UI only offers `availableFactors` |
package/dist/http.d.ts CHANGED
@@ -49,6 +49,12 @@ export declare function setExtraHeaders(headers: Record<string, string>): void;
49
49
  * login or logout event.
50
50
  */
51
51
  export declare function setBearerToken(token: string | undefined): void;
52
+ /**
53
+ * Returns the bearer token currently held by the SDK, or `undefined` if none is set.
54
+ * In proxy mode, credentials are held by the parent frame, not the local SDK,
55
+ * so this returns `undefined` even when the caller is authenticated.
56
+ */
57
+ export declare function getBearerToken(): string | undefined;
52
58
  /**
53
59
  * Get the currently configured API base URL.
54
60
  * Returns null if initializeApi() has not been called yet.
package/dist/http.js CHANGED
@@ -466,6 +466,14 @@ export function setBearerToken(token) {
466
466
  if (cachePersistence !== 'none')
467
467
  idbClear().catch(() => { });
468
468
  }
469
+ /**
470
+ * Returns the bearer token currently held by the SDK, or `undefined` if none is set.
471
+ * In proxy mode, credentials are held by the parent frame, not the local SDK,
472
+ * so this returns `undefined` even when the caller is authenticated.
473
+ */
474
+ export function getBearerToken() {
475
+ return bearerToken;
476
+ }
469
477
  /**
470
478
  * Get the currently configured API base URL.
471
479
  * Returns null if initializeApi() has not been called yet.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken } from "./http";
1
+ export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken, getBearerToken } from "./http";
2
2
  export * from "./api";
3
3
  export * from "./types";
4
4
  export { iframe } from "./iframe";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  // Top-level entrypoint of the npm package. Re-export initializeApi + all namespaces.
3
- export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken } from "./http";
3
+ export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken, getBearerToken } from "./http";
4
4
  export * from "./api";
5
5
  export * from "./types";
6
6
  // Iframe namespace
package/dist/openapi.yaml CHANGED
@@ -8291,7 +8291,7 @@ paths:
8291
8291
  post:
8292
8292
  tags:
8293
8293
  - authKit
8294
- summary: Sign in with Apple via an Apple identity token (public).
8294
+ summary: authKit.appleLogin
8295
8295
  operationId: authKit_appleLogin
8296
8296
  security: []
8297
8297
  parameters:
@@ -18709,6 +18709,8 @@ components:
18709
18709
  userInfo:
18710
18710
  type: object
18711
18711
  additionalProperties: true
18712
+ trustedDeviceToken:
18713
+ type: string
18712
18714
  MfaRequiredDetails:
18713
18715
  type: object
18714
18716
  properties:
@@ -19723,6 +19725,11 @@ components:
19723
19725
  type: boolean
19724
19726
  defaultAuthKitId:
19725
19727
  type: string
19728
+ admin:
19729
+ type: object
19730
+ additionalProperties: true
19731
+ redirect:
19732
+ $ref: "#/components/schemas/CollectionRedirectConfig"
19726
19733
  required:
19727
19734
  - id
19728
19735
  - title
@@ -19737,6 +19744,21 @@ components:
19737
19744
  - roles
19738
19745
  - shortId
19739
19746
  - defaultAuthKitId
19747
+ CollectionRedirectConfig:
19748
+ type: object
19749
+ properties:
19750
+ mode:
19751
+ type: string
19752
+ enum:
19753
+ - fixed
19754
+ - dynamic
19755
+ - deep
19756
+ url:
19757
+ type: string
19758
+ template:
19759
+ type: string
19760
+ required:
19761
+ - mode
19740
19762
  HubAvailabilityResponse:
19741
19763
  type: object
19742
19764
  properties:
@@ -112,6 +112,12 @@ export interface AppleLoginOptions {
112
112
  email?: string;
113
113
  name?: string;
114
114
  };
115
+ /**
116
+ * A previously-issued trusted-device token (from a prior MFA `challenge/verify`
117
+ * or `challenge/recovery-code` response). If still valid, the server skips any
118
+ * step-up challenge for this login. See `SDK_AUTHKIT_MFA_UPDATE.md` §3.
119
+ */
120
+ trustedDeviceToken?: string;
115
121
  }
116
122
  /**
117
123
  * Server-defined error codes returned by AuthKit federated-login endpoints
@@ -121,10 +127,10 @@ export interface AppleLoginOptions {
121
127
  */
122
128
  export type AuthKitErrorCode = 'MISSING_APPLE_TOKEN' | 'APPLE_AUTH_NOT_CONFIGURED' | 'INVALID_APPLE_TOKEN' | 'ACCOUNT_EXISTS_UNVERIFIED' | 'APPLE_AUTH_FAILED';
123
129
  /**
124
- * Details carried on the `MFA_REQUIRED` error thrown by {@link authKit.login} when the
125
- * client's MFA policy requires a step-up. Surfaced as `err.details` (or
126
- * `err.errorResponse.details`) on the `SmartlinksApiError` — `login()` never resolves in
127
- * this case, it throws a 403 instead.
130
+ * Details carried on the `MFA_REQUIRED` error thrown by any session-issuing login method
131
+ * (see the gate list above) when the client's MFA policy requires a step-up. Surfaced as
132
+ * `err.details` (or `err.errorResponse.details`) on the `SmartlinksApiError` — the login
133
+ * method never resolves in this case, it throws a 403 instead.
128
134
  */
129
135
  export interface MfaRequiredDetails {
130
136
  /** Short-lived (10 min) and single-use. Pass it to every challenge/finalize call below. */
@@ -156,7 +162,10 @@ export interface MfaFinalizeResponse extends AuthLoginResponse {
156
162
  /**
157
163
  * Present only when the challenge was completed with `trustDevice: true`. Persist it
158
164
  * alongside the refresh token (same secure storage) and send it as `trustedDeviceToken`
159
- * on future {@link authKit.login} calls to skip the challenge on this device.
165
+ * on future calls to any gated login method ({@link authKit.login},
166
+ * {@link authKit.googleLogin}, {@link authKit.appleLogin}, {@link authKit.verifyPhoneCode},
167
+ * {@link authKit.verifyMagicLink}, {@link authKit.exchangeWhatsAppSession}) to skip the
168
+ * challenge on this device — it isn't tied to which method the user challenged through.
160
169
  */
161
170
  trustedDeviceToken?: string;
162
171
  trustedDeviceExpiresAt?: string;
@@ -199,7 +208,7 @@ export interface TrustedDevice {
199
208
  * Server-defined error codes for the step-up MFA flow, surfaced via
200
209
  * `SmartlinksApiError.errorCode`.
201
210
  *
202
- * - `MFA_REQUIRED` (403) — from {@link authKit.login} only; see {@link MfaRequiredDetails}.
211
+ * - `MFA_REQUIRED` (403) — from any session-issuing login method; see {@link MfaRequiredDetails}.
203
212
  * - `INVALID_MFA_CODE` (401) — wrong code; let the user retry (attempts are capped, see next).
204
213
  * - `MFA_TOO_MANY_ATTEMPTS` (429) — 5 wrong attempts; the challenge is burned, restart from `login()`.
205
214
  * - `MFA_FACTOR_NOT_ENROLLED` (400) — requested factor isn't enrolled (or disabled for the
@@ -58,6 +58,29 @@ export interface Collection {
58
58
  /** Allow users to claim products without providing a proof ID (auto-generates serial on-demand) */
59
59
  allowAutoGenerateClaims?: boolean;
60
60
  defaultAuthKitId: string;
61
+ /** Admin-configured collection behavior not exposed on public reads */
62
+ admin?: {
63
+ /**
64
+ * Redirect behavior for plain collection-level scans (a short link with
65
+ * no product/serial code in the path, e.g. `https://.../c/shortId`).
66
+ * Unset means such links always go to the normal collection page.
67
+ */
68
+ redirect?: CollectionRedirectConfig;
69
+ };
70
+ }
71
+ /**
72
+ * Redirect rule for plain collection-level scans.
73
+ * - `fixed` / `dynamic` - redirect the entire collection to one URL/template,
74
+ * defined right here via `url`/`template`.
75
+ * - `deep` - defer to the collection's product-level, facet-rule, and
76
+ * collection-wide redirect config (checked in that order) instead.
77
+ */
78
+ export interface CollectionRedirectConfig {
79
+ mode: 'fixed' | 'dynamic' | 'deep';
80
+ /** Required when mode === 'fixed' */
81
+ url?: string;
82
+ /** Mustache template, required when mode === 'dynamic' */
83
+ template?: string;
61
84
  }
62
85
  export type CollectionResponse = Collection;
63
86
  export type CollectionCreateRequest = Omit<Collection, 'id' | 'shortId'>;
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.15.13 | Generated: 2026-07-27T08:21:16.504Z
3
+ Version: 1.15.15 | Generated: 2026-07-29T17:46:34.198Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -170,6 +170,9 @@ Replace or augment globally applied custom headers.
170
170
  **setBearerToken**(token: string | undefined) → `void`
171
171
  Allows setting the bearerToken at runtime (e.g. after login/logout). Clears the HTTP cache whenever the token actually changes so that stale user-scoped responses (e.g. /account/profile) are not served after a login or logout event.
172
172
 
173
+ **getBearerToken**() → `string | undefined`
174
+ Returns the bearer token currently held by the SDK, or `undefined` if none is set. In proxy mode, credentials are held by the parent frame, not the local SDK, so this returns `undefined` even when the caller is authenticated.
175
+
173
176
  **getBaseURL**() → `string | null`
174
177
  Get the currently configured API base URL. Returns null if initializeApi() has not been called yet.
175
178
 
@@ -3104,6 +3107,10 @@ interface AppleLoginOptions {
3104
3107
  * these again, and never inside the token. Forwarded so the server can persist the
3105
3108
  * display name on first account creation. Treated as untrusted (never used for identity).
3106
3109
  userInfo?: { email?: string; name?: string }
3110
+ * A previously-issued trusted-device token (from a prior MFA `challenge/verify`
3111
+ * or `challenge/recovery-code` response). If still valid, the server skips any
3112
+ * step-up challenge for this login. See `SDK_AUTHKIT_MFA_UPDATE.md` §3.
3113
+ trustedDeviceToken?: string
3107
3114
  }
3108
3115
  ```
3109
3116
 
@@ -3778,6 +3785,21 @@ interface Collection {
3778
3785
  portalUrl?: string // URL for the collection's portal (if applicable)
3779
3786
  allowAutoGenerateClaims?: boolean
3780
3787
  defaultAuthKitId: string // default auth kit for this collection, used for auth
3788
+ admin?: {
3789
+ * Redirect behavior for plain collection-level scans (a short link with
3790
+ * no product/serial code in the path, e.g. `https://.../c/shortId`).
3791
+ * Unset means such links always go to the normal collection page.
3792
+ redirect?: CollectionRedirectConfig
3793
+ }
3794
+ }
3795
+ ```
3796
+
3797
+ **CollectionRedirectConfig** (interface)
3798
+ ```typescript
3799
+ interface CollectionRedirectConfig {
3800
+ mode: 'fixed' | 'dynamic' | 'deep'
3801
+ url?: string
3802
+ template?: string
3781
3803
  }
3782
3804
  ```
3783
3805
 
@@ -8620,16 +8642,16 @@ Gets current account information for the logged in user. Returns user, owner, ac
8620
8642
  Login with email + password (public). When the client's MFA policy requires a step-up, the server returns **403 `MFA_REQUIRED`** instead of a session — `login()` throws a `SmartlinksApiError` with `err.errorResponse?.errorCode === 'MFA_REQUIRED'` and the challenge details in `err.details` (see {@link MfaRequiredDetails}). Route the caller to {@link mfaChallengeSend} on that error; this method's return type is unchanged. returned one (via {@link mfaChallengeVerify}/{@link mfaRecoveryCode} with `trustDevice: true`), pass it here to skip the challenge entirely as long as it's still valid. If it's revoked/expired, the server silently falls back to requiring a fresh challenge — `login()` just returns `MFA_REQUIRED` again, no special handling.
8621
8643
 
8622
8644
  **register**(clientId: string, data: { email: string; password: string; displayName?: string; accountData?: Record<string, any> }) → `Promise<AuthLoginResponse>`
8623
- Register a new user (public).
8645
+ Register a new user (public). Not gated by step-up MFA — a brand-new user has no enrolled factors yet, so there's nothing to challenge against.
8624
8646
 
8625
- **googleLogin**(clientId: string, idToken: string) → `Promise<AuthLoginResponse>`
8626
- Google OAuth login via ID token (public).
8647
+ **googleLogin**(clientId: string, idToken: string, trustedDeviceToken?: string) → `Promise<AuthLoginResponse>`
8648
+ Google OAuth login via ID token (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. {@link mfaChallengeVerify}/{@link mfaRecoveryCode} (with `trustDevice: true`) to skip the challenge on this device, same as {@link login}.
8627
8649
 
8628
8650
  **googleCodeLogin**(clientId: string, code: string, redirectUri: string) → `Promise<AuthLoginResponse>`
8629
8651
  Google OAuth login via server-side authorization code (public).
8630
8652
 
8631
8653
  **appleLogin**(clientId: string, identityToken: string, opts?: AppleLoginOptions) → `Promise<AuthLoginResponse>`
8632
- 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.
8654
+ 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.
8633
8655
 
8634
8656
  **refreshToken**(clientId: string, refreshToken: string) → `Promise<RefreshResponse>`
8635
8657
  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.
@@ -8640,26 +8662,26 @@ Revoke a refresh token's entire family server-side (that device's whole rotation
8640
8662
  **sendMagicLink**(clientId: string, data: { email: string; redirectUrl: string; accountData?: Record<string, any> }) → `Promise<MagicLinkSendResponse>`
8641
8663
  Send a magic link email to the user (public).
8642
8664
 
8643
- **verifyMagicLink**(clientId: string, token: string) → `Promise<MagicLinkVerifyResponse>`
8644
- Verify a magic link token and authenticate/create the user (public).
8665
+ **verifyMagicLink**(clientId: string, token: string, trustedDeviceToken?: string) → `Promise<MagicLinkVerifyResponse>`
8666
+ Verify a magic link token and authenticate/create the user (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
8645
8667
 
8646
8668
  **sendPhoneCode**(clientId: string, phoneNumber: string) → `Promise<PhoneSendCodeResponse>`
8647
8669
  Send phone verification code (public).
8648
8670
 
8649
- **verifyPhoneCode**(clientId: string, phoneNumber: string, code: string) → `Promise<PhoneVerifyResponse>`
8650
- Verify phone verification code (public).
8671
+ **verifyPhoneCode**(clientId: string, phoneNumber: string, code: string, trustedDeviceToken?: string) → `Promise<PhoneVerifyResponse>`
8672
+ Verify phone verification code (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
8651
8673
 
8652
8674
  **sendWhatsApp**(clientId: string, body: SendWhatsAppRequest = {}) → `Promise<SendWhatsAppResponse>`
8653
8675
  Send a WhatsApp verification deep-link (public).
8654
8676
 
8655
8677
  **verifyWhatsApp**(clientId: string, token: string, phoneNumber: string) → `Promise<VerifyWhatsAppResponse>`
8656
- Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback).
8678
+ Manually verify WhatsApp token if inbound webhook path is unavailable (legacy/public fallback). Not gated by step-up MFA — this endpoint only confirms the code, it never issues a session/bearer token, so there is nothing to challenge. {@link exchangeWhatsAppSession} is the WhatsApp method that's gated.
8657
8679
 
8658
8680
  **getWhatsAppStatus**(clientId: string, token: string) → `Promise<WhatsAppStatusResponse>`
8659
8681
  Poll WhatsApp verification status for a token (public).
8660
8682
 
8661
- **exchangeWhatsAppSession**(clientId: string, token: string, sessionKey: string) → `Promise<ExchangeWhatsAppSessionResponse>`
8662
- Exchange a verified WhatsApp token for an Auth Kit session (public).
8683
+ **exchangeWhatsAppSession**(clientId: string, token: string, sessionKey: string, trustedDeviceToken?: string) → `Promise<ExchangeWhatsAppSessionResponse>`
8684
+ Exchange a verified WhatsApp token for an Auth Kit session (public). Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. This is the WhatsApp method that needs `trustedDeviceToken`, not {@link verifyWhatsApp} (which never issues a session).
8663
8685
 
8664
8686
  **sendSmsVerify**(clientId: string, body: SendSmsVerifyRequest) → `Promise<SendSmsVerifyResponse>`
8665
8687
  Send an SMS click-to-verify link (public).
package/docs/auth-kit.md CHANGED
@@ -226,6 +226,7 @@ const session = await authKit.appleLogin(clientId, appleIdentityToken, {
226
226
  // All optional:
227
227
  nonce, // raw nonce, if you used nonce binding
228
228
  userInfo: { name, email }, // first authorization callback ONLY — Apple never resends it
229
+ trustedDeviceToken, // skip an MFA step-up challenge on a recognized device — see "Step-up MFA" below
229
230
  });
230
231
  // session.isNewUser and session.expiresAt (ms epoch) are populated by this endpoint.
231
232
  ```
@@ -307,16 +308,32 @@ clearPersistedTokens();
307
308
  ## Step-up MFA (Phase 1)
308
309
 
309
310
  Backend-only for now — **no admin-console UI and no challenge UI ship with this pass.**
310
- Factors: **email OTP, SMS OTP, recovery codes**. WhatsApp OTP, TOTP, and passkeys are
311
- deferred to a later phase; don't build against them yet.
311
+ Factors: **email OTP, SMS OTP, recovery codes**. WhatsApp OTP, TOTP, and passkeys as
312
+ *factors* are deferred to a later phase; don't build against them yet.
312
313
 
313
- The step-up gate is wired into **`login()` only** `register`, `verifyPhoneCode`, and
314
- `verifyMagicLink` never return `MFA_REQUIRED`, regardless of client config.
314
+ The step-up gate applies to **every login method that issues a session**:
315
315
 
316
- ### Handling `MFA_REQUIRED` from `login()`
317
-
318
- `login()`'s return type is unchanged. When the client's MFA policy requires a step-up,
319
- the server returns 403 instead of a session, and `login()` throws:
316
+ | Method | Gated? |
317
+ |---|---|
318
+ | `login()` | |
319
+ | `googleLogin()` | ✅ |
320
+ | `appleLogin()` | ✅ — pass `opts.trustedDeviceToken` |
321
+ | `verifyPhoneCode()` | ✅ |
322
+ | `verifyMagicLink()` | ✅ |
323
+ | `exchangeWhatsAppSession()` | ✅ — **this is the WhatsApp method that needs a trusted-device token, not `verifyWhatsApp()`** |
324
+ | `verifyWhatsApp()` | ❌ — only confirms the code, never issues a session, so there's nothing to gate |
325
+ | `register()` | ❌ — a brand-new user has no enrolled factors to challenge against |
326
+ | `googleCodeLogin()` | ❌ — no `/auth/google-code` route exists server-side |
327
+
328
+ All six gated methods accept an optional `trustedDeviceToken` param (the last positional
329
+ argument, or `opts.trustedDeviceToken` for `appleLogin()`) — same purpose everywhere: skip
330
+ the challenge on a device the user already verified.
331
+
332
+ ### Handling `MFA_REQUIRED`
333
+
334
+ Every gated method's return type is unchanged. When the client's MFA policy requires a
335
+ step-up, the server returns 403 instead of a session, and the method throws — identically
336
+ for all six:
320
337
 
321
338
  ```ts
322
339
  import { authKit, SmartlinksApiError } from '@proveanything/smartlinks';
@@ -362,10 +379,15 @@ above — same secure storage, same persist-before-next-call discipline:
362
379
  // 1) Persist trustedDeviceToken from a successful challenge (trustDevice: true)
363
380
  persistTrustedDeviceToken(session.trustedDeviceToken, session.trustedDeviceExpiresAt);
364
381
 
365
- // 2) Send it on every subsequent login() — if still valid, the challenge is skipped entirely
382
+ // 2) Send it on every subsequent call to ANY of the six gated methods — if still valid,
383
+ // the challenge is skipped entirely. It isn't tied to which method the user challenged
384
+ // through, so "remember this device" works no matter which login method they pick next.
366
385
  const session = await authKit.login(clientId, email, password, storedTrustedDeviceToken);
386
+ const session = await authKit.googleLogin(clientId, idToken, storedTrustedDeviceToken);
387
+ const session = await authKit.exchangeWhatsAppSession(clientId, waToken, sessionKey, storedTrustedDeviceToken);
388
+ // ...and so on for appleLogin (via opts), verifyPhoneCode, verifyMagicLink.
367
389
  // If it's revoked/expired, the server silently falls back to requiring a fresh challenge —
368
- // login() just throws MFA_REQUIRED again, no special-case handling needed.
390
+ // the method just throws MFA_REQUIRED again, no special-case handling needed.
369
391
 
370
392
  // 3) Let users audit/revoke devices from a Settings screen
371
393
  const { devices } = await authKit.listTrustedDevices(clientId);
@@ -404,7 +426,7 @@ await authKit.removeMfaFactor(clientId, 'sms', password);
404
426
 
405
427
  | `errorCode` | HTTP | Meaning / client action |
406
428
  |---|---|---|
407
- | `MFA_REQUIRED` | 403 | From `login()` only — see above |
429
+ | `MFA_REQUIRED` | 403 | From any of the six gated login methods — see above |
408
430
  | `INVALID_MFA_CODE` | 401 | Wrong code — let the user retry (attempts are capped) |
409
431
  | `MFA_TOO_MANY_ATTEMPTS` | 429 | 5 wrong attempts — the challenge is burned; restart from `login()` |
410
432
  | `MFA_FACTOR_NOT_ENROLLED` | 400 | Requested factor isn't enrolled — programming error if the UI only offers `availableFactors` |
package/openapi.yaml CHANGED
@@ -8291,7 +8291,7 @@ paths:
8291
8291
  post:
8292
8292
  tags:
8293
8293
  - authKit
8294
- summary: Sign in with Apple via an Apple identity token (public).
8294
+ summary: authKit.appleLogin
8295
8295
  operationId: authKit_appleLogin
8296
8296
  security: []
8297
8297
  parameters:
@@ -18709,6 +18709,8 @@ components:
18709
18709
  userInfo:
18710
18710
  type: object
18711
18711
  additionalProperties: true
18712
+ trustedDeviceToken:
18713
+ type: string
18712
18714
  MfaRequiredDetails:
18713
18715
  type: object
18714
18716
  properties:
@@ -19723,6 +19725,11 @@ components:
19723
19725
  type: boolean
19724
19726
  defaultAuthKitId:
19725
19727
  type: string
19728
+ admin:
19729
+ type: object
19730
+ additionalProperties: true
19731
+ redirect:
19732
+ $ref: "#/components/schemas/CollectionRedirectConfig"
19726
19733
  required:
19727
19734
  - id
19728
19735
  - title
@@ -19737,6 +19744,21 @@ components:
19737
19744
  - roles
19738
19745
  - shortId
19739
19746
  - defaultAuthKitId
19747
+ CollectionRedirectConfig:
19748
+ type: object
19749
+ properties:
19750
+ mode:
19751
+ type: string
19752
+ enum:
19753
+ - fixed
19754
+ - dynamic
19755
+ - deep
19756
+ url:
19757
+ type: string
19758
+ template:
19759
+ type: string
19760
+ required:
19761
+ - mode
19740
19762
  HubAvailabilityResponse:
19741
19763
  type: object
19742
19764
  properties:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "1.15.13",
3
+ "version": "1.15.15",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",