@fleetless/contracts 5.2.0 → 6.0.0-next.1

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/artifacts/openapi.json +1975 -415
  3. package/artifacts/routes.json +1697 -251
  4. package/artifacts/schema/accept-team-invite-request.schema.json +13 -7
  5. package/artifacts/schema/app-auth-config.schema.json +108 -4
  6. package/artifacts/schema/app-hosted-pages.schema.json +33 -0
  7. package/artifacts/schema/app-invitation.schema.json +2 -2
  8. package/artifacts/schema/app-mail-template-list-response.schema.json +4 -3
  9. package/artifacts/schema/app-mail-template.schema.json +3 -2
  10. package/artifacts/schema/app-sign-in-methods.schema.json +19 -0
  11. package/artifacts/schema/app-user-list-response.schema.json +36 -0
  12. package/artifacts/schema/app-user.schema.json +36 -0
  13. package/artifacts/schema/auth-me-response.schema.json +27 -0
  14. package/artifacts/schema/auth-ok.schema.json +13 -1
  15. package/artifacts/schema/client-accept-invitation-request.schema.json +3 -4
  16. package/artifacts/schema/client-identity.schema.json +13 -1
  17. package/artifacts/schema/client-login-code-request.schema.json +24 -0
  18. package/artifacts/schema/client-login-code-verify-request.schema.json +30 -0
  19. package/artifacts/schema/client-provider-list-response.schema.json +22 -2
  20. package/artifacts/schema/client-register-request.schema.json +3 -4
  21. package/artifacts/schema/client-sign-in-result.schema.json +55 -0
  22. package/artifacts/schema/client-two-factor-disable-request.schema.json +15 -0
  23. package/artifacts/schema/client-two-factor-setup-confirm-request.schema.json +20 -0
  24. package/artifacts/schema/client-two-factor-setup-confirm-response.schema.json +49 -0
  25. package/artifacts/schema/client-two-factor-setup-request.schema.json +12 -0
  26. package/artifacts/schema/client-two-factor-verify-request.schema.json +25 -0
  27. package/artifacts/schema/create-app-invitation-request.schema.json +1 -1
  28. package/artifacts/schema/create-passkey-request.schema.json +25 -0
  29. package/artifacts/schema/create-passkey-response.schema.json +84 -0
  30. package/artifacts/schema/developer-passkey.schema.json +56 -0
  31. package/artifacts/schema/developer-two-factor.schema.json +105 -0
  32. package/artifacts/schema/fleetless-user-list-response.schema.json +22 -0
  33. package/artifacts/schema/fleetless-user.schema.json +22 -0
  34. package/artifacts/schema/invalid-code-details.schema.json +16 -0
  35. package/artifacts/schema/org.schema.json +5 -0
  36. package/artifacts/schema/patch-org-request.schema.json +5 -3
  37. package/artifacts/schema/patch-org-response.schema.json +5 -0
  38. package/artifacts/schema/put-app-auth-look-request.schema.json +22 -0
  39. package/artifacts/schema/put-app-auth-mcp-request.schema.json +1 -14
  40. package/artifacts/schema/put-app-auth-sign-in-request.schema.json +39 -0
  41. package/artifacts/schema/put-app-auth-urls-request.schema.json +31 -4
  42. package/artifacts/schema/recovery-codes-response.schema.json +20 -0
  43. package/artifacts/schema/rename-passkey-request.schema.json +16 -0
  44. package/artifacts/schema/totp-confirm-request.schema.json +15 -0
  45. package/artifacts/schema/totp-confirm-response.schema.json +27 -0
  46. package/artifacts/schema/two-factor-challenge.schema.json +24 -0
  47. package/artifacts/schema/two-factor-setup-response.schema.json +21 -0
  48. package/artifacts/schema/webauthn-options-response.schema.json +18 -0
  49. package/dist/app-users.d.ts +154 -34
  50. package/dist/app-users.js +177 -45
  51. package/dist/client-auth.d.ts +133 -24
  52. package/dist/client-auth.js +139 -28
  53. package/dist/config.d.ts +2 -2
  54. package/dist/errors.d.ts +10 -1
  55. package/dist/errors.js +34 -5
  56. package/dist/identity.d.ts +172 -123
  57. package/dist/identity.js +189 -101
  58. package/dist/index.d.ts +9 -9
  59. package/dist/index.js +11 -7
  60. package/dist/protocol.d.ts +1 -1
  61. package/dist/realtime.d.ts +1 -0
  62. package/dist/rest.d.ts +2 -2
  63. package/dist/rest.js +17 -28
  64. package/dist/routes.d.ts +19 -0
  65. package/dist/routes.js +497 -183
  66. package/package.json +1 -1
  67. package/artifacts/schema/developer-login-request.schema.json +0 -19
  68. package/artifacts/schema/password-reset-confirm.schema.json +0 -19
  69. package/artifacts/schema/password-reset-request.schema.json +0 -14
  70. package/artifacts/schema/sign-up-request.schema.json +0 -26
  71. package/artifacts/schema/sign-up-response.schema.json +0 -127
package/dist/identity.js CHANGED
@@ -7,9 +7,10 @@ import { z } from 'zod';
7
7
  * There are two identity spaces now and **nothing joins them**:
8
8
  *
9
9
  * - *Fleetless users*, this file. The people who configure robots in the
10
- * console. Email globally unique, tier `owner | developer`, a Fleetless
11
- * password, login through the auth portal. They always have MCP access, at
12
- * the one central endpoint.
10
+ * console. Email globally unique, tier `owner | developer`, sign-in through
11
+ * the auth portal by a code mailed to them or by a passkey — **no
12
+ * password** — with an optional second factor the organisation may require.
13
+ * They always have MCP access, at the one central endpoint.
13
14
  * - *app users*, `app-users.ts`. The people who use a developer's app. One app
14
15
  * each, email unique per app, authenticated through the JSON client-auth
15
16
  * API that the developer's own UI calls.
@@ -24,25 +25,31 @@ import { z } from 'zod';
24
25
  * the two populations have different lifecycles, and every joining mechanism
25
26
  * was cost without a product reason.
26
27
  *
27
- * What did **not** change: the password rules, the session and token shapes,
28
- * and the enumeration-oracle reasoning on password reset. None of those was
29
- * ever a statement about which space a person lived in.
28
+ * What did **not** change: the session and token shapes, and the
29
+ * enumeration-oracle reasoning. Neither was ever a statement about which space
30
+ * a person lived in.
31
+ *
32
+ * **Developers have no password any more.** They sign in with a six-digit
33
+ * code mailed to them, or with a passkey, which proves possession and user
34
+ * verification at once and so completes a sign-in on its own. After a code,
35
+ * a developer with a second factor — a passkey or an authenticator app —
36
+ * gives it; ten recovery codes are the fallback. The password sign-in, the
37
+ * password change, the reset pages and `POST /api/auth/signup` are gone: the
38
+ * portal's sign-up (email, code, organisation) is the one door in.
30
39
  *
31
40
  * **Email is globally unique here** — `lower(email)` unique across all orgs, so
32
- * one address is exactly one Fleetless user in exactly one org. Every shape
33
- * that identifies a person by a bare address (`developerLoginRequest`,
34
- * `passwordResetRequest`) therefore resolves to at most one account with no org
35
- * context needed. App users are the opposite and say so on their own shape:
36
- * unique **per app**, so one address may be several unrelated app accounts.
41
+ * one address is exactly one Fleetless user in exactly one org. A bare address
42
+ * therefore resolves to at most one account with no org context needed. App
43
+ * users are the opposite and say so on their own shape: unique **per app**, so
44
+ * one address may be several unrelated app accounts.
37
45
  */
38
46
  /**
39
47
  * The password rule, stated once so the cloud, the console and the SDK refuse
40
48
  * the same inputs for the same reason. Length only: a rule a user cannot
41
49
  * predict is a rule they work around.
42
50
  *
43
- * Shared by both identity spaces on purpose. A weaker rule for app users would
44
- * be a second policy for one decision, and the weaker one always wins by
45
- * accident.
51
+ * App users only: Fleetless users sign in by emailed code or passkey and hold
52
+ * no password.
46
53
  */
47
54
  export const password = z.string().min(12).max(256);
48
55
  /** The bound on a Fleetless user's display name; `APP_USER_DISPLAY_NAME_MAX` matches it, so a rename cannot be legal in one space and refused in the other. */
@@ -70,6 +77,9 @@ export const org = z.object({
70
77
  name: z.string().min(1).max(120).meta({
71
78
  description: 'The organisation\'s display name. Free text, changed through `PATCH /api/org`.',
72
79
  }),
80
+ require_two_factor: z.boolean().meta({
81
+ description: 'Whether every member must have a second factor — a passkey or an authenticator app. A member without one sets it up at their next sign-in, before any session exists; nobody is signed out when it is switched on. It covers the console and the central MCP endpoint; server keys and robot bridges are not people and are not affected. Owners change it through `PATCH /api/org`.',
82
+ }),
73
83
  created_at: z.iso.datetime().meta({
74
84
  description: 'When the organisation was created, as an ISO 8601 timestamp.',
75
85
  }),
@@ -81,8 +91,8 @@ export const patchOrgResponse = z.object({
81
91
  }),
82
92
  });
83
93
  /**
84
- * **A member of the org's team.** Console access, a tier, a Fleetless
85
- * password, and no relationship whatsoever to any app's users.
94
+ * **A member of the org's team.** Console access, a tier, a sign-in by
95
+ * emailed code or passkey, and no relationship whatsoever to any app's users.
86
96
  *
87
97
  * `email` is **globally unique** — `lower(email)` unique across every org, a
88
98
  * constraint the cloud enforces in the database; a schema cannot see two rows
@@ -93,10 +103,10 @@ export const patchOrgResponse = z.object({
93
103
  * groups), `mcp_access` (a Fleetless user always has MCP access, at the
94
104
  * central endpoint), and `has_password`. The last is the interesting one — it
95
105
  * existed because a pool user might have been provisioned by an identity
96
- * provider and hold no Fleetless credential. A Fleetless user always holds
97
- * one: the console is password-only by design, which removes the
98
- * IdP-lockout class entirely, so a field reporting whether the credential
99
- * exists would have exactly one value forever.
106
+ * provider and hold no Fleetless credential. Every Fleetless user signs in by
107
+ * a code mailed to their address, so the console has no federated door and
108
+ * no IdP-lockout class, and a field about a password would describe nothing.
109
+ * What varies is the second factor, which `two_factor` reports.
100
110
  */
101
111
  export const fleetlessUser = z.object({
102
112
  id: z.uuid().meta({
@@ -114,6 +124,14 @@ export const fleetlessUser = z.object({
114
124
  tier: orgAdminTier.meta({
115
125
  description: 'The console powers this person holds. **Required** — every Fleetless user has a tier; it was optional only while the org also held people with no console powers to grade, and that pool is gone.',
116
126
  }),
127
+ two_factor: z
128
+ .object({
129
+ passkeys: z.number().int().min(0).meta({ description: 'How many passkeys the person has registered.' }),
130
+ authenticator: z.boolean().meta({ description: 'Whether the person has a confirmed authenticator app.' }),
131
+ })
132
+ .meta({
133
+ description: 'The person\'s second factors, as the team list shows them: none, passkeys, an authenticator, or both. No credential travels here. An owner resets them through `DELETE /api/org/users/:id/two-factor`.',
134
+ }),
117
135
  created_at: z.iso.datetime().meta({
118
136
  description: 'When the account was created, as an ISO 8601 timestamp.',
119
137
  }),
@@ -144,28 +162,43 @@ export const sessionTokens = z.object({
144
162
  }),
145
163
  });
146
164
  export const refreshRequest = z.object({ refresh_token: z.string().min(1) });
165
+ /* ------------------------------------------- codes and second factors --
166
+ * Shared by both identity spaces, like `password` and `sessionTokens`: a
167
+ * six-digit code and a recovery code are the same thing whoever types them,
168
+ * and two spellings would be two rules for one decision.
169
+ */
147
170
  /**
148
- * Registering an org creates the org and its first owner in one step: whoever
149
- * registers the organisation is the owner.
171
+ * **A six-digit code**: the emailed sign-in code (valid ten minutes, five
172
+ * wrong attempts) and an authenticator's time-based code alike. Exactly six
173
+ * digits on the wire, leading zeros included — the portal and hosted forms
174
+ * strip spaces before they send it, the JSON API does not.
150
175
  */
151
- export const signUpRequest = z.object({
152
- org_name: z.string().min(1).max(120),
153
- email: z.email(),
154
- password,
155
- });
176
+ export const loginCode = z.string().regex(/^\d{6}$/, 'must be exactly six digits');
177
+ /** An authenticator app's code (TOTP, RFC 6238: SHA-1, six digits, thirty-second steps). The same shape as `loginCode`. */
178
+ export const totpCode = loginCode;
156
179
  /**
157
- * Registering answers with the founding **Fleetless user**, tier `owner`.
158
- *
159
- * The key is `user` rather than `member` or `owner`, and it has stayed `user`
160
- * through two identity redesigns deliberately: a renamed shape under a
161
- * renamed key would typecheck in every consumer that reads `.user.id` and mean
162
- * something subtly different, which is the quietest way for a cut like this to
163
- * go wrong.
180
+ * **A recovery code as typed**: two groups of five base32 characters,
181
+ * `xxxxx-xxxxx`, in either case — the cloud lower-cases before it compares.
182
+ * Single use.
183
+ */
184
+ export const recoveryCode = z.string().regex(/^[a-zA-Z2-7]{5}-[a-zA-Z2-7]{5}$/, 'must be two groups of five characters, xxxxx-xxxxx');
185
+ /**
186
+ * **The ten recovery codes, as issued**: lowercase, shown once. Generating a
187
+ * new set voids the old one.
164
188
  */
165
- export const signUpResponse = z.object({
166
- org,
167
- user: fleetlessUser,
168
- tokens: sessionTokens,
189
+ export const recoveryCodesList = z.array(z.string().regex(/^[a-z2-7]{5}-[a-z2-7]{5}$/)).length(10);
190
+ /**
191
+ * **An authenticator being set up**: the secret to type in, and the same
192
+ * secret as an `otpauth://` URL for a QR code. Nothing is stored as
193
+ * confirmed until a code from it is confirmed.
194
+ */
195
+ export const twoFactorSetupResponse = z.object({
196
+ secret: z.string().min(1).meta({
197
+ description: 'The shared secret, base32, for an authenticator app that cannot scan a QR code. Shown once; the cloud stores it encrypted.',
198
+ }),
199
+ otpauth_url: z.string().startsWith('otpauth://totp/').meta({
200
+ description: 'The same secret as an `otpauth://totp/` URL, to render as a QR code. It carries the secret: never log it.',
201
+ }),
169
202
  });
170
203
  /**
171
204
  * The landing page's waiting list (public site, 2026-09-04): one address,
@@ -178,24 +211,6 @@ export const signUpResponse = z.object({
178
211
  * turns an unauthenticated public route into a `500`.
179
212
  */
180
213
  export const waitlistRequest = z.object({ email: z.email().max(254) });
181
- /**
182
- * Console login. Fleetless users only, always the Fleetless password — the
183
- * console has no federated door at all, which removes the IdP-lockout
184
- * class entirely.
185
- *
186
- * This resolves a person by address alone, and a Fleetless user's email is
187
- * **globally unique**, so a bare address names at most one account and no org
188
- * context is needed to disambiguate. An org selector was never needed and
189
- * would have told an unauthenticated caller which org an address belongs to.
190
- *
191
- * **It cannot be reached by an app user**, whatever their address: the two
192
- * spaces have separate tables and separate routes, and a credential from one
193
- * never authenticates the other.
194
- */
195
- export const developerLoginRequest = z.object({
196
- email: z.email(),
197
- password: z.string().min(1),
198
- });
199
214
  /**
200
215
  * What happened to the mail, in four words instead of one.
201
216
  *
@@ -304,14 +319,18 @@ export const pendingTeamInviteListResponse = z.object({
304
319
  description: 'Pending invitations only. An accepted invitation is history, not something to revoke.',
305
320
  }),
306
321
  });
307
- /** Accepting it: the token proves the invitation, the password creates the login. */
322
+ /**
323
+ * Accepting it: the token proves the invitation, and the mailed link proves
324
+ * the address, so accepting needs no code and takes **no password** — the
325
+ * new member signs in by emailed code from then on.
326
+ */
308
327
  export const acceptTeamInviteRequest = z
309
328
  .object({
310
329
  token: z.string().min(1).meta({
311
330
  description: 'The opaque invitation token from the link. Unknown, expired and already-accepted all collapse into `410 token_spent` — telling them apart would say whether a token ever existed.',
312
331
  }),
313
- password: password.meta({
314
- description: 'The password the new Fleetless account will use. At least 12 characters.',
332
+ display_name: z.string().min(1).max(USER_DISPLAY_NAME_MAX).nullable().optional().meta({
333
+ description: 'An optional name, overriding whatever the invitation pre-filled. Absent keeps it.',
315
334
  }),
316
335
  })
317
336
  .strict();
@@ -366,17 +385,14 @@ export const tierRequiredDetails = z.object({
366
385
  actual: orgAdminTier,
367
386
  });
368
387
  /**
369
- * Changing your own password while logged in.
388
+ * An app user changing their own password while signed in
389
+ * (`POST /api/client/password/change`). Fleetless users have no password.
370
390
  *
371
391
  * `current_password` is required even though the session already proves
372
392
  * identity: it is what makes a stolen *session* insufficient to take the
373
393
  * *account*. Every other session is revoked on success; the one that made the
374
394
  * change survives, because logging someone out of the tab they just used is
375
395
  * indistinguishable from the change having failed.
376
- *
377
- * Shared with the app-user surface (`POST /api/client/password/change`): the
378
- * argument is about credentials and sessions, not about which space the person
379
- * lives in.
380
396
  */
381
397
  export const passwordChangeRequest = z.object({
382
398
  current_password: z.string().min(1).meta({
@@ -386,39 +402,6 @@ export const passwordChangeRequest = z.object({
386
402
  description: 'The replacement password. Every other session is revoked when it is accepted, while the session that made the change survives — logging somebody out of the tab they just used is indistinguishable from the change having failed.',
387
403
  }),
388
404
  });
389
- /**
390
- * Asking for a reset link, **as a Fleetless user**.
391
- *
392
- * **The response never says whether the address exists.** It is unauthenticated
393
- * and would otherwise be an account-enumeration oracle — the one place where
394
- * revealing nothing about what exists is not a preference but the whole point.
395
- * So this answers the same way for a known and an unknown address, in
396
- * status, body **and timing**, and any consumer that renders "no such account"
397
- * from it has reintroduced the oracle.
398
- *
399
- * A bare address resolves to at most one account: a Fleetless user's email is
400
- * **globally unique**, so the reset mails the one match, if any, with a token
401
- * bound to that account.
402
- *
403
- * The app-user equivalent is `clientPasswordResetRequest` in `client-auth.ts`
404
- * and carries an `app_identifier`, because on that surface a person is
405
- * identified by app *and* address. Two shapes rather than one, because the two
406
- * surfaces identify a person differently — not because the reasoning differs.
407
- */
408
- export const passwordResetRequest = z.object({
409
- email: z.email(),
410
- });
411
- /**
412
- * Using the link. The token is **single-use and expires**; spending it revokes
413
- * every session of that subject, because a forgotten password is one of the
414
- * two states where somebody else may be holding one. `token_spent` covers used
415
- * and expired alike — telling them apart tells a stranger whether a token ever
416
- * existed.
417
- */
418
- export const passwordResetConfirm = z.object({
419
- token: z.string().min(1),
420
- new_password: password,
421
- });
422
405
  /**
423
406
  * An IdP issuer URL — **an attacker-supplied string that decides where the
424
407
  * *server* connects.**
@@ -471,8 +454,8 @@ export const idpIssuer = z
471
454
  * here.** `oidcCallbackErrorCode` and `oidcCallbackError` described the page
472
455
  * `GET /mcp/oauth/idp-callback` rendered when a group's identity provider sent
473
456
  * a browser back — `jit_disabled` and `email_collision` name provisioning steps
474
- * only a group provider had. Fleetless users are password-only now, which deletes
475
- * group providers, so the flow that produced these codes cannot start; the
457
+ * only a group provider had. Fleetless users sign in by emailed code or
458
+ * passkey, with no group providers, so the flow that produced these codes cannot start; the
476
459
  * route is gone from this manifest and from the cloud.
477
460
  *
478
461
  * The per-app OIDC vocabulary is `clientOidcErrorCode` in `client-auth.ts`: a
@@ -492,7 +475,112 @@ export const idpIssuer = z
492
475
  * field required on the shape itself.
493
476
  */
494
477
  export const authMeResponse = z.object({ org, user: fleetlessUser });
495
- /** `PATCH /api/org` — rename the org. Owner only. Same bounds as signup's `org_name`. */
496
- export const patchOrgRequest = z.object({ name: z.string().min(1).max(120) }).strict();
478
+ /**
479
+ * `PATCH /api/org` — rename the org, require two-factor for its members, or
480
+ * both. Owner only. At least one field: an empty patch is a refusal rather
481
+ * than a write that changed nothing.
482
+ */
483
+ export const patchOrgRequest = z
484
+ .object({
485
+ name: z.string().min(1).max(120).optional().meta({
486
+ description: 'The organisation\'s new display name. Absent leaves it alone.',
487
+ }),
488
+ require_two_factor: z.boolean().optional().meta({
489
+ description: 'Whether every member must have a second factor. Turning it on signs nobody out: each member without one sets it up at their next sign-in. Absent leaves it alone.',
490
+ }),
491
+ })
492
+ .strict()
493
+ .refine((b) => b.name !== undefined || b.require_two_factor !== undefined, { message: 'Send name, require_two_factor, or both.' });
497
494
  /** `PATCH /api/auth/me` — the caller updates their own display name (null clears it). */
498
495
  export const patchAuthMeRequest = z.object({ display_name: z.string().min(1).max(USER_DISPLAY_NAME_MAX).nullable() }).strict();
496
+ /* ----------------------------------------- a developer's second factors --
497
+ * Settings › Profile › Security in the console. Several passkeys, at most one
498
+ * authenticator app, and ten recovery codes issued with the first factor.
499
+ * Passkeys are for Fleetless users only; app users have the authenticator.
500
+ */
501
+ /**
502
+ * **A WebAuthn JSON document, opaque here.** The `PublicKeyCredential*JSON`
503
+ * shapes of the WebAuthn Level 3 specification — creation and request options
504
+ * going out, the browser's credential coming back. The browser API and the
505
+ * server library define them; a second, hand-written copy here would be one
506
+ * that drifts.
507
+ */
508
+ export const webauthnJson = z.record(z.string(), z.unknown());
509
+ /**
510
+ * **Options for a WebAuthn ceremony**, to hand to the browser as they are.
511
+ * The relying party is `fleetless.dev`, so the auth portal and the console
512
+ * both accept the same passkey; user verification is required.
513
+ */
514
+ export const webauthnOptionsResponse = z.object({
515
+ options: webauthnJson.meta({
516
+ description: 'The `PublicKeyCredentialCreationOptionsJSON` or `PublicKeyCredentialRequestOptionsJSON` to pass to the browser. Its challenge is single-use and short-lived.',
517
+ }),
518
+ });
519
+ /** **One registered passkey**, as Settings › Profile lists it. No key material travels here. */
520
+ export const developerPasskey = z.object({
521
+ id: z.uuid().meta({ description: 'The passkey in the API, as renamed and removed through `/api/auth/passkeys/:id`.' }),
522
+ name: z.string().min(1).max(80).meta({ description: 'What the person called it, such as the device it lives on.' }),
523
+ created_at: z.iso.datetime().meta({ description: 'When it was registered.' }),
524
+ last_used_at: z.iso.datetime().nullable().meta({ description: 'When it last signed the person in or confirmed a sign-in, or `null` if never.' }),
525
+ synced: z.boolean().nullable().meta({
526
+ description: 'Whether the authenticator reported the passkey as syncable across the person\'s devices (the backup-eligible flag), or `null` when it said nothing.',
527
+ }),
528
+ });
529
+ /** **`GET /api/auth/two-factor`** — the caller's own second factors, in full. */
530
+ export const developerTwoFactor = z.object({
531
+ passkeys: z.array(developerPasskey).meta({ description: 'Every passkey the caller has registered, oldest first. Empty when none.' }),
532
+ authenticator: z
533
+ .object({ created_at: z.iso.datetime().meta({ description: 'When the authenticator was confirmed.' }) })
534
+ .nullable()
535
+ .meta({ description: 'The confirmed authenticator app, or `null` when there is none. At most one.' }),
536
+ recovery_codes_left: z.number().int().min(0).max(10).meta({
537
+ description: 'How many of the ten recovery codes are unspent. `0` while the caller has no second factor.',
538
+ }),
539
+ required_by_org: z.boolean().meta({
540
+ description: 'Whether the organisation requires a second factor. While it does, the last one cannot be removed.',
541
+ }),
542
+ });
543
+ /** **Registering a passkey**: the name, and the browser's answer to the creation options. */
544
+ export const createPasskeyRequest = z
545
+ .object({
546
+ name: z.string().min(1).max(80).meta({ description: 'What to call the passkey, such as the device it lives on.' }),
547
+ credential: webauthnJson.meta({ description: 'The browser\'s `RegistrationResponseJSON` for the options `POST /api/auth/passkeys/options` answered.' }),
548
+ })
549
+ .strict();
550
+ /**
551
+ * **The registered passkey**, and the ten recovery codes when it is the
552
+ * account's first second factor — `null` otherwise, since the existing codes
553
+ * stay valid.
554
+ */
555
+ export const createPasskeyResponse = z.object({
556
+ passkey: developerPasskey.meta({ description: 'The passkey as it is now stored.' }),
557
+ recovery_codes: recoveryCodesList.nullable().meta({
558
+ description: 'The ten recovery codes, shown once, when this passkey is the account\'s first second factor; `null` when the account already had one and its codes stay valid.',
559
+ }),
560
+ });
561
+ /** **Renaming a passkey.** The name is the only thing about one that can change. */
562
+ export const renamePasskeyRequest = z
563
+ .object({
564
+ name: z.string().min(1).max(80).meta({ description: 'The new name.' }),
565
+ })
566
+ .strict();
567
+ /** **Confirming a new authenticator** with a code it shows now. */
568
+ export const totpConfirmRequest = z
569
+ .object({
570
+ code: totpCode.meta({ description: 'A code the new authenticator shows now. It proves the secret was copied correctly before anything depends on it.' }),
571
+ })
572
+ .strict();
573
+ /**
574
+ * **The confirmed authenticator.** Ten recovery codes when it is the
575
+ * account's first second factor; `null` when it replaces an authenticator or
576
+ * joins passkeys, whose codes stay valid.
577
+ */
578
+ export const totpConfirmResponse = z.object({
579
+ recovery_codes: recoveryCodesList.nullable().meta({
580
+ description: 'The ten recovery codes, shown once, when this is the account\'s first second factor; `null` otherwise.',
581
+ }),
582
+ });
583
+ /** **A fresh set of ten recovery codes**, shown once. The previous set stops working. */
584
+ export const recoveryCodesResponse = z.object({
585
+ recovery_codes: recoveryCodesList.meta({ description: 'The ten new recovery codes, lowercase, shown once. Every earlier code is void.' }),
586
+ });
package/dist/index.d.ts CHANGED
@@ -29,25 +29,25 @@ export type { UsageMetric, OrgUsageQuery, UsageRow, OrgUsageResponse } from './r
29
29
  export type { Robot, PatchRobotResponse, CreateRobotRequest, CreateRobotResponse, RobotTokenRotateResponse, JointStatePutRequest, JointStatePutResponse, ExposureCounts, ProtocolStatusValue, RobotListItem, RobotListResponse, DatapointValue, RobotDetailResponse, ConfigDraftResponse, PutConfigDraftRequest, PublishConfigResponse, ConfigVersionsResponse, ConfigVersionResponse, IntrospectionResponse, TypesResponse, FetchTypesRequest, FetchTypesResponse, DatapointDescriptor, DatapointListResponse, RobotDetailsDoc, PutRobotDetailsResponse, PutRobotDetailsRequest, InvokeRequest, InvokeResponse, CancelRequest, ReleaseLiveQuery, ServiceCallResponse, InvokeOrServiceResponse, PublishRequest, JobResponse, RobotJobsResponse, RateLimitDetails, Exposure, ExposureListResponse, CameraDescriptor, CameraListResponse, LiveSessionResponse, SnapshotMetaResponse, } from './rest.js';
30
30
  export { clientAuth, authOk, authError, clientInvoke, clientCancel, clientPublish, commandResult, errorFrame, clientSubscribe, clientUnsubscribe, subscribeError, datapointEvent, resourceHealthEvent, resourceHealthCleared, liveSessionEndReason, liveSessionEvent, ORG_EVENT_SAMPLE_INTERVAL_MS, ORG_EVENT_ORG_CEILING_PER_SECOND, ORG_EVENT_BUFFER_SIZE, ORG_EVENT_BUFFER_IDLE_MS, ORG_EVENT_DETAIL_MAX_BYTES, orgEventKind, orgEventSeverity, orgEvent, orgEventSubscribe, orgEventUnsubscribe, orgEventReplay, orgEventDropped, } from './realtime.js';
31
31
  export type { ClientAuth, AuthOk, AuthError, ClientInvoke, ClientCancel, ClientPublish, CommandResult, ErrorFrame, ClientSubscribe, ClientUnsubscribe, SubscribeError, DatapointEvent, ResourceHealthEvent, ResourceHealthCleared, LiveSessionEndReason, LiveSessionEvent, OrgEventKind, OrgEventSeverity, OrgEvent, OrgEventSubscribe, OrgEventUnsubscribe, OrgEventReplay, OrgEventDropped, } from './realtime.js';
32
- export { password, org, patchOrgResponse, sessionTokens, refreshRequest, signUpRequest, signUpResponse, waitlistRequest, developerLoginRequest, USER_DISPLAY_NAME_MAX, orgAdminTier, fleetlessUser, fleetlessUserListResponse, createTeamInviteRequest, teamInvite, pendingTeamInvite, pendingTeamInviteListResponse, acceptTeamInviteRequest, patchFleetlessUserRequest, tierChangeRequest, mailStatus, tierRequiredDetails, passwordChangeRequest, passwordResetRequest, passwordResetConfirm, idpIssuer, authMeResponse, patchOrgRequest, patchAuthMeRequest, } from './identity.js';
33
- export type { Org, PatchOrgResponse, SessionTokens, RefreshRequest, SignUpRequest, SignUpResponse, WaitlistRequest, DeveloperLoginRequest, OrgAdminTier, FleetlessUser, FleetlessUserListResponse, CreateTeamInviteRequest, TeamInvite, PendingTeamInvite, PendingTeamInviteListResponse, AcceptTeamInviteRequest, PatchFleetlessUserRequest, TierChangeRequest, MailStatus, TierRequiredDetails, PasswordChangeRequest, PasswordResetRequest, PasswordResetConfirm, IdpIssuer, AuthMeResponse, PatchOrgRequest, PatchAuthMeRequest, } from './identity.js';
32
+ export { password, org, patchOrgResponse, sessionTokens, refreshRequest, waitlistRequest, USER_DISPLAY_NAME_MAX, orgAdminTier, fleetlessUser, fleetlessUserListResponse, createTeamInviteRequest, teamInvite, pendingTeamInvite, pendingTeamInviteListResponse, acceptTeamInviteRequest, patchFleetlessUserRequest, tierChangeRequest, mailStatus, tierRequiredDetails, passwordChangeRequest, idpIssuer, loginCode, totpCode, recoveryCode, recoveryCodesList, twoFactorSetupResponse, webauthnJson, webauthnOptionsResponse, developerPasskey, developerTwoFactor, createPasskeyRequest, createPasskeyResponse, renamePasskeyRequest, totpConfirmRequest, totpConfirmResponse, recoveryCodesResponse, authMeResponse, patchOrgRequest, patchAuthMeRequest, } from './identity.js';
33
+ export type { Org, PatchOrgResponse, SessionTokens, RefreshRequest, WaitlistRequest, OrgAdminTier, FleetlessUser, FleetlessUserListResponse, CreateTeamInviteRequest, TeamInvite, PendingTeamInvite, PendingTeamInviteListResponse, AcceptTeamInviteRequest, PatchFleetlessUserRequest, TierChangeRequest, MailStatus, TierRequiredDetails, PasswordChangeRequest, IdpIssuer, TwoFactorSetupResponse, WebauthnOptionsResponse, DeveloperPasskey, DeveloperTwoFactor, CreatePasskeyRequest, CreatePasskeyResponse, RenamePasskeyRequest, TotpConfirmRequest, TotpConfirmResponse, RecoveryCodesResponse, AuthMeResponse, PatchOrgRequest, PatchAuthMeRequest, } from './identity.js';
34
34
  export { appIdentifier, app, appListResponse, appDeletionSummary, createAppRequest, updateAppRequest, serverKeyToken, serverKey, serverKeyListResponse, createServerKeyResponse, role, roleListResponse, rolePermissions, } from './apps.js';
35
35
  export type { App, AppListResponse, AppDeletionSummary, CreateAppRequest, UpdateAppRequest, ServerKey, ServerKeyListResponse, CreateServerKeyResponse, Role, RoleListResponse, RolePermissions, } from './apps.js';
36
- export { clientLoginRequest, clientRefreshRequest, clientLogoutRequest, clientRegisterRequest, clientVerifyEmailRequest, clientResendVerificationRequest, clientPasswordResetRequest, clientPasswordResetConfirmRequest, clientAcceptInvitationRequest, CLIENT_OIDC_CALLBACK_PATH, clientProviderListQuery, clientProviderListResponse, clientOidcStartQuery, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcErrorCode, clientMcpInteraction, clientMcpInteractionDecisionResponse, mcpConsentGrant, mcpConsentGrantListResponse, clientIdentity, } from './client-auth.js';
37
- export type { ClientLoginRequest, ClientRefreshRequest, ClientLogoutRequest, ClientRegisterRequest, ClientVerifyEmailRequest, ClientResendVerificationRequest, ClientPasswordResetRequest, ClientPasswordResetConfirmRequest, ClientAcceptInvitationRequest, ClientProviderListQuery, ClientProviderListResponse, ClientOidcStartQuery, ClientOidcCallbackQuery, ClientOidcExchangeRequest, ClientOidcErrorCode, ClientMcpInteraction, ClientMcpInteractionDecisionResponse, McpConsentGrant, McpConsentGrantListResponse, ClientIdentity, } from './client-auth.js';
36
+ export { clientLoginRequest, clientRefreshRequest, clientLogoutRequest, clientRegisterRequest, clientVerifyEmailRequest, clientResendVerificationRequest, clientPasswordResetRequest, clientPasswordResetConfirmRequest, clientAcceptInvitationRequest, CLIENT_OIDC_CALLBACK_PATH, clientProviderListQuery, clientProviderListResponse, clientOidcStartQuery, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcErrorCode, clientMcpInteraction, clientMcpInteractionDecisionResponse, mcpConsentGrant, mcpConsentGrantListResponse, clientIdentity, clientLoginCodeRequest, clientLoginCodeVerifyRequest, twoFactorChallenge, clientSignInResult, clientTwoFactorVerifyRequest, clientTwoFactorSetupRequest, clientTwoFactorSetupConfirmRequest, clientTwoFactorSetupConfirmResponse, clientTwoFactorDisableRequest, } from './client-auth.js';
37
+ export type { ClientLoginRequest, ClientRefreshRequest, ClientLogoutRequest, ClientRegisterRequest, ClientVerifyEmailRequest, ClientResendVerificationRequest, ClientPasswordResetRequest, ClientPasswordResetConfirmRequest, ClientAcceptInvitationRequest, ClientProviderListQuery, ClientProviderListResponse, ClientOidcStartQuery, ClientOidcCallbackQuery, ClientOidcExchangeRequest, ClientOidcErrorCode, ClientMcpInteraction, ClientMcpInteractionDecisionResponse, McpConsentGrant, McpConsentGrantListResponse, ClientIdentity, ClientLoginCodeRequest, ClientLoginCodeVerifyRequest, TwoFactorChallenge, ClientSignInResult, ClientTwoFactorVerifyRequest, ClientTwoFactorSetupRequest, ClientTwoFactorSetupConfirmRequest, ClientTwoFactorSetupConfirmResponse, ClientTwoFactorDisableRequest, } from './client-auth.js';
38
38
  export { clientRobotListItem, clientRobotListResponse } from './client-robots.js';
39
39
  export type { ClientRobotListItem, ClientRobotListResponse } from './client-robots.js';
40
- export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig, putAppAuthRegistrationRequest, putAppAuthUrlsRequest, putAppAuthMcpRequest, mailTemplateKind, appMailTemplate, appMailTemplateListResponse, putAppMailTemplateRequest, mailTemplatePreviewRequest, mailTemplatePreviewResponse, mailTemplateProblemDetails, mailOutcome, } from './app-users.js';
41
- export type { AppUserStatus, AppUser, AppUserListResponse, CreateAppUserRequest, PatchAppUserRequest, CreateAppInvitationRequest, AppInvitation, PendingAppInvitation, AppInvitationListResponse, AppOidcProvider, AppOidcProviderListResponse, CreateAppOidcProviderRequest, PatchAppOidcProviderRequest, AppAuthConfig, PutAppAuthRegistrationRequest, PutAppAuthUrlsRequest, PutAppAuthMcpRequest, MailTemplateKind, AppMailTemplate, AppMailTemplateListResponse, PutAppMailTemplateRequest, MailTemplatePreviewRequest, MailTemplatePreviewResponse, MailTemplateProblemDetails, MailOutcome, } from './app-users.js';
40
+ export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig, appSignInMethods, appTwoFactorPolicy, appHomeUrl, hostedAccent, HOSTED_LOGO_MAX_BYTES, HOSTED_LOGO_TYPES, appHostedPages, putAppAuthRegistrationRequest, putAppAuthSignInRequest, putAppAuthUrlsRequest, putAppAuthMcpRequest, putAppAuthLookRequest, mailTemplateKind, appMailTemplate, appMailTemplateListResponse, putAppMailTemplateRequest, mailTemplatePreviewRequest, mailTemplatePreviewResponse, mailTemplateProblemDetails, mailOutcome, } from './app-users.js';
41
+ export type { AppUserStatus, AppUser, AppUserListResponse, CreateAppUserRequest, PatchAppUserRequest, CreateAppInvitationRequest, AppInvitation, PendingAppInvitation, AppInvitationListResponse, AppOidcProvider, AppOidcProviderListResponse, CreateAppOidcProviderRequest, PatchAppOidcProviderRequest, AppAuthConfig, AppSignInMethods, AppTwoFactorPolicy, AppHostedPages, PutAppAuthRegistrationRequest, PutAppAuthSignInRequest, PutAppAuthUrlsRequest, PutAppAuthMcpRequest, PutAppAuthLookRequest, MailTemplateKind, AppMailTemplate, AppMailTemplateListResponse, PutAppMailTemplateRequest, MailTemplatePreviewRequest, MailTemplatePreviewResponse, MailTemplateProblemDetails, MailOutcome, } from './app-users.js';
42
42
  export { assetKind, URDF_ASSET_NAME, asset, urdfCompleteness, assetListResponse, assetsClearResponse, missingAssetQuery, assetSyncRequest, assetSyncResponse, assetSyncState, assetSyncStatus, assetFailure, assetFailureKind, assetStoreRefusedDetails, assetSyncBusyDetails, ROBOT_ASSET_STORE_BYTES, } from './assets.js';
43
43
  export type { AssetKind, Asset, UrdfCompleteness, AssetListResponse, AssetsClearResponse, MissingAssetQuery, AssetSyncRequest, AssetSyncResponse, AssetSyncState, AssetSyncStatus, AssetFailure, AssetFailureKind, AssetStoreRefusedDetails, AssetSyncBusyDetails, } from './assets.js';
44
44
  export { auditActor, auditEvent, auditQuery, auditListResponse, AUDIT_CSV_COLUMNS, AUDIT_RETENTION_DAYS } from './audit.js';
45
45
  export type { AuditActor, AuditEvent, AuditQuery, AuditListResponse } from './audit.js';
46
46
  export { alertRowCondition, alertSeverity, alertState, datapointAlertRow, alertListResponse, orgFiringAlertsResponse, orgAlertsQuery, datapointDisplay, putDatapointDisplayRequest, } from './alerts.js';
47
47
  export type { AlertRowCondition, AlertSeverity, AlertState, DatapointAlertRow, AlertListResponse, OrgFiringAlertsResponse, OrgAlertsQuery, DatapointDisplay, PutDatapointDisplayRequest, } from './alerts.js';
48
- export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, ERROR_CODES } from './errors.js';
49
- export type { ApiError, ParameterViolation, ParameterInvalidDetails, CancelRejectedDetails, ErrorCode } from './errors.js';
48
+ export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, invalidCodeDetails, ERROR_CODES } from './errors.js';
49
+ export type { ApiError, ParameterViolation, ParameterInvalidDetails, CancelRejectedDetails, InvalidCodeDetails, ErrorCode } from './errors.js';
50
50
  export { oauthErrorCode, oauthError, oauthRedirectResponse, oauthCodeTokenRequest, oauthRefreshTokenRequest, oauthTokenRequest, oauthTokenResponse, redirectUri, codeChallengeMethod, oauthAuthorizeQuery, dynamicClientRegistrationRequest, MCP_DCR_MAX_REDIRECT_URIS, dynamicClientRegistrationResponse, authorizationServerMetadata, protectedResourceMetadata, } from './oauth.js';
51
51
  export type { OauthErrorCode, OauthError, OauthRedirectResponse, OauthCodeTokenRequest, OauthRefreshTokenRequest, OauthTokenRequest, OauthTokenResponse, RedirectUri, OauthAuthorizeQuery, DynamicClientRegistrationRequest, DynamicClientRegistrationResponse, AuthorizationServerMetadata, ProtectedResourceMetadata, } from './oauth.js';
52
- export { ROUTES, ROUTE_SECTIONS, IN_HANDLER_ROUTES } from './routes.js';
52
+ export { ROUTES, ROUTE_SECTIONS, IN_HANDLER_ROUTES, developerSignInRoutes } from './routes.js';
53
53
  export type { RouteEntry, RouteParam, RouteAudience, RouteAuth, RouteSection, RouteMethod, RouteTransport, } from './routes.js';
package/dist/index.js CHANGED
@@ -29,23 +29,27 @@ historyQuery, historySamplesResponse, historyBucketsResponse, historyResponse, o
29
29
  patchRobotRequest, renameSlugRequest, renameSlugResponse, slugUsageResponse, } from './rest.js';
30
30
  export { LATENCY_BUCKET_MS, BRIDGE_LATENCY_RETENTION_DAYS, MAX_LATENCY_BUCKETS_PER_RESPONSE, latencyBucket, robotLatencySeries, orgLatencyQuery, orgLatencyResponse, USAGE_WINDOW_MAX_DAYS, usageMetric, usageDay, orgUsageQuery, usageRow, orgUsageResponse, } from './rest.js';
31
31
  export { clientAuth, authOk, authError, clientInvoke, clientCancel, clientPublish, commandResult, errorFrame, clientSubscribe, clientUnsubscribe, subscribeError, datapointEvent, resourceHealthEvent, resourceHealthCleared, liveSessionEndReason, liveSessionEvent, ORG_EVENT_SAMPLE_INTERVAL_MS, ORG_EVENT_ORG_CEILING_PER_SECOND, ORG_EVENT_BUFFER_SIZE, ORG_EVENT_BUFFER_IDLE_MS, ORG_EVENT_DETAIL_MAX_BYTES, orgEventKind, orgEventSeverity, orgEvent, orgEventSubscribe, orgEventUnsubscribe, orgEventReplay, orgEventDropped, } from './realtime.js';
32
- export { password, org, patchOrgResponse, sessionTokens, refreshRequest, signUpRequest, signUpResponse,
32
+ export { password, org, patchOrgResponse, sessionTokens, refreshRequest,
33
33
  // 2026-09-04 — the public site (closed beta).
34
- waitlistRequest, developerLoginRequest,
34
+ waitlistRequest,
35
35
  // The two identity spaces.
36
36
  USER_DISPLAY_NAME_MAX, orgAdminTier, fleetlessUser, fleetlessUserListResponse, createTeamInviteRequest, teamInvite, pendingTeamInvite, pendingTeamInviteListResponse, acceptTeamInviteRequest, patchFleetlessUserRequest, tierChangeRequest,
37
37
  // Identity.
38
- mailStatus, tierRequiredDetails, passwordChangeRequest, passwordResetRequest, passwordResetConfirm, idpIssuer,
38
+ mailStatus, tierRequiredDetails, passwordChangeRequest, idpIssuer,
39
+ // Codes and second factors, shared by both identity spaces.
40
+ loginCode, totpCode, recoveryCode, recoveryCodesList, twoFactorSetupResponse,
41
+ // A developer's second factors.
42
+ webauthnJson, webauthnOptionsResponse, developerPasskey, developerTwoFactor, createPasskeyRequest, createPasskeyResponse, renamePasskeyRequest, totpConfirmRequest, totpConfirmResponse, recoveryCodesResponse,
39
43
  // auth/me, org and member patches.
40
44
  authMeResponse, patchOrgRequest, patchAuthMeRequest, } from './identity.js';
41
45
  export { appIdentifier, app, appListResponse, appDeletionSummary, createAppRequest, updateAppRequest, serverKeyToken, serverKey, serverKeyListResponse, createServerKeyResponse, role, roleListResponse, rolePermissions, } from './apps.js';
42
- export { clientLoginRequest, clientRefreshRequest, clientLogoutRequest, clientRegisterRequest, clientVerifyEmailRequest, clientResendVerificationRequest, clientPasswordResetRequest, clientPasswordResetConfirmRequest, clientAcceptInvitationRequest, CLIENT_OIDC_CALLBACK_PATH, clientProviderListQuery, clientProviderListResponse, clientOidcStartQuery, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcErrorCode, clientMcpInteraction, clientMcpInteractionDecisionResponse, mcpConsentGrant, mcpConsentGrantListResponse, clientIdentity, } from './client-auth.js';
46
+ export { clientLoginRequest, clientRefreshRequest, clientLogoutRequest, clientRegisterRequest, clientVerifyEmailRequest, clientResendVerificationRequest, clientPasswordResetRequest, clientPasswordResetConfirmRequest, clientAcceptInvitationRequest, CLIENT_OIDC_CALLBACK_PATH, clientProviderListQuery, clientProviderListResponse, clientOidcStartQuery, clientOidcCallbackQuery, clientOidcExchangeRequest, clientOidcErrorCode, clientMcpInteraction, clientMcpInteractionDecisionResponse, mcpConsentGrant, mcpConsentGrantListResponse, clientIdentity, clientLoginCodeRequest, clientLoginCodeVerifyRequest, twoFactorChallenge, clientSignInResult, clientTwoFactorVerifyRequest, clientTwoFactorSetupRequest, clientTwoFactorSetupConfirmRequest, clientTwoFactorSetupConfirmResponse, clientTwoFactorDisableRequest, } from './client-auth.js';
43
47
  export { clientRobotListItem, clientRobotListResponse } from './client-robots.js';
44
48
  // The per-app identity space.
45
- export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig, putAppAuthRegistrationRequest, putAppAuthUrlsRequest, putAppAuthMcpRequest, mailTemplateKind, appMailTemplate, appMailTemplateListResponse, putAppMailTemplateRequest, mailTemplatePreviewRequest, mailTemplatePreviewResponse, mailTemplateProblemDetails, mailOutcome, } from './app-users.js';
49
+ export { APP_USER_DISPLAY_NAME_MAX, APP_URL_PLACEHOLDERS, MAIL_TEMPLATE_VARIABLES, DEFAULT_MAIL_TEMPLATES, providerSlug, appUserStatus, appUser, appUserListResponse, createAppUserRequest, patchAppUserRequest, createAppInvitationRequest, appInvitation, pendingAppInvitation, appInvitationListResponse, appOidcProvider, appOidcProviderListResponse, createAppOidcProviderRequest, patchAppOidcProviderRequest, appUrlTemplate, allowedOrigin, emailDomain, appAuthConfig, appSignInMethods, appTwoFactorPolicy, appHomeUrl, hostedAccent, HOSTED_LOGO_MAX_BYTES, HOSTED_LOGO_TYPES, appHostedPages, putAppAuthRegistrationRequest, putAppAuthSignInRequest, putAppAuthUrlsRequest, putAppAuthMcpRequest, putAppAuthLookRequest, mailTemplateKind, appMailTemplate, appMailTemplateListResponse, putAppMailTemplateRequest, mailTemplatePreviewRequest, mailTemplatePreviewResponse, mailTemplateProblemDetails, mailOutcome, } from './app-users.js';
46
50
  export { assetKind, URDF_ASSET_NAME, asset, urdfCompleteness, assetListResponse, assetsClearResponse, missingAssetQuery, assetSyncRequest, assetSyncResponse, assetSyncState, assetSyncStatus, assetFailure, assetFailureKind, assetStoreRefusedDetails, assetSyncBusyDetails, ROBOT_ASSET_STORE_BYTES, } from './assets.js';
47
51
  export { auditActor, auditEvent, auditQuery, auditListResponse, AUDIT_CSV_COLUMNS, AUDIT_RETENTION_DAYS } from './audit.js';
48
52
  export { alertRowCondition, alertSeverity, alertState, datapointAlertRow, alertListResponse, orgFiringAlertsResponse, orgAlertsQuery, datapointDisplay, putDatapointDisplayRequest, } from './alerts.js';
49
- export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, ERROR_CODES } from './errors.js';
53
+ export { apiError, parameterViolation, parameterInvalidDetails, cancelRejectedDetails, invalidCodeDetails, ERROR_CODES } from './errors.js';
50
54
  export { oauthErrorCode, oauthError, oauthRedirectResponse, oauthCodeTokenRequest, oauthRefreshTokenRequest, oauthTokenRequest, oauthTokenResponse, redirectUri, codeChallengeMethod, oauthAuthorizeQuery, dynamicClientRegistrationRequest, MCP_DCR_MAX_REDIRECT_URIS, dynamicClientRegistrationResponse, authorizationServerMetadata, protectedResourceMetadata, } from './oauth.js';
51
- export { ROUTES, ROUTE_SECTIONS, IN_HANDLER_ROUTES } from './routes.js';
55
+ export { ROUTES, ROUTE_SECTIONS, IN_HANDLER_ROUTES, developerSignInRoutes } from './routes.js';
@@ -559,9 +559,9 @@ export declare const cloudConfig: z.ZodObject<{
559
559
  }, z.core.$strict>>>;
560
560
  low_bandwidth: z.ZodOptional<z.ZodObject<{
561
561
  mode: z.ZodOptional<z.ZodEnum<{
562
+ off: "off";
562
563
  auto: "auto";
563
564
  on: "on";
564
- off: "off";
565
565
  }>>;
566
566
  enter_lag_ms: z.ZodOptional<z.ZodNumber>;
567
567
  enter_after_s: z.ZodOptional<z.ZodNumber>;
@@ -41,6 +41,7 @@ export declare const authOk: z.ZodObject<{
41
41
  app_id: z.ZodNullable<z.ZodUUID>;
42
42
  role_id: z.ZodNullable<z.ZodUUID>;
43
43
  email: z.ZodNullable<z.ZodEmail>;
44
+ two_factor_enabled: z.ZodNullable<z.ZodBoolean>;
44
45
  }, z.core.$strip>;
45
46
  }, z.core.$strip>;
46
47
  export type AuthOk = z.infer<typeof authOk>;
package/dist/rest.d.ts CHANGED
@@ -444,9 +444,9 @@ export declare const configDraftResponse: z.ZodObject<{
444
444
  }, z.core.$strict>>>;
445
445
  low_bandwidth: z.ZodOptional<z.ZodObject<{
446
446
  mode: z.ZodOptional<z.ZodEnum<{
447
+ off: "off";
447
448
  auto: "auto";
448
449
  on: "on";
449
- off: "off";
450
450
  }>>;
451
451
  enter_lag_ms: z.ZodOptional<z.ZodNumber>;
452
452
  enter_after_s: z.ZodOptional<z.ZodNumber>;
@@ -698,9 +698,9 @@ export declare const configVersionResponse: z.ZodObject<{
698
698
  }, z.core.$strict>>>;
699
699
  low_bandwidth: z.ZodOptional<z.ZodObject<{
700
700
  mode: z.ZodOptional<z.ZodEnum<{
701
+ off: "off";
701
702
  auto: "auto";
702
703
  on: "on";
703
- off: "off";
704
704
  }>>;
705
705
  enter_lag_ms: z.ZodOptional<z.ZodNumber>;
706
706
  enter_after_s: z.ZodOptional<z.ZodNumber>;
package/dist/rest.js CHANGED
@@ -567,23 +567,17 @@ export const jobResponse = z.object({
567
567
  * There are no compatibility aliases, because an alias here is how a deleted
568
568
  * model survives in production while the contract says otherwise.
569
569
  *
570
- * **`POST /api/auth/password/reset` answers `202` for every well-formed
571
- * address**, known or not. It is the one route where saying nothing about
572
- * whether an account exists is not a preference but the entire point: any
573
- * status, body or timing difference between the two cases is an
570
+ * **Every route that mails a code or a link answers the same for a known and
571
+ * an unknown address** — the portal's identify step, `POST
572
+ * /api/client/login/code`, `POST /api/client/password/reset`. Saying nothing
573
+ * about whether an account exists is not a preference there but the entire
574
+ * point: any status, body or timing difference between the two cases is an
574
575
  * account-enumeration oracle. Note *timing* — a route that only sends mail for
575
- * a real address must not become measurably faster for an unknown one. Email is
576
- * **globally unique**, so a bare address names at most one account and the
577
- * route mails the one match, if any. See `passwordResetRequest`.
578
- *
579
- * **Both surfaces get the password routes, mirrored.** An end user and a
580
- * console user each need a way to change and to reset a password.
581
- * `passwordChangeRequest` is shared because the operation is identical; the
582
- * **prefix** is what says
583
- * which session is being spent, exactly as it does for `login`. The two *reset*
584
- * requests are separate shapes rather than one, because the surfaces identify a
585
- * person differently: a Fleetless user by a globally unique address, an app
586
- * user by app **and** address.
576
+ * a real address must not become measurably faster for an unknown one.
577
+ *
578
+ * **Only app users hold a password.** Fleetless users sign in by emailed code
579
+ * or passkey, so the password change and reset routes are the app user's
580
+ * alone, under `/api/client`.
587
581
  *
588
582
  * **A password change answers with fresh `sessionTokens`, not `204`.** The
589
583
  * promise is that the session which made the change survives while every other
@@ -611,20 +605,15 @@ export const jobResponse = z.object({
611
605
  *
612
606
  * | purpose | URL |
613
607
  * |---|---|
614
- * | password reset, Fleetless user | `{portal}/reset-password/{token}` |
615
608
  * | team invitation | `{portal}/accept-invite/{token}` |
616
609
  *
617
- * **An app user's links are not in this table, and cannot be** (2026-09-05,
618
- * Fleetless renders an app user no page, so there is no `{portal}` path
619
- * to name: the link points into the **developer's own app**, at the template
620
- * they configured (`appAuthConfig.invite_url`, `verify_url`, `reset_url`), with
621
- * the token substituted for `{token}`. That is why those fields are validated
622
- * as templates rather than as URLs, and why an app with none configured is
623
- * refused a `send_mail` instead of being mailed a link to nowhere.
624
- *
625
- * The paragraph this replaces said an app-user reset *"is a feature to design,
626
- * not a row to restore"*. It was designed; the answer was that the row belongs
627
- * to the developer and not to this table.
610
+ * **An app user's links point into the developer's own app** where it has
611
+ * configured one — the template in `appAuthConfig.invite_url`, `verify_url` or
612
+ * `reset_url`, with the token substituted for `{token}`. That is why those
613
+ * fields are validated as templates rather than as URLs. Where the app has
614
+ * configured none, the link points at the Fleetless-hosted page the cloud
615
+ * reports in `appAuthConfig.hosted_pages` (`{portal}/app/<identifier>/…`), so
616
+ * no mail is refused for a missing URL.
628
617
  *
629
618
  * The strings themselves live server-side, read by the
630
619
  * route that serves each page AND by the builder that mails it — one constant,