@fleetless/contracts 5.3.0-next.1 → 6.0.0-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +108 -33
- package/artifacts/openapi.json +2053 -784
- package/artifacts/routes.json +1680 -323
- package/artifacts/schema/accept-team-invite-request.schema.json +13 -7
- package/artifacts/schema/app-auth-config.schema.json +108 -4
- package/artifacts/schema/app-hosted-pages.schema.json +33 -0
- package/artifacts/schema/app-invitation.schema.json +5 -12
- package/artifacts/schema/app-mail-template-list-response.schema.json +4 -3
- package/artifacts/schema/app-mail-template.schema.json +3 -2
- package/artifacts/schema/app-sign-in-methods.schema.json +19 -0
- package/artifacts/schema/app-user-list-response.schema.json +36 -0
- package/artifacts/schema/app-user.schema.json +36 -0
- package/artifacts/schema/audit-query.schema.json +0 -6
- package/artifacts/schema/auth-me-response.schema.json +27 -0
- package/artifacts/schema/auth-ok.schema.json +13 -1
- package/artifacts/schema/client-accept-invitation-request.schema.json +3 -4
- package/artifacts/schema/client-identity.schema.json +13 -1
- package/artifacts/schema/client-login-code-request.schema.json +24 -0
- package/artifacts/schema/client-login-code-verify-request.schema.json +30 -0
- package/artifacts/schema/client-provider-list-response.schema.json +22 -2
- package/artifacts/schema/client-register-request.schema.json +3 -4
- package/artifacts/schema/client-sign-in-result.schema.json +55 -0
- package/artifacts/schema/client-two-factor-disable-request.schema.json +15 -0
- package/artifacts/schema/client-two-factor-setup-confirm-request.schema.json +20 -0
- package/artifacts/schema/client-two-factor-setup-confirm-response.schema.json +49 -0
- package/artifacts/schema/client-two-factor-setup-request.schema.json +12 -0
- package/artifacts/schema/client-two-factor-verify-request.schema.json +25 -0
- package/artifacts/schema/create-app-invitation-request.schema.json +1 -1
- package/artifacts/schema/create-passkey-request.schema.json +25 -0
- package/artifacts/schema/create-passkey-response.schema.json +84 -0
- package/artifacts/schema/developer-passkey.schema.json +56 -0
- package/artifacts/schema/developer-two-factor.schema.json +105 -0
- package/artifacts/schema/fleetless-user-list-response.schema.json +22 -0
- package/artifacts/schema/fleetless-user.schema.json +22 -0
- package/artifacts/schema/invalid-code-details.schema.json +16 -0
- package/artifacts/schema/job-actor.schema.json +1 -15
- package/artifacts/schema/job-run-list-response.schema.json +1 -15
- package/artifacts/schema/job-run.schema.json +1 -15
- package/artifacts/schema/org.schema.json +5 -0
- package/artifacts/schema/patch-org-request.schema.json +5 -3
- package/artifacts/schema/patch-org-response.schema.json +5 -0
- package/artifacts/schema/put-app-auth-look-request.schema.json +22 -0
- package/artifacts/schema/put-app-auth-mcp-request.schema.json +1 -14
- package/artifacts/schema/put-app-auth-sign-in-request.schema.json +39 -0
- package/artifacts/schema/put-app-auth-urls-request.schema.json +31 -4
- package/artifacts/schema/recovery-codes-response.schema.json +20 -0
- package/artifacts/schema/{role-rename-request.schema.json → rename-passkey-request.schema.json} +2 -2
- package/artifacts/schema/role-list-response.schema.json +1 -1
- package/artifacts/schema/role.schema.json +1 -1
- package/artifacts/schema/totp-confirm-request.schema.json +15 -0
- package/artifacts/schema/totp-confirm-response.schema.json +27 -0
- package/artifacts/schema/two-factor-challenge.schema.json +24 -0
- package/artifacts/schema/two-factor-setup-response.schema.json +21 -0
- package/artifacts/schema/webauthn-options-response.schema.json +18 -0
- package/dist/app-users.d.ts +154 -35
- package/dist/app-users.js +177 -46
- package/dist/apps.d.ts +2 -38
- package/dist/apps.js +3 -46
- package/dist/audit.d.ts +0 -1
- package/dist/audit.js +0 -13
- package/dist/client-auth.d.ts +133 -24
- package/dist/client-auth.js +139 -28
- package/dist/config.d.ts +2 -2
- package/dist/errors.d.ts +10 -1
- package/dist/errors.js +37 -38
- package/dist/identity.d.ts +175 -128
- package/dist/identity.js +192 -106
- package/dist/index.d.ts +11 -13
- package/dist/index.js +12 -10
- package/dist/jobs.d.ts +0 -3
- package/dist/jobs.js +0 -16
- package/dist/protocol.d.ts +1 -1
- package/dist/realtime.d.ts +1 -0
- package/dist/rest.d.ts +2 -2
- package/dist/rest.js +17 -28
- package/dist/routes.d.ts +26 -0
- package/dist/routes.js +528 -234
- package/package.json +1 -1
- package/artifacts/schema/developer-login-request.schema.json +0 -19
- package/artifacts/schema/feedback-request.schema.json +0 -34
- package/artifacts/schema/feedback-response.schema.json +0 -27
- package/artifacts/schema/password-reset-confirm.schema.json +0 -19
- package/artifacts/schema/password-reset-request.schema.json +0 -14
- package/artifacts/schema/role-delete-query.schema.json +0 -13
- package/artifacts/schema/role-in-use-details.schema.json +0 -28
- package/artifacts/schema/sign-up-request.schema.json +0 -26
- package/artifacts/schema/sign-up-response.schema.json +0 -127
- package/dist/feedback.d.ts +0 -42
- package/dist/feedback.js +0 -35
package/artifacts/openapi.json
CHANGED
|
@@ -69,50 +69,6 @@
|
|
|
69
69
|
}
|
|
70
70
|
],
|
|
71
71
|
"paths": {
|
|
72
|
-
"/api/auth/signup": {
|
|
73
|
-
"post": {
|
|
74
|
-
"operationId": "post_api_auth_signup",
|
|
75
|
-
"summary": "Creates an org and its founding Owner, and answers a developer session.",
|
|
76
|
-
"tags": [
|
|
77
|
-
"developer-auth"
|
|
78
|
-
],
|
|
79
|
-
"security": [],
|
|
80
|
-
"parameters": [],
|
|
81
|
-
"responses": {
|
|
82
|
-
"201": {
|
|
83
|
-
"description": "Success.",
|
|
84
|
-
"content": {
|
|
85
|
-
"application/json": {
|
|
86
|
-
"schema": {
|
|
87
|
-
"$ref": "#/components/schemas/sign-up-response"
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
"default": {
|
|
93
|
-
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `signup_closed`, `validation_error`, `email_taken`.",
|
|
94
|
-
"content": {
|
|
95
|
-
"application/json": {
|
|
96
|
-
"schema": {
|
|
97
|
-
"$ref": "#/components/schemas/api-error"
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
"description": "While the deployment runs in closed beta this answers `403 signup_closed` before it looks at the body — there is nothing for a validation message, or an `email_taken` answer, to be right about when nothing will be created. Email is globally unique, so an address already registered in any org is refused.",
|
|
104
|
-
"requestBody": {
|
|
105
|
-
"required": true,
|
|
106
|
-
"content": {
|
|
107
|
-
"application/json": {
|
|
108
|
-
"schema": {
|
|
109
|
-
"$ref": "#/components/schemas/sign-up-request"
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
72
|
"/api/auth/refresh": {
|
|
117
73
|
"post": {
|
|
118
74
|
"operationId": "post_api_auth_refresh",
|
|
@@ -144,7 +100,7 @@
|
|
|
144
100
|
}
|
|
145
101
|
}
|
|
146
102
|
},
|
|
147
|
-
"description": "The whole family is re-checked here, not just the token: an account that has been removed from the org, or whose `token_version` was bumped by
|
|
103
|
+
"description": "The whole family is re-checked here, not just the token: an account that has been removed from the org, or whose `token_version` was bumped by an owner's two-factor reset, cannot mint a fresh console token and answers `token_revoked`. Refusing that only on the other routes would leave a session that is dead everywhere but here.",
|
|
148
104
|
"requestBody": {
|
|
149
105
|
"required": true,
|
|
150
106
|
"content": {
|
|
@@ -277,10 +233,48 @@
|
|
|
277
233
|
}
|
|
278
234
|
}
|
|
279
235
|
},
|
|
280
|
-
"/api/auth/
|
|
236
|
+
"/api/auth/two-factor": {
|
|
237
|
+
"get": {
|
|
238
|
+
"operationId": "get_api_auth_two_factor",
|
|
239
|
+
"summary": "Answers the calling developer's passkeys, authenticator, recovery codes left and the org's policy.",
|
|
240
|
+
"tags": [
|
|
241
|
+
"developer-auth"
|
|
242
|
+
],
|
|
243
|
+
"security": [
|
|
244
|
+
{
|
|
245
|
+
"developerSession": []
|
|
246
|
+
}
|
|
247
|
+
],
|
|
248
|
+
"parameters": [],
|
|
249
|
+
"responses": {
|
|
250
|
+
"200": {
|
|
251
|
+
"description": "Success.",
|
|
252
|
+
"content": {
|
|
253
|
+
"application/json": {
|
|
254
|
+
"schema": {
|
|
255
|
+
"$ref": "#/components/schemas/developer-two-factor"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"default": {
|
|
261
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`.",
|
|
262
|
+
"content": {
|
|
263
|
+
"application/json": {
|
|
264
|
+
"schema": {
|
|
265
|
+
"$ref": "#/components/schemas/api-error"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
"description": "What Settings › Profile › Security draws. No key material, secret or code travels here — the passkeys are names and dates, the authenticator is a date, the recovery codes are a count."
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
"/api/auth/passkeys/options": {
|
|
281
275
|
"post": {
|
|
282
|
-
"operationId": "
|
|
283
|
-
"summary": "
|
|
276
|
+
"operationId": "post_api_auth_passkeys_options",
|
|
277
|
+
"summary": "Answers the WebAuthn creation options for registering a passkey.",
|
|
284
278
|
"tags": [
|
|
285
279
|
"developer-auth"
|
|
286
280
|
],
|
|
@@ -296,13 +290,51 @@
|
|
|
296
290
|
"content": {
|
|
297
291
|
"application/json": {
|
|
298
292
|
"schema": {
|
|
299
|
-
"$ref": "#/components/schemas/
|
|
293
|
+
"$ref": "#/components/schemas/webauthn-options-response"
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
"default": {
|
|
299
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`.",
|
|
300
|
+
"content": {
|
|
301
|
+
"application/json": {
|
|
302
|
+
"schema": {
|
|
303
|
+
"$ref": "#/components/schemas/api-error"
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
"description": "Hand `options` to the browser's WebAuthn API as it is. The relying party is `fleetless.dev`, so the passkey works on the auth portal and in the console alike; user verification is required and the credential is discoverable, so it can sign the person in without an address. The passkeys the caller already has are excluded. The challenge is single-use and expires with the ceremony."
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
"/api/auth/passkeys": {
|
|
313
|
+
"post": {
|
|
314
|
+
"operationId": "post_api_auth_passkeys",
|
|
315
|
+
"summary": "Registers a passkey from the browser's answer to the creation options.",
|
|
316
|
+
"tags": [
|
|
317
|
+
"developer-auth"
|
|
318
|
+
],
|
|
319
|
+
"security": [
|
|
320
|
+
{
|
|
321
|
+
"developerSession": []
|
|
322
|
+
}
|
|
323
|
+
],
|
|
324
|
+
"parameters": [],
|
|
325
|
+
"responses": {
|
|
326
|
+
"201": {
|
|
327
|
+
"description": "Success.",
|
|
328
|
+
"content": {
|
|
329
|
+
"application/json": {
|
|
330
|
+
"schema": {
|
|
331
|
+
"$ref": "#/components/schemas/create-passkey-response"
|
|
300
332
|
}
|
|
301
333
|
}
|
|
302
334
|
}
|
|
303
335
|
},
|
|
304
336
|
"default": {
|
|
305
|
-
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `validation_error
|
|
337
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `validation_error`.",
|
|
306
338
|
"content": {
|
|
307
339
|
"application/json": {
|
|
308
340
|
"schema": {
|
|
@@ -312,34 +344,55 @@
|
|
|
312
344
|
}
|
|
313
345
|
}
|
|
314
346
|
},
|
|
315
|
-
"description": "
|
|
347
|
+
"description": "A ceremony that does not verify — a wrong challenge, origin or relying party, no user verification — is `400 validation_error` naming `credential`. When this is the account's first second factor, ten recovery codes are issued and answered once; otherwise `recovery_codes` is `null` and the existing ones stay valid. Audited as `developer.two_factor_added` with `details.kind` `passkey`.",
|
|
316
348
|
"requestBody": {
|
|
317
349
|
"required": true,
|
|
318
350
|
"content": {
|
|
319
351
|
"application/json": {
|
|
320
352
|
"schema": {
|
|
321
|
-
"$ref": "#/components/schemas/
|
|
353
|
+
"$ref": "#/components/schemas/create-passkey-request"
|
|
322
354
|
}
|
|
323
355
|
}
|
|
324
356
|
}
|
|
325
357
|
}
|
|
326
358
|
}
|
|
327
359
|
},
|
|
328
|
-
"/api/auth/
|
|
329
|
-
"
|
|
330
|
-
"operationId": "
|
|
331
|
-
"summary": "
|
|
360
|
+
"/api/auth/passkeys/{id}": {
|
|
361
|
+
"patch": {
|
|
362
|
+
"operationId": "patch_api_auth_passkeys_id",
|
|
363
|
+
"summary": "Renames one of the caller's passkeys.",
|
|
332
364
|
"tags": [
|
|
333
365
|
"developer-auth"
|
|
334
366
|
],
|
|
335
|
-
"security": [
|
|
336
|
-
|
|
367
|
+
"security": [
|
|
368
|
+
{
|
|
369
|
+
"developerSession": []
|
|
370
|
+
}
|
|
371
|
+
],
|
|
372
|
+
"parameters": [
|
|
373
|
+
{
|
|
374
|
+
"name": "id",
|
|
375
|
+
"in": "path",
|
|
376
|
+
"required": true,
|
|
377
|
+
"description": "The passkey's uuid, as listed by `GET /api/auth/two-factor`; another person's passkey answers `404`.",
|
|
378
|
+
"schema": {
|
|
379
|
+
"type": "string"
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
],
|
|
337
383
|
"responses": {
|
|
338
|
-
"
|
|
339
|
-
"description": "Success."
|
|
384
|
+
"200": {
|
|
385
|
+
"description": "Success.",
|
|
386
|
+
"content": {
|
|
387
|
+
"application/json": {
|
|
388
|
+
"schema": {
|
|
389
|
+
"$ref": "#/components/schemas/developer-passkey"
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
340
393
|
},
|
|
341
394
|
"default": {
|
|
342
|
-
"description": "An error envelope. Codes this route is known to answer: `
|
|
395
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `validation_error`, `not_found`.",
|
|
343
396
|
"content": {
|
|
344
397
|
"application/json": {
|
|
345
398
|
"schema": {
|
|
@@ -349,34 +402,112 @@
|
|
|
349
402
|
}
|
|
350
403
|
}
|
|
351
404
|
},
|
|
352
|
-
"description": "Status, body and timing are identical for a known and an unknown address — any difference is an account-enumeration oracle, which is why the unknown branch still pays a real SMTP round trip to a discard address. An account provisioned through OIDC has no Fleetless password and is mailed nothing. A browser form post gets a `303` to the \"check your mail\" card instead of this `202`.",
|
|
353
405
|
"requestBody": {
|
|
354
406
|
"required": true,
|
|
355
407
|
"content": {
|
|
356
408
|
"application/json": {
|
|
357
409
|
"schema": {
|
|
358
|
-
"$ref": "#/components/schemas/
|
|
410
|
+
"$ref": "#/components/schemas/rename-passkey-request"
|
|
359
411
|
}
|
|
360
412
|
}
|
|
361
413
|
}
|
|
362
414
|
}
|
|
415
|
+
},
|
|
416
|
+
"delete": {
|
|
417
|
+
"operationId": "delete_api_auth_passkeys_id",
|
|
418
|
+
"summary": "Removes one of the caller's passkeys.",
|
|
419
|
+
"tags": [
|
|
420
|
+
"developer-auth"
|
|
421
|
+
],
|
|
422
|
+
"security": [
|
|
423
|
+
{
|
|
424
|
+
"developerSession": []
|
|
425
|
+
}
|
|
426
|
+
],
|
|
427
|
+
"parameters": [
|
|
428
|
+
{
|
|
429
|
+
"name": "id",
|
|
430
|
+
"in": "path",
|
|
431
|
+
"required": true,
|
|
432
|
+
"description": "The passkey's uuid, as listed by `GET /api/auth/two-factor`; another person's passkey answers `404`.",
|
|
433
|
+
"schema": {
|
|
434
|
+
"type": "string"
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
],
|
|
438
|
+
"responses": {
|
|
439
|
+
"204": {
|
|
440
|
+
"description": "Success."
|
|
441
|
+
},
|
|
442
|
+
"default": {
|
|
443
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`, `target_state_conflict`.",
|
|
444
|
+
"content": {
|
|
445
|
+
"application/json": {
|
|
446
|
+
"schema": {
|
|
447
|
+
"$ref": "#/components/schemas/api-error"
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
"description": "`409 target_state_conflict` names `two_factor` with rule `required_by_org` when this is the caller's last second factor and the organisation requires one. Removing the last one otherwise also voids the recovery codes. Audited as `developer.two_factor_removed` with `details.kind` `passkey`."
|
|
363
454
|
}
|
|
364
455
|
},
|
|
365
|
-
"/api/auth/
|
|
456
|
+
"/api/auth/totp": {
|
|
366
457
|
"post": {
|
|
367
|
-
"operationId": "
|
|
368
|
-
"summary": "
|
|
458
|
+
"operationId": "post_api_auth_totp",
|
|
459
|
+
"summary": "Starts an authenticator setup and answers its secret and otpauth URL.",
|
|
369
460
|
"tags": [
|
|
370
461
|
"developer-auth"
|
|
371
462
|
],
|
|
372
|
-
"security": [
|
|
463
|
+
"security": [
|
|
464
|
+
{
|
|
465
|
+
"developerSession": []
|
|
466
|
+
}
|
|
467
|
+
],
|
|
468
|
+
"parameters": [],
|
|
469
|
+
"responses": {
|
|
470
|
+
"200": {
|
|
471
|
+
"description": "Success.",
|
|
472
|
+
"content": {
|
|
473
|
+
"application/json": {
|
|
474
|
+
"schema": {
|
|
475
|
+
"$ref": "#/components/schemas/two-factor-setup-response"
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
"default": {
|
|
481
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`.",
|
|
482
|
+
"content": {
|
|
483
|
+
"application/json": {
|
|
484
|
+
"schema": {
|
|
485
|
+
"$ref": "#/components/schemas/api-error"
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
"description": "The secret is pending until `POST /api/auth/totp/confirm` accepts a code from it; a second call replaces a pending secret. A developer who already has an authenticator keeps it until the new one is confirmed, which is how `Replace…` works."
|
|
492
|
+
},
|
|
493
|
+
"delete": {
|
|
494
|
+
"operationId": "delete_api_auth_totp",
|
|
495
|
+
"summary": "Removes the caller's authenticator app.",
|
|
496
|
+
"tags": [
|
|
497
|
+
"developer-auth"
|
|
498
|
+
],
|
|
499
|
+
"security": [
|
|
500
|
+
{
|
|
501
|
+
"developerSession": []
|
|
502
|
+
}
|
|
503
|
+
],
|
|
373
504
|
"parameters": [],
|
|
374
505
|
"responses": {
|
|
375
506
|
"204": {
|
|
376
507
|
"description": "Success."
|
|
377
508
|
},
|
|
378
509
|
"default": {
|
|
379
|
-
"description": "An error envelope. Codes this route is known to answer: `
|
|
510
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `not_found`, `target_state_conflict`.",
|
|
380
511
|
"content": {
|
|
381
512
|
"application/json": {
|
|
382
513
|
"schema": {
|
|
@@ -386,19 +517,95 @@
|
|
|
386
517
|
}
|
|
387
518
|
}
|
|
388
519
|
},
|
|
389
|
-
"description": "
|
|
520
|
+
"description": "`404 not_found` when there is no authenticator. `409 target_state_conflict` names `two_factor` with rule `required_by_org` when it is the caller's last second factor and the organisation requires one. Audited as `developer.two_factor_removed` with `details.kind` `authenticator`."
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
"/api/auth/totp/confirm": {
|
|
524
|
+
"post": {
|
|
525
|
+
"operationId": "post_api_auth_totp_confirm",
|
|
526
|
+
"summary": "Confirms the pending authenticator with a code it shows now.",
|
|
527
|
+
"tags": [
|
|
528
|
+
"developer-auth"
|
|
529
|
+
],
|
|
530
|
+
"security": [
|
|
531
|
+
{
|
|
532
|
+
"developerSession": []
|
|
533
|
+
}
|
|
534
|
+
],
|
|
535
|
+
"parameters": [],
|
|
536
|
+
"responses": {
|
|
537
|
+
"200": {
|
|
538
|
+
"description": "Success.",
|
|
539
|
+
"content": {
|
|
540
|
+
"application/json": {
|
|
541
|
+
"schema": {
|
|
542
|
+
"$ref": "#/components/schemas/totp-confirm-response"
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
"default": {
|
|
548
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `rate_limited`, `validation_error`, `invalid_code`, `token_spent`.",
|
|
549
|
+
"content": {
|
|
550
|
+
"application/json": {
|
|
551
|
+
"schema": {
|
|
552
|
+
"$ref": "#/components/schemas/api-error"
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
"description": "A code that does not match the pending secret is `400 invalid_code`; no pending setup is `410 token_spent`. On success the new authenticator replaces any earlier one. Ten recovery codes are answered when it is the account's first second factor, otherwise `null`. Audited as `developer.two_factor_added` with `details.kind` `authenticator`.",
|
|
390
559
|
"requestBody": {
|
|
391
560
|
"required": true,
|
|
392
561
|
"content": {
|
|
393
562
|
"application/json": {
|
|
394
563
|
"schema": {
|
|
395
|
-
"$ref": "#/components/schemas/
|
|
564
|
+
"$ref": "#/components/schemas/totp-confirm-request"
|
|
396
565
|
}
|
|
397
566
|
}
|
|
398
567
|
}
|
|
399
568
|
}
|
|
400
569
|
}
|
|
401
570
|
},
|
|
571
|
+
"/api/auth/recovery-codes": {
|
|
572
|
+
"post": {
|
|
573
|
+
"operationId": "post_api_auth_recovery_codes",
|
|
574
|
+
"summary": "Issues ten new recovery codes and voids the old ones.",
|
|
575
|
+
"tags": [
|
|
576
|
+
"developer-auth"
|
|
577
|
+
],
|
|
578
|
+
"security": [
|
|
579
|
+
{
|
|
580
|
+
"developerSession": []
|
|
581
|
+
}
|
|
582
|
+
],
|
|
583
|
+
"parameters": [],
|
|
584
|
+
"responses": {
|
|
585
|
+
"200": {
|
|
586
|
+
"description": "Success.",
|
|
587
|
+
"content": {
|
|
588
|
+
"application/json": {
|
|
589
|
+
"schema": {
|
|
590
|
+
"$ref": "#/components/schemas/recovery-codes-response"
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
},
|
|
595
|
+
"default": {
|
|
596
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `target_state_conflict`.",
|
|
597
|
+
"content": {
|
|
598
|
+
"application/json": {
|
|
599
|
+
"schema": {
|
|
600
|
+
"$ref": "#/components/schemas/api-error"
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
"description": "The codes are shown this once. `409 target_state_conflict` names `two_factor` with rule `off` when the caller has no second factor: recovery codes only stand in for one. Audited as `developer.recovery_codes_generated`."
|
|
607
|
+
}
|
|
608
|
+
},
|
|
402
609
|
"/api/waitlist": {
|
|
403
610
|
"post": {
|
|
404
611
|
"operationId": "post_api_waitlist",
|
|
@@ -525,17 +732,6 @@
|
|
|
525
732
|
"maxLength": 40
|
|
526
733
|
}
|
|
527
734
|
},
|
|
528
|
-
{
|
|
529
|
-
"name": "target_id",
|
|
530
|
-
"in": "query",
|
|
531
|
-
"required": false,
|
|
532
|
-
"schema": {
|
|
533
|
-
"description": "Events whose target is this id; for a robot also the events that name it in `details.robot_id` (`action.invoked`, `service.called`, …), so a robot's events include what was started on it.",
|
|
534
|
-
"type": "string",
|
|
535
|
-
"minLength": 1,
|
|
536
|
-
"maxLength": 200
|
|
537
|
-
}
|
|
538
|
-
},
|
|
539
735
|
{
|
|
540
736
|
"name": "from_ms",
|
|
541
737
|
"in": "query",
|
|
@@ -687,17 +883,6 @@
|
|
|
687
883
|
"maxLength": 40
|
|
688
884
|
}
|
|
689
885
|
},
|
|
690
|
-
{
|
|
691
|
-
"name": "target_id",
|
|
692
|
-
"in": "query",
|
|
693
|
-
"required": false,
|
|
694
|
-
"schema": {
|
|
695
|
-
"description": "Events whose target is this id; for a robot also the events that name it in `details.robot_id` (`action.invoked`, `service.called`, …), so a robot's events include what was started on it.",
|
|
696
|
-
"type": "string",
|
|
697
|
-
"minLength": 1,
|
|
698
|
-
"maxLength": 200
|
|
699
|
-
}
|
|
700
|
-
},
|
|
701
886
|
{
|
|
702
887
|
"name": "from_ms",
|
|
703
888
|
"in": "query",
|
|
@@ -1080,7 +1265,7 @@
|
|
|
1080
1265
|
}
|
|
1081
1266
|
}
|
|
1082
1267
|
},
|
|
1083
|
-
"description": "The body is `{ \"name\": string }` — non-empty, trimmed, at most
|
|
1268
|
+
"description": "The body is `{ \"name\": string }` — non-empty, trimmed, at most 120 characters — and is deliberately not a contract shape: contracts define the `role` this answers with, not this one trivial request. **The answer is a bare `role`, not an envelope**, unlike the listing beside it."
|
|
1084
1269
|
},
|
|
1085
1270
|
"get": {
|
|
1086
1271
|
"operationId": "get_api_apps_id_roles",
|
|
@@ -1307,132 +1492,6 @@
|
|
|
1307
1492
|
"description": "Built by the same builder the MCP server's own `robot_describe` uses, so the two cannot drift. It answers what the role *would* be offered and consults nothing about any user's actual MCP entitlement. A robot the role grants nothing on still appears, with an empty `exposures` — dropping it would read as \"not attached\", which is a different fact."
|
|
1308
1493
|
}
|
|
1309
1494
|
},
|
|
1310
|
-
"/api/apps/{id}/roles/{roleId}": {
|
|
1311
|
-
"patch": {
|
|
1312
|
-
"operationId": "patch_api_apps_id_roles_roleId",
|
|
1313
|
-
"summary": "Renames a role; its users keep it.",
|
|
1314
|
-
"tags": [
|
|
1315
|
-
"apps"
|
|
1316
|
-
],
|
|
1317
|
-
"security": [
|
|
1318
|
-
{
|
|
1319
|
-
"developerSession": []
|
|
1320
|
-
}
|
|
1321
|
-
],
|
|
1322
|
-
"parameters": [
|
|
1323
|
-
{
|
|
1324
|
-
"name": "id",
|
|
1325
|
-
"in": "path",
|
|
1326
|
-
"required": true,
|
|
1327
|
-
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
1328
|
-
"schema": {
|
|
1329
|
-
"type": "string"
|
|
1330
|
-
}
|
|
1331
|
-
},
|
|
1332
|
-
{
|
|
1333
|
-
"name": "roleId",
|
|
1334
|
-
"in": "path",
|
|
1335
|
-
"required": true,
|
|
1336
|
-
"description": "The role's uuid, from `GET /api/apps/:id/roles`; a role of another app answers `404`.",
|
|
1337
|
-
"schema": {
|
|
1338
|
-
"type": "string"
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
],
|
|
1342
|
-
"responses": {
|
|
1343
|
-
"200": {
|
|
1344
|
-
"description": "Success.",
|
|
1345
|
-
"content": {
|
|
1346
|
-
"application/json": {
|
|
1347
|
-
"schema": {
|
|
1348
|
-
"$ref": "#/components/schemas/role"
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
},
|
|
1353
|
-
"default": {
|
|
1354
|
-
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`, `validation_error`, `role_name_taken`.",
|
|
1355
|
-
"content": {
|
|
1356
|
-
"application/json": {
|
|
1357
|
-
"schema": {
|
|
1358
|
-
"$ref": "#/components/schemas/api-error"
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
},
|
|
1364
|
-
"description": "Names are unique per app, compared exactly as stored after trimming. Built-in roles can be renamed.",
|
|
1365
|
-
"requestBody": {
|
|
1366
|
-
"required": true,
|
|
1367
|
-
"content": {
|
|
1368
|
-
"application/json": {
|
|
1369
|
-
"schema": {
|
|
1370
|
-
"$ref": "#/components/schemas/role-rename-request"
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
},
|
|
1376
|
-
"delete": {
|
|
1377
|
-
"operationId": "delete_api_apps_id_roles_roleId",
|
|
1378
|
-
"summary": "Deletes a role, moving its users, pending invitations and default-role status to another role.",
|
|
1379
|
-
"tags": [
|
|
1380
|
-
"apps"
|
|
1381
|
-
],
|
|
1382
|
-
"security": [
|
|
1383
|
-
{
|
|
1384
|
-
"developerSession": []
|
|
1385
|
-
}
|
|
1386
|
-
],
|
|
1387
|
-
"parameters": [
|
|
1388
|
-
{
|
|
1389
|
-
"name": "id",
|
|
1390
|
-
"in": "path",
|
|
1391
|
-
"required": true,
|
|
1392
|
-
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
1393
|
-
"schema": {
|
|
1394
|
-
"type": "string"
|
|
1395
|
-
}
|
|
1396
|
-
},
|
|
1397
|
-
{
|
|
1398
|
-
"name": "roleId",
|
|
1399
|
-
"in": "path",
|
|
1400
|
-
"required": true,
|
|
1401
|
-
"description": "The role's uuid, from `GET /api/apps/:id/roles`; a role of another app answers `404`.",
|
|
1402
|
-
"schema": {
|
|
1403
|
-
"type": "string"
|
|
1404
|
-
}
|
|
1405
|
-
},
|
|
1406
|
-
{
|
|
1407
|
-
"name": "move_to",
|
|
1408
|
-
"in": "query",
|
|
1409
|
-
"required": false,
|
|
1410
|
-
"schema": {
|
|
1411
|
-
"description": "Another role of the same app that takes over the deleted role's app users, pending invitations and, when it applies, the app's default. The role itself or a role of another app answers `400 validation_error`.",
|
|
1412
|
-
"type": "string",
|
|
1413
|
-
"format": "uuid",
|
|
1414
|
-
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
],
|
|
1418
|
-
"responses": {
|
|
1419
|
-
"204": {
|
|
1420
|
-
"description": "Success."
|
|
1421
|
-
},
|
|
1422
|
-
"default": {
|
|
1423
|
-
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`, `validation_error`, `role_in_use`, `last_role`.",
|
|
1424
|
-
"content": {
|
|
1425
|
-
"application/json": {
|
|
1426
|
-
"schema": {
|
|
1427
|
-
"$ref": "#/components/schemas/api-error"
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
},
|
|
1433
|
-
"description": "Without `move_to`, a role that app users or pending invitations hold, or that is the app's default, answers `409 role_in_use` with `{ users, invitations, is_default }`. With `move_to` — another role of the same app, else `400 validation_error` — one transaction moves `app_users.role_id`, pending invitations and `default_role_id`, then deletes the role and its permissions. The app's only role answers `409 last_role`. Built-in roles can be deleted like any other."
|
|
1434
|
-
}
|
|
1435
|
-
},
|
|
1436
1495
|
"/api/apps/{id}/server-keys": {
|
|
1437
1496
|
"post": {
|
|
1438
1497
|
"operationId": "post_api_apps_id_server_keys",
|
|
@@ -1952,7 +2011,57 @@
|
|
|
1952
2011
|
}
|
|
1953
2012
|
},
|
|
1954
2013
|
"default": {
|
|
1955
|
-
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`, `target_state_conflict`.",
|
|
2014
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`, `target_state_conflict`, `method_not_allowed`.",
|
|
2015
|
+
"content": {
|
|
2016
|
+
"application/json": {
|
|
2017
|
+
"schema": {
|
|
2018
|
+
"$ref": "#/components/schemas/api-error"
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
},
|
|
2024
|
+
"description": "The support door beside `POST /api/client/password/reset`, refused like it with `403 method_not_allowed` while the app has the password method off: the same one-hour token and the same link, triggered by a developer for a user who asked them rather than the form. **No enumeration discipline applies** — the caller is authenticated into the app and can read the user list — so this one answers what actually happened: `{ \"mail\": mailStatus }`, where `not_configured` is a deployment without a mailer and `failed` is the state worth somebody's attention. The link points at the app's `reset_url`, or at the hosted reset page when the app has configured none. `409 target_state_conflict` names `password` with rule `not_set` for an account that has none — an OIDC-only app user, whom a reset link would hand a second, quieter door — and `status` with rule `blocked` for a blocked one, since `POST /api/client/password/reset` mails a blocked account nothing and the two doors may not disagree. Setting the password directly is deliberately not offered; a developer who could would hold their customers' credentials."
|
|
2025
|
+
}
|
|
2026
|
+
},
|
|
2027
|
+
"/api/apps/{id}/users/{userId}/two-factor": {
|
|
2028
|
+
"delete": {
|
|
2029
|
+
"operationId": "delete_api_apps_id_users_userId_two_factor",
|
|
2030
|
+
"summary": "Removes an app user's authenticator and recovery codes and ends every session they hold.",
|
|
2031
|
+
"tags": [
|
|
2032
|
+
"apps"
|
|
2033
|
+
],
|
|
2034
|
+
"security": [
|
|
2035
|
+
{
|
|
2036
|
+
"developerSession": []
|
|
2037
|
+
}
|
|
2038
|
+
],
|
|
2039
|
+
"parameters": [
|
|
2040
|
+
{
|
|
2041
|
+
"name": "id",
|
|
2042
|
+
"in": "path",
|
|
2043
|
+
"required": true,
|
|
2044
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
2045
|
+
"schema": {
|
|
2046
|
+
"type": "string"
|
|
2047
|
+
}
|
|
2048
|
+
},
|
|
2049
|
+
{
|
|
2050
|
+
"name": "userId",
|
|
2051
|
+
"in": "path",
|
|
2052
|
+
"required": true,
|
|
2053
|
+
"description": "The app user's uuid, from `GET /api/apps/:id/users`; a user of another app answers `404`.",
|
|
2054
|
+
"schema": {
|
|
2055
|
+
"type": "string"
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
],
|
|
2059
|
+
"responses": {
|
|
2060
|
+
"204": {
|
|
2061
|
+
"description": "Success."
|
|
2062
|
+
},
|
|
2063
|
+
"default": {
|
|
2064
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`.",
|
|
1956
2065
|
"content": {
|
|
1957
2066
|
"application/json": {
|
|
1958
2067
|
"schema": {
|
|
@@ -1962,7 +2071,7 @@
|
|
|
1962
2071
|
}
|
|
1963
2072
|
}
|
|
1964
2073
|
},
|
|
1965
|
-
"description": "The support door
|
|
2074
|
+
"description": "The support door for a person who lost their authenticator and their recovery codes. The authenticator and every recovery code go, and so does every session of the account — whoever held one may be the reason for the reset. **A user with no second factor answers `204` too**: that is the end state being asked for. When the app requires two-factor, the person sets it up again at their next sign-in, before any session exists. Audited as `app_user.two_factor_reset`."
|
|
1966
2075
|
}
|
|
1967
2076
|
},
|
|
1968
2077
|
"/api/apps/{id}/users/{userId}/mcp-grants": {
|
|
@@ -2172,7 +2281,7 @@
|
|
|
2172
2281
|
}
|
|
2173
2282
|
}
|
|
2174
2283
|
},
|
|
2175
|
-
"description": "**An app user, not a team member.** `POST /api/org/invitations` is the other space and leads to the console; this link leads into the developer's own app. The role is resolved and stored now, so a later change to `default_role_id` does not re-aim a link already sent. An invitation **always bypasses `allowed_domains`**. \n\nThe answer carries `accept_url
|
|
2284
|
+
"description": "**An app user, not a team member.** `POST /api/org/invitations` is the other space and leads to the console; this link leads into the developer's own app. The role is resolved and stored now, so a later change to `default_role_id` does not re-aim a link already sent. An invitation **always bypasses `allowed_domains`**. \n\nThe answer carries `accept_url`: the app's `invite_url` with the token in it, or the Fleetless-hosted invitation page when the app has configured none — so mailing it is never refused for a missing URL. `409 target_state_conflict` names `default_role_id` when `role_id` is absent and the app has no default role, or its default no longer resolves: an invitation that names no role has nothing to hand its acceptor, so it is refused here rather than at the acceptance a week later. `409 email_taken` is an address the app already has as a user; `404 not_found` is the app or a `role_id` that is not one of its roles. \n\nCreating shares the reissue route's ceiling of **five invitation mails a minute per app**, answering `429 rate_limited` with `retry_after_ms`: re-creating an invitation for one address replaces it and mails again, so a limit that bound only reissue would be a limit on the wrong door.",
|
|
2176
2285
|
"requestBody": {
|
|
2177
2286
|
"required": true,
|
|
2178
2287
|
"content": {
|
|
@@ -2569,7 +2678,7 @@
|
|
|
2569
2678
|
"/api/apps/{id}/auth-config": {
|
|
2570
2679
|
"get": {
|
|
2571
2680
|
"operationId": "get_api_apps_id_auth_config",
|
|
2572
|
-
"summary": "Reads the app's auth settings:
|
|
2681
|
+
"summary": "Reads the app's auth settings: sign-in methods, two-factor, registration, pages, the hosted look and the MCP switch.",
|
|
2573
2682
|
"tags": [
|
|
2574
2683
|
"apps"
|
|
2575
2684
|
],
|
|
@@ -2611,7 +2720,7 @@
|
|
|
2611
2720
|
}
|
|
2612
2721
|
}
|
|
2613
2722
|
},
|
|
2614
|
-
"description": "One row per app, created with the app and never absent — an app that has configured nothing reads back the defaults rather than a `404`. `oidc_callback_url` is in the answer and not in the request: it is minted by the cloud from its own public base URL, is the same for every app and every provider, and is the value a developer registers at their identity provider. It stays read-only on every slice write below for a second reason: a writable callback URL would let a caller point the return leg of an OIDC sign-in, which carries an authorization code, at a host they own. `updated_at` is read-only for a duller one: the server stamps it on every write, and a client-supplied value would be a lie about when the row last changed."
|
|
2723
|
+
"description": "One row per app, created with the app and never absent — an app that has configured nothing reads back the defaults rather than a `404`. `oidc_callback_url` is in the answer and not in the request: it is minted by the cloud from its own public base URL, is the same for every app and every provider, and is the value a developer registers at their identity provider. It stays read-only on every slice write below for a second reason: a writable callback URL would let a caller point the return leg of an OIDC sign-in, which carries an authorization code, at a host they own. `updated_at` is read-only for a duller one: the server stamps it on every write, and a client-supplied value would be a lie about when the row last changed. `hosted_pages` and `hosted_logo_url` are read-only as well: the cloud mints both from the auth portal's base URL and the app's identifier."
|
|
2615
2724
|
}
|
|
2616
2725
|
},
|
|
2617
2726
|
"/api/apps/{id}/auth-config/registration": {
|
|
@@ -2659,7 +2768,7 @@
|
|
|
2659
2768
|
}
|
|
2660
2769
|
}
|
|
2661
2770
|
},
|
|
2662
|
-
"description": "**A replace, not a merge, and `.strict()`**: `self_registration`, `allowed_domains` and `allowed_origins` all arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's — see `GET`'s notes for why. \n\n`400 validation_error` is where the two field rules land: an entry in `allowed_domains` must be lowercase, since a capitalised one can never match a lowercased address, and an entry in `allowed_origins` must be a bare scheme-host-port with no path, since a browser sends nothing longer in its `Origin` header. Each refuses at configuration time rather than failing silently later. \n\nThe merge is server-side against the stored row, so this write never disturbs
|
|
2771
|
+
"description": "**A replace, not a merge, and `.strict()`**: `self_registration`, `allowed_domains` and `allowed_origins` all arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's — see `GET`'s notes for why. \n\n`400 validation_error` is where the two field rules land: an entry in `allowed_domains` must be lowercase, since a capitalised one can never match a lowercased address, and an entry in `allowed_origins` must be a bare scheme-host-port with no path, since a browser sends nothing longer in its `Origin` header. Each refuses at configuration time rather than failing silently later. \n\nThe merge is server-side against the stored row, so this write never disturbs another slice.",
|
|
2663
2772
|
"requestBody": {
|
|
2664
2773
|
"required": true,
|
|
2665
2774
|
"content": {
|
|
@@ -2672,10 +2781,68 @@
|
|
|
2672
2781
|
}
|
|
2673
2782
|
}
|
|
2674
2783
|
},
|
|
2784
|
+
"/api/apps/{id}/auth-config/sign-in": {
|
|
2785
|
+
"put": {
|
|
2786
|
+
"operationId": "put_api_apps_id_auth_config_sign_in",
|
|
2787
|
+
"summary": "Replaces how the app's users sign in and whether they give a second factor.",
|
|
2788
|
+
"tags": [
|
|
2789
|
+
"apps"
|
|
2790
|
+
],
|
|
2791
|
+
"security": [
|
|
2792
|
+
{
|
|
2793
|
+
"developerSession": []
|
|
2794
|
+
}
|
|
2795
|
+
],
|
|
2796
|
+
"parameters": [
|
|
2797
|
+
{
|
|
2798
|
+
"name": "id",
|
|
2799
|
+
"in": "path",
|
|
2800
|
+
"required": true,
|
|
2801
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
2802
|
+
"schema": {
|
|
2803
|
+
"type": "string"
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
],
|
|
2807
|
+
"responses": {
|
|
2808
|
+
"200": {
|
|
2809
|
+
"description": "Success.",
|
|
2810
|
+
"content": {
|
|
2811
|
+
"application/json": {
|
|
2812
|
+
"schema": {
|
|
2813
|
+
"$ref": "#/components/schemas/app-auth-config"
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
},
|
|
2818
|
+
"default": {
|
|
2819
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `validation_error`, `not_found`.",
|
|
2820
|
+
"content": {
|
|
2821
|
+
"application/json": {
|
|
2822
|
+
"schema": {
|
|
2823
|
+
"$ref": "#/components/schemas/api-error"
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
},
|
|
2829
|
+
"description": "**A replace, not a merge, and `.strict()`**: `sign_in_methods` and `two_factor` both arrive or the write is refused. Both methods off is `400 validation_error` naming `sign_in_methods.password` — an app needs at least one door besides its identity providers. \n\nTurning a method off refuses its routes with `method_not_allowed` from the next request on; a stored password stays stored. Setting `two_factor` to `required` signs nobody out: each person without an authenticator sets one up at their next sign-in, before any session exists. Audited with both old and new values. The merge is server-side against the stored row, so this write never disturbs another slice.",
|
|
2830
|
+
"requestBody": {
|
|
2831
|
+
"required": true,
|
|
2832
|
+
"content": {
|
|
2833
|
+
"application/json": {
|
|
2834
|
+
"schema": {
|
|
2835
|
+
"$ref": "#/components/schemas/put-app-auth-sign-in-request"
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
},
|
|
2675
2842
|
"/api/apps/{id}/auth-config/urls": {
|
|
2676
2843
|
"put": {
|
|
2677
2844
|
"operationId": "put_api_apps_id_auth_config_urls",
|
|
2678
|
-
"summary": "Replaces the
|
|
2845
|
+
"summary": "Replaces the app's home page and the four pages Fleetless's mails and MCP sign-in point at.",
|
|
2679
2846
|
"tags": [
|
|
2680
2847
|
"apps"
|
|
2681
2848
|
],
|
|
@@ -2717,7 +2884,7 @@
|
|
|
2717
2884
|
}
|
|
2718
2885
|
}
|
|
2719
2886
|
},
|
|
2720
|
-
"description": "**A replace, not a merge, and `.strict()`**: `invite_url`, `verify_url` and `
|
|
2887
|
+
"description": "**A replace, not a merge, and `.strict()`**: `app_url`, `invite_url`, `verify_url`, `reset_url` and `mcp_login_url` all arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's — see `GET`'s notes for why. \n\nEach may be `null`, and then the Fleetless-hosted page in `hosted_pages` stands in for it: nothing is refused for a missing URL. `400 validation_error` is where the field rules land: a URL template must be https (or `http` on `localhost`) and carry its placeholder exactly once — a second occurrence leaves one literal in a mailed link, refused here rather than failing silently once the mail is sent — and `app_url` takes the same host rule with no placeholder. `mcp_login_url` moved here from the `mcp` slice, because one screen owns all four pages. \n\nThe merge is server-side against the stored row, so this write never disturbs another slice.",
|
|
2721
2888
|
"requestBody": {
|
|
2722
2889
|
"required": true,
|
|
2723
2890
|
"content": {
|
|
@@ -2733,7 +2900,7 @@
|
|
|
2733
2900
|
"/api/apps/{id}/auth-config/mcp": {
|
|
2734
2901
|
"put": {
|
|
2735
2902
|
"operationId": "put_api_apps_id_auth_config_mcp",
|
|
2736
|
-
"summary": "
|
|
2903
|
+
"summary": "Turns the app's MCP endpoint on or off.",
|
|
2737
2904
|
"tags": [
|
|
2738
2905
|
"apps"
|
|
2739
2906
|
],
|
|
@@ -2775,7 +2942,7 @@
|
|
|
2775
2942
|
}
|
|
2776
2943
|
}
|
|
2777
2944
|
},
|
|
2778
|
-
"description": "**A replace, not a merge, and `.strict()`**: `mcp_enabled`
|
|
2945
|
+
"description": "**A replace, not a merge, and `.strict()`**: `mcp_enabled` arrives or the write is refused. It used to take `mcp_login_url` as well, because on without a URL refused every sign-in; the hosted MCP sign-in now stands in for an unset URL, and the URL moved to the `urls` slice. A body still carrying it is `400 validation_error`. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's. The merge is server-side against the stored row, so this write never disturbs another slice.",
|
|
2779
2946
|
"requestBody": {
|
|
2780
2947
|
"required": true,
|
|
2781
2948
|
"content": {
|
|
@@ -2788,6 +2955,158 @@
|
|
|
2788
2955
|
}
|
|
2789
2956
|
}
|
|
2790
2957
|
},
|
|
2958
|
+
"/api/apps/{id}/auth-config/look": {
|
|
2959
|
+
"put": {
|
|
2960
|
+
"operationId": "put_api_apps_id_auth_config_look",
|
|
2961
|
+
"summary": "Replaces the hosted pages' accent colour.",
|
|
2962
|
+
"tags": [
|
|
2963
|
+
"apps"
|
|
2964
|
+
],
|
|
2965
|
+
"security": [
|
|
2966
|
+
{
|
|
2967
|
+
"developerSession": []
|
|
2968
|
+
}
|
|
2969
|
+
],
|
|
2970
|
+
"parameters": [
|
|
2971
|
+
{
|
|
2972
|
+
"name": "id",
|
|
2973
|
+
"in": "path",
|
|
2974
|
+
"required": true,
|
|
2975
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
2976
|
+
"schema": {
|
|
2977
|
+
"type": "string"
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
],
|
|
2981
|
+
"responses": {
|
|
2982
|
+
"200": {
|
|
2983
|
+
"description": "Success.",
|
|
2984
|
+
"content": {
|
|
2985
|
+
"application/json": {
|
|
2986
|
+
"schema": {
|
|
2987
|
+
"$ref": "#/components/schemas/app-auth-config"
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
},
|
|
2992
|
+
"default": {
|
|
2993
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `validation_error`, `not_found`.",
|
|
2994
|
+
"content": {
|
|
2995
|
+
"application/json": {
|
|
2996
|
+
"schema": {
|
|
2997
|
+
"$ref": "#/components/schemas/api-error"
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
},
|
|
3003
|
+
"description": "**A replace, and `.strict()`**: `hosted_accent` arrives, `#rrggbb` in lowercase, or `null` for the neutral shell's own accent. The logo is its own write, `PUT /api/apps/:id/auth-config/logo`, because it is an image rather than a field. The merge is server-side against the stored row, so this write never disturbs another slice.",
|
|
3004
|
+
"requestBody": {
|
|
3005
|
+
"required": true,
|
|
3006
|
+
"content": {
|
|
3007
|
+
"application/json": {
|
|
3008
|
+
"schema": {
|
|
3009
|
+
"$ref": "#/components/schemas/put-app-auth-look-request"
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
},
|
|
3016
|
+
"/api/apps/{id}/auth-config/logo": {
|
|
3017
|
+
"put": {
|
|
3018
|
+
"operationId": "put_api_apps_id_auth_config_logo",
|
|
3019
|
+
"summary": "Stores the logo the hosted pages show above the app's name.",
|
|
3020
|
+
"tags": [
|
|
3021
|
+
"apps"
|
|
3022
|
+
],
|
|
3023
|
+
"security": [
|
|
3024
|
+
{
|
|
3025
|
+
"developerSession": []
|
|
3026
|
+
}
|
|
3027
|
+
],
|
|
3028
|
+
"parameters": [
|
|
3029
|
+
{
|
|
3030
|
+
"name": "id",
|
|
3031
|
+
"in": "path",
|
|
3032
|
+
"required": true,
|
|
3033
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
3034
|
+
"schema": {
|
|
3035
|
+
"type": "string"
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
],
|
|
3039
|
+
"responses": {
|
|
3040
|
+
"200": {
|
|
3041
|
+
"description": "Success.",
|
|
3042
|
+
"content": {
|
|
3043
|
+
"application/json": {
|
|
3044
|
+
"schema": {
|
|
3045
|
+
"$ref": "#/components/schemas/app-auth-config"
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
},
|
|
3050
|
+
"default": {
|
|
3051
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `validation_error`, `not_found`, `unsupported_media_type`.",
|
|
3052
|
+
"content": {
|
|
3053
|
+
"application/json": {
|
|
3054
|
+
"schema": {
|
|
3055
|
+
"$ref": "#/components/schemas/api-error"
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
},
|
|
3061
|
+
"description": "The body is the **raw image**, not JSON, so it has no request schema: `Content-Type` is one of `HOSTED_LOGO_TYPES` (`image/png`, `image/svg+xml`) and anything else is `415 unsupported_media_type`. At most `HOSTED_LOGO_MAX_BYTES` (100 KB); a larger body, or one that is not the image its type names, is `400 validation_error`. A new logo replaces the stored one. The hosted pages load it from `hosted_logo_url` as an image only, and the cloud serves an SVG sandboxed, so a script inside one never runs."
|
|
3062
|
+
},
|
|
3063
|
+
"delete": {
|
|
3064
|
+
"operationId": "delete_api_apps_id_auth_config_logo",
|
|
3065
|
+
"summary": "Removes the logo from the hosted pages.",
|
|
3066
|
+
"tags": [
|
|
3067
|
+
"apps"
|
|
3068
|
+
],
|
|
3069
|
+
"security": [
|
|
3070
|
+
{
|
|
3071
|
+
"developerSession": []
|
|
3072
|
+
}
|
|
3073
|
+
],
|
|
3074
|
+
"parameters": [
|
|
3075
|
+
{
|
|
3076
|
+
"name": "id",
|
|
3077
|
+
"in": "path",
|
|
3078
|
+
"required": true,
|
|
3079
|
+
"description": "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`.",
|
|
3080
|
+
"schema": {
|
|
3081
|
+
"type": "string"
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
],
|
|
3085
|
+
"responses": {
|
|
3086
|
+
"200": {
|
|
3087
|
+
"description": "Success.",
|
|
3088
|
+
"content": {
|
|
3089
|
+
"application/json": {
|
|
3090
|
+
"schema": {
|
|
3091
|
+
"$ref": "#/components/schemas/app-auth-config"
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
},
|
|
3096
|
+
"default": {
|
|
3097
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `invalid_uuid`, `not_found`.",
|
|
3098
|
+
"content": {
|
|
3099
|
+
"application/json": {
|
|
3100
|
+
"schema": {
|
|
3101
|
+
"$ref": "#/components/schemas/api-error"
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
},
|
|
3107
|
+
"description": "Answers the whole configuration, with `hosted_logo_url` now `null`; the hosted pages show the app's name alone. An app with no logo answers the same: that is the end state being asked for."
|
|
3108
|
+
}
|
|
3109
|
+
},
|
|
2791
3110
|
"/api/apps/{id}/mail-templates": {
|
|
2792
3111
|
"get": {
|
|
2793
3112
|
"operationId": "get_api_apps_id_mail_templates",
|
|
@@ -2833,7 +3152,7 @@
|
|
|
2833
3152
|
}
|
|
2834
3153
|
}
|
|
2835
3154
|
},
|
|
2836
|
-
"description": "Answers `{ \"templates\": [appMailTemplate, …] }` with **only the kinds that have a custom template** — at most
|
|
3155
|
+
"description": "Answers `{ \"templates\": [appMailTemplate, …] }` with **only the kinds that have a custom template** — at most four. A kind that does not appear is one using the Fleetless default text, which is an ordinary state and not a missing row. Mails to *Fleetless* users, a team invitation or a console sign-in code, are not in this list and are deliberately not customisable: they are about this platform, not about the developer's product."
|
|
2837
3156
|
}
|
|
2838
3157
|
},
|
|
2839
3158
|
"/api/apps/{id}/mail-templates/{kind}": {
|
|
@@ -2862,7 +3181,7 @@
|
|
|
2862
3181
|
"name": "kind",
|
|
2863
3182
|
"in": "path",
|
|
2864
3183
|
"required": true,
|
|
2865
|
-
"description": "Which of the
|
|
3184
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
|
|
2866
3185
|
"schema": {
|
|
2867
3186
|
"type": "string"
|
|
2868
3187
|
}
|
|
@@ -2917,7 +3236,7 @@
|
|
|
2917
3236
|
"name": "kind",
|
|
2918
3237
|
"in": "path",
|
|
2919
3238
|
"required": true,
|
|
2920
|
-
"description": "Which of the
|
|
3239
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
|
|
2921
3240
|
"schema": {
|
|
2922
3241
|
"type": "string"
|
|
2923
3242
|
}
|
|
@@ -2982,7 +3301,7 @@
|
|
|
2982
3301
|
"name": "kind",
|
|
2983
3302
|
"in": "path",
|
|
2984
3303
|
"required": true,
|
|
2985
|
-
"description": "Which of the
|
|
3304
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
|
|
2986
3305
|
"schema": {
|
|
2987
3306
|
"type": "string"
|
|
2988
3307
|
}
|
|
@@ -3032,7 +3351,7 @@
|
|
|
3032
3351
|
"name": "kind",
|
|
3033
3352
|
"in": "path",
|
|
3034
3353
|
"required": true,
|
|
3035
|
-
"description": "Which of the
|
|
3354
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
|
|
3036
3355
|
"schema": {
|
|
3037
3356
|
"type": "string"
|
|
3038
3357
|
}
|
|
@@ -3099,7 +3418,7 @@
|
|
|
3099
3418
|
"name": "kind",
|
|
3100
3419
|
"in": "path",
|
|
3101
3420
|
"required": true,
|
|
3102
|
-
"description": "Which of the
|
|
3421
|
+
"description": "Which of the four mails this template replaces — a `mailTemplateKind`: `invite`, `verify`, `reset` or `login_code`.",
|
|
3103
3422
|
"schema": {
|
|
3104
3423
|
"type": "string"
|
|
3105
3424
|
}
|
|
@@ -3496,7 +3815,7 @@
|
|
|
3496
3815
|
"/api/org/invitations/accept": {
|
|
3497
3816
|
"post": {
|
|
3498
3817
|
"operationId": "post_api_org_invitations_accept",
|
|
3499
|
-
"summary": "Spends an invitation token and creates the
|
|
3818
|
+
"summary": "Spends an invitation token and creates the account it was addressed to.",
|
|
3500
3819
|
"tags": [
|
|
3501
3820
|
"users"
|
|
3502
3821
|
],
|
|
@@ -3517,7 +3836,7 @@
|
|
|
3517
3836
|
}
|
|
3518
3837
|
}
|
|
3519
3838
|
},
|
|
3520
|
-
"description": "**`204`, not a session.** The console signs in through its own OAuth portal, so a session minted here would be a second credential door for one account — and every security property would then have to be right in two places. Unknown, expired and already-accepted tokens collapse into `410 token_spent`. A browser form post gets the rendered \"you're in\" page instead.",
|
|
3839
|
+
"description": "**`204`, not a session.** The console signs in through its own OAuth portal, so a session minted here would be a second credential door for one account — and every security property would then have to be right in two places. **No password**: the mailed link proves the address, so accepting needs no code either, and the new member signs in by emailed code from then on. When the organisation requires two-factor, the member sets one up at their first sign-in. Unknown, expired and already-accepted tokens collapse into `410 token_spent`. A browser form post gets the rendered \"you're in\" page instead.",
|
|
3521
3840
|
"requestBody": {
|
|
3522
3841
|
"required": true,
|
|
3523
3842
|
"content": {
|
|
@@ -3554,18 +3873,69 @@
|
|
|
3554
3873
|
}
|
|
3555
3874
|
],
|
|
3556
3875
|
"responses": {
|
|
3557
|
-
"200": {
|
|
3558
|
-
"description": "Success.",
|
|
3559
|
-
"content": {
|
|
3560
|
-
"application/json": {
|
|
3561
|
-
"schema": {
|
|
3562
|
-
"$ref": "#/components/schemas/fleetless-user"
|
|
3563
|
-
}
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3876
|
+
"200": {
|
|
3877
|
+
"description": "Success.",
|
|
3878
|
+
"content": {
|
|
3879
|
+
"application/json": {
|
|
3880
|
+
"schema": {
|
|
3881
|
+
"$ref": "#/components/schemas/fleetless-user"
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
},
|
|
3886
|
+
"default": {
|
|
3887
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `tier_required`, `invalid_uuid`, `not_found`, `validation_error`, `last_owner`.",
|
|
3888
|
+
"content": {
|
|
3889
|
+
"application/json": {
|
|
3890
|
+
"schema": {
|
|
3891
|
+
"$ref": "#/components/schemas/api-error"
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
},
|
|
3897
|
+
"description": "Owner tier, unconditionally — this is the route the whole owner-exclusive list is about. A uuid that is not a Fleetless user of this org answers `404 not_found`, the same as one that does not exist anywhere: the `409 target_state_conflict` documented here until the two-space cut had exactly one producer, the Org Admins membership check, and went with it. Demoting the last Owner is `409 last_owner`, decided by a row lock inside the writing transaction rather than by a read beforehand. Setting the tier already held changes nothing and writes no audit event. No session is revoked: a tier is re-read from the row on every request, so no issued token carries a stale copy of it.",
|
|
3898
|
+
"requestBody": {
|
|
3899
|
+
"required": true,
|
|
3900
|
+
"content": {
|
|
3901
|
+
"application/json": {
|
|
3902
|
+
"schema": {
|
|
3903
|
+
"$ref": "#/components/schemas/tier-change-request"
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
},
|
|
3910
|
+
"/api/org/users/{id}/two-factor": {
|
|
3911
|
+
"delete": {
|
|
3912
|
+
"operationId": "delete_api_org_users_id_two_factor",
|
|
3913
|
+
"summary": "Removes a team member's passkeys, authenticator and recovery codes and ends their sessions.",
|
|
3914
|
+
"tags": [
|
|
3915
|
+
"users"
|
|
3916
|
+
],
|
|
3917
|
+
"security": [
|
|
3918
|
+
{
|
|
3919
|
+
"developerSession": []
|
|
3920
|
+
}
|
|
3921
|
+
],
|
|
3922
|
+
"parameters": [
|
|
3923
|
+
{
|
|
3924
|
+
"name": "id",
|
|
3925
|
+
"in": "path",
|
|
3926
|
+
"required": true,
|
|
3927
|
+
"description": "The Fleetless user's uuid, as listed by `GET /api/org/users`.",
|
|
3928
|
+
"schema": {
|
|
3929
|
+
"type": "string"
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
],
|
|
3933
|
+
"responses": {
|
|
3934
|
+
"204": {
|
|
3935
|
+
"description": "Success."
|
|
3566
3936
|
},
|
|
3567
3937
|
"default": {
|
|
3568
|
-
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `tier_required`, `invalid_uuid`, `not_found`, `
|
|
3938
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `tier_required`, `invalid_uuid`, `not_found`, `target_state_conflict`.",
|
|
3569
3939
|
"content": {
|
|
3570
3940
|
"application/json": {
|
|
3571
3941
|
"schema": {
|
|
@@ -3575,23 +3945,13 @@
|
|
|
3575
3945
|
}
|
|
3576
3946
|
}
|
|
3577
3947
|
},
|
|
3578
|
-
"description": "Owner tier
|
|
3579
|
-
"requestBody": {
|
|
3580
|
-
"required": true,
|
|
3581
|
-
"content": {
|
|
3582
|
-
"application/json": {
|
|
3583
|
-
"schema": {
|
|
3584
|
-
"$ref": "#/components/schemas/tier-change-request"
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3588
|
-
}
|
|
3948
|
+
"description": "Owner tier: the door for a member who lost every second factor and every recovery code. Every session of the member ends, and when the organisation requires two-factor they set one up again at their next sign-in. **An owner cannot reset their own** — `409 target_state_conflict` naming `user_id` with rule `self`; Settings › Profile is where they change it. A member with no second factor answers `204` too. Audited as `developer.two_factor_reset`, naming the owner who did it."
|
|
3589
3949
|
}
|
|
3590
3950
|
},
|
|
3591
3951
|
"/api/org": {
|
|
3592
3952
|
"patch": {
|
|
3593
3953
|
"operationId": "patch_api_org",
|
|
3594
|
-
"summary": "Renames the org.",
|
|
3954
|
+
"summary": "Renames the org, requires two-factor for its members, or both.",
|
|
3595
3955
|
"tags": [
|
|
3596
3956
|
"org"
|
|
3597
3957
|
],
|
|
@@ -3623,7 +3983,7 @@
|
|
|
3623
3983
|
}
|
|
3624
3984
|
}
|
|
3625
3985
|
},
|
|
3626
|
-
"description": "Answers `{ \"org\": org }`. Owner tier, and the gate runs before the body is looked at, so a malformed
|
|
3986
|
+
"description": "Answers `{ \"org\": org }`. Owner tier, and the gate runs before the body is looked at, so a malformed patch and a forbidden one answer the same way — `403 tier_required` for a developer, whichever field they sent. An empty body is `400 validation_error`. Writing the values already held writes nothing and records no audit event. \n\n`require_two_factor` on signs nobody out: each member without a passkey or authenticator sets one up at their next sign-in, before any session exists, on the console and the central MCP endpoint alike. Server keys and robot bridges are not people and are not affected. Audited as `org.two_factor_required_changed`.",
|
|
3627
3987
|
"requestBody": {
|
|
3628
3988
|
"required": true,
|
|
3629
3989
|
"content": {
|
|
@@ -3841,7 +4201,7 @@
|
|
|
3841
4201
|
}
|
|
3842
4202
|
}
|
|
3843
4203
|
},
|
|
3844
|
-
"description": "**The query schema is what this endpoint accepts, not what it parses**: the handler reads it parameter by parameter because the answers differ, and one parse would collapse them. Client and `redirect_uri` are validated first and a failure there never redirects, the same open-redirect discipline the app flow applies; those refusals are `oauthError`. Exact `redirect_uri` matching for both client kinds — the loopback-port wildcard of RFC 8252 §7.3 belongs to the one central client alone, whose URIs are configured ahead of time and cannot name an ephemeral port. A client that registered itself seconds ago can name the port it bound, and widening the wildcard there would only widen where a stolen `client_id` may send a browser. Nothing about the person is decided here — the next card asks for an email address and the
|
|
4204
|
+
"description": "**The query schema is what this endpoint accepts, not what it parses**: the handler reads it parameter by parameter because the answers differ, and one parse would collapse them. Client and `redirect_uri` are validated first and a failure there never redirects, the same open-redirect discipline the app flow applies; those refusals are `oauthError`. Exact `redirect_uri` matching for both client kinds — the loopback-port wildcard of RFC 8252 §7.3 belongs to the one central client alone, whose URIs are configured ahead of time and cannot name an ephemeral port. A client that registered itself seconds ago can name the port it bound, and widening the wildcard there would only widen where a stolen `client_id` may send a browser. Nothing about the person is decided here — the next card asks for an email address, or a passkey, and the steps after it resolve the account; this route knows only the client."
|
|
3845
4205
|
}
|
|
3846
4206
|
},
|
|
3847
4207
|
"/mcp/oauth/token": {
|
|
@@ -4123,7 +4483,7 @@
|
|
|
4123
4483
|
}
|
|
4124
4484
|
}
|
|
4125
4485
|
},
|
|
4126
|
-
"description": "RFC 8414, for the issuer `<PUBLIC_API_BASE_URL>/mcp/<identifier>` — the same path rule as the document above, and the same `404` for a switched-off app. `registration_endpoint` is present for the reason the central document states: a client that finds it registers itself and never asks a person for a `client_id`. \n\n**`issuer`, `token_endpoint` and the resource identifier are minted from the canonical public base, never from the friendly `mcp.fleetless.dev` alias or the request's `Host`**, because a client checks a minted token's `iss` and `aud` against these exact strings. \n\n**Unlike the central document, `authorization_endpoint` does not move to an auth-portal origin
|
|
4486
|
+
"description": "RFC 8414, for the issuer `<PUBLIC_API_BASE_URL>/mcp/<identifier>` — the same path rule as the document above, and the same `404` for a switched-off app. `registration_endpoint` is present for the reason the central document states: a client that finds it registers itself and never asks a person for a `client_id`. \n\n**`issuer`, `token_endpoint` and the resource identifier are minted from the canonical public base, never from the friendly `mcp.fleetless.dev` alias or the request's `Host`**, because a client checks a minted token's `iss` and `aud` against these exact strings. \n\n**Unlike the central document, `authorization_endpoint` does not move to an auth-portal origin**: the authorization step renders no page itself. It redirects to the app's own `mcp_login_url`, or to the hosted MCP sign-in on the auth portal when the app has configured none."
|
|
4127
4487
|
}
|
|
4128
4488
|
},
|
|
4129
4489
|
"/mcp/{appIdentifier}/oauth/register": {
|
|
@@ -4183,7 +4543,7 @@
|
|
|
4183
4543
|
"/mcp/{appIdentifier}/oauth/authorize": {
|
|
4184
4544
|
"get": {
|
|
4185
4545
|
"operationId": "get_mcp_appIdentifier_oauth_authorize",
|
|
4186
|
-
"summary": "Starts an MCP sign-in and redirects the browser to the app's own login page.",
|
|
4546
|
+
"summary": "Starts an MCP sign-in and redirects the browser to the app's own login page, or to the hosted one.",
|
|
4187
4547
|
"tags": [
|
|
4188
4548
|
"mcp"
|
|
4189
4549
|
],
|
|
@@ -4272,7 +4632,7 @@
|
|
|
4272
4632
|
"description": "Success."
|
|
4273
4633
|
},
|
|
4274
4634
|
"default": {
|
|
4275
|
-
"description": "An error envelope. Codes this route is known to answer: `not_found
|
|
4635
|
+
"description": "An error envelope. Codes this route is known to answer: `not_found`.",
|
|
4276
4636
|
"content": {
|
|
4277
4637
|
"application/json": {
|
|
4278
4638
|
"schema": {
|
|
@@ -4282,7 +4642,7 @@
|
|
|
4282
4642
|
}
|
|
4283
4643
|
}
|
|
4284
4644
|
},
|
|
4285
|
-
"description": "The same query as `GET /mcp/oauth/authorize`, read the same way — parameter by parameter, because the answers differ and one parse would collapse them. \n\n**
|
|
4645
|
+
"description": "The same query as `GET /mcp/oauth/authorize`, read the same way — parameter by parameter, because the answers differ and one parse would collapse them. \n\n**This route renders no page.** It writes an interaction — ten minutes, as the OIDC ones live — and redirects to `appAuthConfig.mcp_login_url` with `{interaction}` filled in. The app then authenticates the person with its own UI, reads `GET /api/client/mcp/interactions/:id` to show the client's claimed name and the scopes it asked for, and calls approve or deny. **An app with no `mcp_login_url` is redirected to the hosted MCP sign-in** (`GET /app/:appIdentifier/mcp/:interaction`), which runs the same steps on the auth portal; nothing is refused for a missing URL. \n\nClient and `redirect_uri` are validated first and a failure there never redirects — the open-redirect discipline `GET /mcp/oauth/authorize` and `GET /api/client/oidc/:slug/start` both keep — and those refusals are RFC 6749's flat `oauthError`, which is why none of them appear above. `redirect_uri` is matched **exactly** against the registration, with no loopback-port wildcard: every client here registered itself minutes ago and can name the port it bound, so a wildcard would only widen where a stolen `client_id` may send a browser. \n\nThe code above is the `apiError` envelope because it is a refusal about the **app**, decided before an OAuth parameter is looked at. **`404 not_found` covers an identifier no app carries AND an app with MCP switched off** — the same single answer the two metadata documents, `register` and the transport give. An earlier draft answered `403 mcp_disabled` here, on the argument that a client which registered while the switch was on is owed the difference between \"turned off\" and \"mistyped\"; that argument does not survive the caller being anonymous. This route takes no credential, so the extra code was readable by anyone who could type an identifier, and it handed back precisely the existence distinction every neighbouring route collapses. `mcp_disabled` survives only where the caller has already proved they belong to the app — the two decision routes under `/api/client/mcp/interactions/:id`."
|
|
4286
4646
|
}
|
|
4287
4647
|
},
|
|
4288
4648
|
"/mcp/{appIdentifier}/oauth/token": {
|
|
@@ -4354,13 +4714,13 @@
|
|
|
4354
4714
|
"content": {
|
|
4355
4715
|
"application/json": {
|
|
4356
4716
|
"schema": {
|
|
4357
|
-
"$ref": "#/components/schemas/
|
|
4717
|
+
"$ref": "#/components/schemas/client-sign-in-result"
|
|
4358
4718
|
}
|
|
4359
4719
|
}
|
|
4360
4720
|
}
|
|
4361
4721
|
},
|
|
4362
4722
|
"default": {
|
|
4363
|
-
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `invalid_credentials`.",
|
|
4723
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `invalid_credentials`, `method_not_allowed`.",
|
|
4364
4724
|
"content": {
|
|
4365
4725
|
"application/json": {
|
|
4366
4726
|
"schema": {
|
|
@@ -4370,7 +4730,7 @@
|
|
|
4370
4730
|
}
|
|
4371
4731
|
}
|
|
4372
4732
|
},
|
|
4373
|
-
"description": "One refusal for every miss — unknown app, unknown address, wrong password, a `blocked` account and one still `pending_verification` — because the caller supplies the `app_identifier` unauthenticated, so \"this app knows this user\" is not a fact the answer may carry. The argon2 verify is paid unconditionally, including for an unknown app identifier, so response time is not an oracle either.",
|
|
4733
|
+
"description": "One refusal for every miss — unknown app, unknown address, wrong password, a `blocked` account and one still `pending_verification` — because the caller supplies the `app_identifier` unauthenticated, so \"this app knows this user\" is not a fact the answer may carry. The argon2 verify is paid unconditionally, including for an unknown app identifier, so response time is not an oracle either. \n\n**The answer is a `clientSignInResult`**: session tokens, or a `twoFactorChallenge` when the person has a confirmed authenticator or the app requires one — then no session exists until `POST /api/client/two-factor/verify` or the setup is done. `403 method_not_allowed` when the app has the password method off; it names the app's policy, not a person.",
|
|
4374
4734
|
"requestBody": {
|
|
4375
4735
|
"required": true,
|
|
4376
4736
|
"content": {
|
|
@@ -4383,6 +4743,87 @@
|
|
|
4383
4743
|
}
|
|
4384
4744
|
}
|
|
4385
4745
|
},
|
|
4746
|
+
"/api/client/login/code": {
|
|
4747
|
+
"post": {
|
|
4748
|
+
"operationId": "post_api_client_login_code",
|
|
4749
|
+
"summary": "Mails a six-digit sign-in code, and answers the same whether or not the address exists.",
|
|
4750
|
+
"tags": [
|
|
4751
|
+
"client-auth"
|
|
4752
|
+
],
|
|
4753
|
+
"security": [],
|
|
4754
|
+
"parameters": [],
|
|
4755
|
+
"responses": {
|
|
4756
|
+
"202": {
|
|
4757
|
+
"description": "Success."
|
|
4758
|
+
},
|
|
4759
|
+
"default": {
|
|
4760
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `not_found`, `method_not_allowed`.",
|
|
4761
|
+
"content": {
|
|
4762
|
+
"application/json": {
|
|
4763
|
+
"schema": {
|
|
4764
|
+
"$ref": "#/components/schemas/api-error"
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
},
|
|
4770
|
+
"description": "**`202` and an empty body for every request the policy allows**, in status, body and timing, whether or not the address names an active account of this app — a decoy like `POST /api/client/resend-verification`, so this is no enumeration oracle. A mail goes out for an `active` account and for one still `pending_verification` — spending the code proves the address, as the verification link would — and never for a `blocked` one or an unknown address. The code is six digits, valid ten minutes, takes five wrong attempts, and a new request expires the previous one for the same address; a request within sixty seconds of the last sends no second mail. The address is trimmed and compared case-insensitively. `404 not_found` is the **app identifier**, never the address; `403 method_not_allowed` when the app has the email-code method off. Limited per app, address and IP, so it cannot be used to mail somebody repeatedly.",
|
|
4771
|
+
"requestBody": {
|
|
4772
|
+
"required": true,
|
|
4773
|
+
"content": {
|
|
4774
|
+
"application/json": {
|
|
4775
|
+
"schema": {
|
|
4776
|
+
"$ref": "#/components/schemas/client-login-code-request"
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
},
|
|
4783
|
+
"/api/client/login/code/verify": {
|
|
4784
|
+
"post": {
|
|
4785
|
+
"operationId": "post_api_client_login_code_verify",
|
|
4786
|
+
"summary": "Spends a mailed sign-in code and answers a session or a two-factor challenge.",
|
|
4787
|
+
"tags": [
|
|
4788
|
+
"client-auth"
|
|
4789
|
+
],
|
|
4790
|
+
"security": [],
|
|
4791
|
+
"parameters": [],
|
|
4792
|
+
"responses": {
|
|
4793
|
+
"200": {
|
|
4794
|
+
"description": "Success.",
|
|
4795
|
+
"content": {
|
|
4796
|
+
"application/json": {
|
|
4797
|
+
"schema": {
|
|
4798
|
+
"$ref": "#/components/schemas/client-sign-in-result"
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
}
|
|
4802
|
+
},
|
|
4803
|
+
"default": {
|
|
4804
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `invalid_code`, `token_spent`, `method_not_allowed`.",
|
|
4805
|
+
"content": {
|
|
4806
|
+
"application/json": {
|
|
4807
|
+
"schema": {
|
|
4808
|
+
"$ref": "#/components/schemas/api-error"
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
},
|
|
4814
|
+
"description": "A wrong code is `400 invalid_code` with `details.attempts_left` (`invalidCodeDetails`). A code that is spent, past its ten minutes, out of attempts, or was never mailed is `410 token_spent` — one answer, because telling them apart would say whether a code was ever sent to that address; the recovery is the same, ask for a new code. The address is trimmed and compared case-insensitively, so the address typed at the request and here need not match in case. \n\n**The answer is a `clientSignInResult`**, like the password login: tokens, or a `twoFactorChallenge` when the person has an authenticator or the app requires one. A pending-verification account that spends a code is activated — reading a mail at that address is the proof verification asks for.",
|
|
4815
|
+
"requestBody": {
|
|
4816
|
+
"required": true,
|
|
4817
|
+
"content": {
|
|
4818
|
+
"application/json": {
|
|
4819
|
+
"schema": {
|
|
4820
|
+
"$ref": "#/components/schemas/client-login-code-verify-request"
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
},
|
|
4386
4827
|
"/api/client/register": {
|
|
4387
4828
|
"post": {
|
|
4388
4829
|
"operationId": "post_api_client_register",
|
|
@@ -4407,7 +4848,7 @@
|
|
|
4407
4848
|
}
|
|
4408
4849
|
}
|
|
4409
4850
|
},
|
|
4410
|
-
"description": "**`202` and an empty body for every request policy allows** — a new address, one this app already knows and one it does not answer identically, in status, body and timing. An answer that depended on existence would be the account-enumeration oracle the whole client family is built to avoid. The account cannot log in until the mailed link is spent; `POST /api/client/verify-email` is what does that. \n\n**An address on an account still `pending_verification` is re-registered, not ignored.** The password and display name from this call replace what is stored, every outstanding verification link for the address stops working, and a fresh one is mailed. Otherwise whoever typed an address first would own the password of the account its real owner later verifies. An address on an `active` account changes nothing and sends nothing — that account has already been proven, and its way back in is `POST /api/client/password/reset`. Neither case is visible in the answer. \n\nThe refusals it *does* make are about policy or about what the caller typed, never about a person. `403 registration_closed` when the app has self-registration off and `403 domain_not_allowed` when the address is outside `allowed_domains`: both are the developer's own configuration, and a stranger learns the app's policy rather than who is in it. **A password under twelve characters is part of that `400 validation_error`** and not a code of its own — the minimum is the `password` field's schema rule, and the error names the field, which is what a form needs to mark it. `404 not_found` names an **app identifier no app carries**, and never an address: an app identifier is already public (it is in the MCP metadata path and in the developer's own URLs), while collapsing it into `registration_closed` sent a developer who mistyped their own identifier hunting a configuration bug that was not there. `409 target_state_conflict` when the app has
|
|
4851
|
+
"description": "**`202` and an empty body for every request policy allows** — a new address, one this app already knows and one it does not answer identically, in status, body and timing. An answer that depended on existence would be the account-enumeration oracle the whole client family is built to avoid. The account cannot log in until the mailed link is spent; `POST /api/client/verify-email` is what does that. \n\n**An address on an account still `pending_verification` is re-registered, not ignored.** The password and display name from this call replace what is stored, every outstanding verification link for the address stops working, and a fresh one is mailed. Otherwise whoever typed an address first would own the password of the account its real owner later verifies. An address on an `active` account changes nothing and sends nothing — that account has already been proven, and its way back in is `POST /api/client/password/reset`. Neither case is visible in the answer. \n\nThe refusals it *does* make are about policy or about what the caller typed, never about a person. `403 registration_closed` when the app has self-registration off and `403 domain_not_allowed` when the address is outside `allowed_domains`: both are the developer's own configuration, and a stranger learns the app's policy rather than who is in it. **A password under twelve characters is part of that `400 validation_error`** and not a code of its own — the minimum is the `password` field's schema rule, and the error names the field, which is what a form needs to mark it. The same `400` names `password` when one is missing while the app's password method is on, or sent while it is off: an email-code-only app registers people without one. `404 not_found` names an **app identifier no app carries**, and never an address: an app identifier is already public (it is in the MCP metadata path and in the developer's own URLs), while collapsing it into `registration_closed` sent a developer who mistyped their own identifier hunting a configuration bug that was not there. `409 target_state_conflict` when the app has no default role — there would be no role to give the person. An app with no `verify_url` is not refused: the mailed link points at the hosted confirmation page instead. \n\n**`409 quota_exceeded` when the org is at its `max_end_users` limit**, counted across every app of the org. It is the one refusal here that is answered **before the address is looked at** — and that ordering is the point rather than an implementation detail: a quota checked after the existence branch would answer `202` for an address the app already knows and `409` for one it does not, which is precisely the enumeration oracle every other line of this route exists to close. At the quota, every registration is refused identically, including one that would only have re-mailed a pending account's link.",
|
|
4411
4852
|
"requestBody": {
|
|
4412
4853
|
"required": true,
|
|
4413
4854
|
"content": {
|
|
@@ -4435,7 +4876,7 @@
|
|
|
4435
4876
|
"content": {
|
|
4436
4877
|
"application/json": {
|
|
4437
4878
|
"schema": {
|
|
4438
|
-
"$ref": "#/components/schemas/
|
|
4879
|
+
"$ref": "#/components/schemas/client-sign-in-result"
|
|
4439
4880
|
}
|
|
4440
4881
|
}
|
|
4441
4882
|
}
|
|
@@ -4451,7 +4892,7 @@
|
|
|
4451
4892
|
}
|
|
4452
4893
|
}
|
|
4453
4894
|
},
|
|
4454
|
-
"description": "**The answer is a session, not a `204
|
|
4895
|
+
"description": "**The answer is a session, not a `204`** — or, as on every sign-in step, a `twoFactorChallenge` when the app requires two-factor (`clientSignInResult`). Somebody who has just proved they can read the mail should not be asked to type their password again on the next screen, and the app has an access token to carry them into it. The token is spent first and the account is activated second, as **two writes**: the spend is the atomic one, so a link opened twice cannot mint two sessions, but a process that died between them would leave a spent token on an account still `pending_verification`, whose recovery is `POST /api/client/resend-verification`. Spending the token also proves the address, so a later `PATCH` may return the account to `active` after a block. \n\n**One refusal for every token that does not work: `410 token_spent`** — unknown, past its twenty-four hours, or already used. There is one code because distinguishing them would tell a stranger whether a token ever existed, and because the recovery is the same in all three cases: ask for a fresh link with `POST /api/client/resend-verification`. An app rendering this refusal should offer that and nothing conditional on which of the three it was.",
|
|
4455
4896
|
"requestBody": {
|
|
4456
4897
|
"required": true,
|
|
4457
4898
|
"content": {
|
|
@@ -4494,28 +4935,197 @@
|
|
|
4494
4935
|
"content": {
|
|
4495
4936
|
"application/json": {
|
|
4496
4937
|
"schema": {
|
|
4497
|
-
"$ref": "#/components/schemas/client-resend-verification-request"
|
|
4938
|
+
"$ref": "#/components/schemas/client-resend-verification-request"
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
},
|
|
4945
|
+
"/api/client/password/reset": {
|
|
4946
|
+
"post": {
|
|
4947
|
+
"operationId": "post_api_client_password_reset",
|
|
4948
|
+
"summary": "Mails an app user a reset link, and answers the same either way.",
|
|
4949
|
+
"tags": [
|
|
4950
|
+
"client-auth"
|
|
4951
|
+
],
|
|
4952
|
+
"security": [],
|
|
4953
|
+
"parameters": [],
|
|
4954
|
+
"responses": {
|
|
4955
|
+
"202": {
|
|
4956
|
+
"description": "Success."
|
|
4957
|
+
},
|
|
4958
|
+
"default": {
|
|
4959
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `not_found`, `method_not_allowed`.",
|
|
4960
|
+
"content": {
|
|
4961
|
+
"application/json": {
|
|
4962
|
+
"schema": {
|
|
4963
|
+
"$ref": "#/components/schemas/api-error"
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
},
|
|
4969
|
+
"description": "The pair of app identifier and address is the identifier: an app user's address is unique only within their app. `403 method_not_allowed` when the app has the password method off — a reset link whose confirmation would be refused is not mailed; the code names the app's policy, not a person. Status, body and timing are identical for a known and an unknown address. An account with no password — one created through an identity provider — is mailed nothing and still answers `202`. `404 not_found` is the **app identifier**, never the address. The link points at the app's `reset_url`, or at the hosted reset page when the app has configured none.",
|
|
4970
|
+
"requestBody": {
|
|
4971
|
+
"required": true,
|
|
4972
|
+
"content": {
|
|
4973
|
+
"application/json": {
|
|
4974
|
+
"schema": {
|
|
4975
|
+
"$ref": "#/components/schemas/client-password-reset-request"
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
},
|
|
4982
|
+
"/api/client/password/reset/confirm": {
|
|
4983
|
+
"post": {
|
|
4984
|
+
"operationId": "post_api_client_password_reset_confirm",
|
|
4985
|
+
"summary": "Spends a reset token, sets the new password and answers a fresh session.",
|
|
4986
|
+
"tags": [
|
|
4987
|
+
"client-auth"
|
|
4988
|
+
],
|
|
4989
|
+
"security": [],
|
|
4990
|
+
"parameters": [],
|
|
4991
|
+
"responses": {
|
|
4992
|
+
"200": {
|
|
4993
|
+
"description": "Success.",
|
|
4994
|
+
"content": {
|
|
4995
|
+
"application/json": {
|
|
4996
|
+
"schema": {
|
|
4997
|
+
"$ref": "#/components/schemas/client-sign-in-result"
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
},
|
|
5002
|
+
"default": {
|
|
5003
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `token_spent`, `method_not_allowed`.",
|
|
5004
|
+
"content": {
|
|
5005
|
+
"application/json": {
|
|
5006
|
+
"schema": {
|
|
5007
|
+
"$ref": "#/components/schemas/api-error"
|
|
5008
|
+
}
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
},
|
|
5013
|
+
"description": "**A new password does not bypass the second factor**: a person with an authenticator, or in an app that requires one, gets a `twoFactorChallenge` instead of tokens (`clientSignInResult`), and the authenticator stays on. `403 method_not_allowed` when the app has the password method off. \n\n**Every refresh family of that account is revoked**, then a fresh pair is minted for the caller — a forgotten password is one of the two states where somebody else may be holding a live session, and the person completing the reset is the one who should keep theirs. The account is activated if it was still `pending_verification`: reading a mail at that address is the same proof verification asks for. \n\n**One refusal for every token that does not work: `410 token_spent`** — unknown, past its hour, or already used. There is one code because distinguishing them would tell a stranger whether a token ever existed, and the recovery is identical either way: ask for a new link. A replacement password under twelve characters is a `400 validation_error` naming the `new_password` field — the twelve-character minimum is that field's schema rule, and it is refused the way any other malformed field is.",
|
|
5014
|
+
"requestBody": {
|
|
5015
|
+
"required": true,
|
|
5016
|
+
"content": {
|
|
5017
|
+
"application/json": {
|
|
5018
|
+
"schema": {
|
|
5019
|
+
"$ref": "#/components/schemas/client-password-reset-confirm-request"
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
},
|
|
5026
|
+
"/api/client/invitations/accept": {
|
|
5027
|
+
"post": {
|
|
5028
|
+
"operationId": "post_api_client_invitations_accept",
|
|
5029
|
+
"summary": "Spends an invitation token, creates or activates the app user and answers a session.",
|
|
5030
|
+
"tags": [
|
|
5031
|
+
"client-auth"
|
|
5032
|
+
],
|
|
5033
|
+
"security": [],
|
|
5034
|
+
"parameters": [],
|
|
5035
|
+
"responses": {
|
|
5036
|
+
"200": {
|
|
5037
|
+
"description": "Success.",
|
|
5038
|
+
"content": {
|
|
5039
|
+
"application/json": {
|
|
5040
|
+
"schema": {
|
|
5041
|
+
"$ref": "#/components/schemas/client-sign-in-result"
|
|
5042
|
+
}
|
|
5043
|
+
}
|
|
5044
|
+
}
|
|
5045
|
+
},
|
|
5046
|
+
"default": {
|
|
5047
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `token_spent`, `email_taken`, `target_state_conflict`, `quota_exceeded`.",
|
|
5048
|
+
"content": {
|
|
5049
|
+
"application/json": {
|
|
5050
|
+
"schema": {
|
|
5051
|
+
"$ref": "#/components/schemas/api-error"
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
},
|
|
5057
|
+
"description": "**An app invitation, not a team one.** `POST /api/org/invitations/accept` is the other space and answers `204`; this one answers a session, because the person is landing in the developer's app and there is no second door for them to sign in through. The role is the one the invitation fixed at creation, so a later change to the app's default role does not re-aim a link already in somebody's inbox, and the invitation **bypasses `allowed_domains`** — a developer inviting somebody by hand has already made the decision the whitelist automates. The answer is a `clientSignInResult`: a `twoFactorChallenge` instead of tokens when the app requires two-factor. `password` is required while the app's password method is on and refused while it is off, both as `400 validation_error` naming the field. \n\n**One refusal for every token that does not work: `410 token_spent`** — unknown, expired past the seven days, revoked by the developer, or already accepted. There is one code because telling them apart would say whether a token ever existed, and because the one thing the holder of a dead link can do is ask the developer for a new one, whichever of the four it was. A chosen password under twelve characters is part of the `400 validation_error`, naming the `password` field. `409 email_taken` is an address this app has acquired since the invitation was written **as an account that is already in use** — the invitation stays outstanding rather than being spent, so the developer can revoke it or point the person at the login. An address that registered itself and is still `pending_verification` is not that state: accepting sets the password the invitee just chose, activates the account and gives it the invitation's role, because reading the invitation mail proves the address the verification link was waiting on. \n\n`409 target_state_conflict` names `role_id` with rule `not_set` when the role the invitation was fixed to has since been deleted and the app has no default role to fall back on: there is no access to hand the acceptor, and creating an account with none would be worse than saying so. \n\n**`409 quota_exceeded` when accepting would CREATE an account and the org is at its `max_end_users` limit**, counted across every app of the org. An invitation that names a row the developer already created, and one whose address is held by an unfinished self-registration, both finish an account that already counts — those are not refused, because the org is not one account larger afterwards. The token is not spent by the refusal: the developer can raise the limit, or delete somebody, and the same link still works.",
|
|
5058
|
+
"requestBody": {
|
|
5059
|
+
"required": true,
|
|
5060
|
+
"content": {
|
|
5061
|
+
"application/json": {
|
|
5062
|
+
"schema": {
|
|
5063
|
+
"$ref": "#/components/schemas/client-accept-invitation-request"
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
},
|
|
5070
|
+
"/api/client/refresh": {
|
|
5071
|
+
"post": {
|
|
5072
|
+
"operationId": "post_api_client_refresh",
|
|
5073
|
+
"summary": "Rotates an app-user refresh token and mints a fresh access token.",
|
|
5074
|
+
"tags": [
|
|
5075
|
+
"client-auth"
|
|
5076
|
+
],
|
|
5077
|
+
"security": [],
|
|
5078
|
+
"parameters": [],
|
|
5079
|
+
"responses": {
|
|
5080
|
+
"200": {
|
|
5081
|
+
"description": "Success.",
|
|
5082
|
+
"content": {
|
|
5083
|
+
"application/json": {
|
|
5084
|
+
"schema": {
|
|
5085
|
+
"$ref": "#/components/schemas/session-tokens"
|
|
5086
|
+
}
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
},
|
|
5090
|
+
"default": {
|
|
5091
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `token_expired`, `token_revoked`.",
|
|
5092
|
+
"content": {
|
|
5093
|
+
"application/json": {
|
|
5094
|
+
"schema": {
|
|
5095
|
+
"$ref": "#/components/schemas/api-error"
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
}
|
|
5100
|
+
},
|
|
5101
|
+
"description": "The account is re-proved here, not just the token: refresh is where every session eventually re-proves itself, so a user who was blocked or deleted loses the family here even if the proactive revoke had not landed. A family minted from a `resource`-carrying token exchange keeps its audience across every rotation.",
|
|
5102
|
+
"requestBody": {
|
|
5103
|
+
"required": true,
|
|
5104
|
+
"content": {
|
|
5105
|
+
"application/json": {
|
|
5106
|
+
"schema": {
|
|
5107
|
+
"$ref": "#/components/schemas/client-refresh-request"
|
|
4498
5108
|
}
|
|
4499
5109
|
}
|
|
4500
5110
|
}
|
|
4501
5111
|
}
|
|
4502
5112
|
}
|
|
4503
5113
|
},
|
|
4504
|
-
"/api/client/
|
|
5114
|
+
"/api/client/logout": {
|
|
4505
5115
|
"post": {
|
|
4506
|
-
"operationId": "
|
|
4507
|
-
"summary": "
|
|
5116
|
+
"operationId": "post_api_client_logout",
|
|
5117
|
+
"summary": "Revokes an app-user refresh family.",
|
|
4508
5118
|
"tags": [
|
|
4509
5119
|
"client-auth"
|
|
4510
5120
|
],
|
|
4511
5121
|
"security": [],
|
|
4512
5122
|
"parameters": [],
|
|
4513
5123
|
"responses": {
|
|
4514
|
-
"
|
|
5124
|
+
"204": {
|
|
4515
5125
|
"description": "Success."
|
|
4516
5126
|
},
|
|
4517
5127
|
"default": {
|
|
4518
|
-
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error
|
|
5128
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`.",
|
|
4519
5129
|
"content": {
|
|
4520
5130
|
"application/json": {
|
|
4521
5131
|
"schema": {
|
|
@@ -4525,27 +5135,37 @@
|
|
|
4525
5135
|
}
|
|
4526
5136
|
}
|
|
4527
5137
|
},
|
|
4528
|
-
"description": "
|
|
5138
|
+
"description": "**`204`, and a token the server does not recognise gets it too** — the end state a caller asked for is the end state they get, and distinguishing the two would say whether a token ever existed. It answered a body until 2026-09-05, reporting what was left of the session at the identity provider; that belonged to the hosted login flow, where Fleetless owned the browser. The developer's app owns it now and redirects to its own provider itself, knowing which one it is. Open `/realtime` sockets for the session are closed.",
|
|
4529
5139
|
"requestBody": {
|
|
4530
5140
|
"required": true,
|
|
4531
5141
|
"content": {
|
|
4532
5142
|
"application/json": {
|
|
4533
5143
|
"schema": {
|
|
4534
|
-
"$ref": "#/components/schemas/client-
|
|
5144
|
+
"$ref": "#/components/schemas/client-logout-request"
|
|
4535
5145
|
}
|
|
4536
5146
|
}
|
|
4537
5147
|
}
|
|
4538
5148
|
}
|
|
4539
5149
|
}
|
|
4540
5150
|
},
|
|
4541
|
-
"/api/client/password/
|
|
5151
|
+
"/api/client/password/change": {
|
|
4542
5152
|
"post": {
|
|
4543
|
-
"operationId": "
|
|
4544
|
-
"summary": "
|
|
5153
|
+
"operationId": "post_api_client_password_change",
|
|
5154
|
+
"summary": "Changes an app user's own password and answers a fresh session.",
|
|
4545
5155
|
"tags": [
|
|
4546
5156
|
"client-auth"
|
|
4547
5157
|
],
|
|
4548
|
-
"security": [
|
|
5158
|
+
"security": [
|
|
5159
|
+
{
|
|
5160
|
+
"developerSession": []
|
|
5161
|
+
},
|
|
5162
|
+
{
|
|
5163
|
+
"clientToken": []
|
|
5164
|
+
},
|
|
5165
|
+
{
|
|
5166
|
+
"serverKey": []
|
|
5167
|
+
}
|
|
5168
|
+
],
|
|
4549
5169
|
"parameters": [],
|
|
4550
5170
|
"responses": {
|
|
4551
5171
|
"200": {
|
|
@@ -4559,7 +5179,7 @@
|
|
|
4559
5179
|
}
|
|
4560
5180
|
},
|
|
4561
5181
|
"default": {
|
|
4562
|
-
"description": "An error envelope. Codes this route is known to answer: `
|
|
5182
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`, `validation_error`, `invalid_credentials`, `target_state_conflict`, `method_not_allowed`.",
|
|
4563
5183
|
"content": {
|
|
4564
5184
|
"application/json": {
|
|
4565
5185
|
"schema": {
|
|
@@ -4569,27 +5189,37 @@
|
|
|
4569
5189
|
}
|
|
4570
5190
|
}
|
|
4571
5191
|
},
|
|
4572
|
-
"description": "
|
|
5192
|
+
"description": "`403 method_not_allowed` when the app has the password method off: a stored password stays stored but is not in use, so it is not changed either. The guard admits all three caller kinds, but a password belongs to an app user specifically — a developer bearer or a server key reaching this is `401 unauthorized`. Every other session of the account ends; the answer is the replacement pair, so the tab that made the change stays signed in. An app user belongs to one app, so \"every session\" is this app's. An account that has **no password** — an OIDC-only app user, which the schema admits — answers `409 target_state_conflict` naming the `password` field with rule `not_set`, not `401`: the session is live and the token is fine, it is the account that has nothing to change, and telling such a caller to sign in again sends them round a loop that ends here.",
|
|
4573
5193
|
"requestBody": {
|
|
4574
5194
|
"required": true,
|
|
4575
5195
|
"content": {
|
|
4576
5196
|
"application/json": {
|
|
4577
5197
|
"schema": {
|
|
4578
|
-
"$ref": "#/components/schemas/
|
|
5198
|
+
"$ref": "#/components/schemas/password-change-request"
|
|
4579
5199
|
}
|
|
4580
5200
|
}
|
|
4581
5201
|
}
|
|
4582
5202
|
}
|
|
4583
5203
|
}
|
|
4584
5204
|
},
|
|
4585
|
-
"/api/client/
|
|
4586
|
-
"
|
|
4587
|
-
"operationId": "
|
|
4588
|
-
"summary": "
|
|
5205
|
+
"/api/client/me": {
|
|
5206
|
+
"get": {
|
|
5207
|
+
"operationId": "get_api_client_me",
|
|
5208
|
+
"summary": "Answers who the calling token is and what it is allowed to reach.",
|
|
4589
5209
|
"tags": [
|
|
4590
5210
|
"client-auth"
|
|
4591
5211
|
],
|
|
4592
|
-
"security": [
|
|
5212
|
+
"security": [
|
|
5213
|
+
{
|
|
5214
|
+
"developerSession": []
|
|
5215
|
+
},
|
|
5216
|
+
{
|
|
5217
|
+
"clientToken": []
|
|
5218
|
+
},
|
|
5219
|
+
{
|
|
5220
|
+
"serverKey": []
|
|
5221
|
+
}
|
|
5222
|
+
],
|
|
4593
5223
|
"parameters": [],
|
|
4594
5224
|
"responses": {
|
|
4595
5225
|
"200": {
|
|
@@ -4597,13 +5227,13 @@
|
|
|
4597
5227
|
"content": {
|
|
4598
5228
|
"application/json": {
|
|
4599
5229
|
"schema": {
|
|
4600
|
-
"$ref": "#/components/schemas/
|
|
5230
|
+
"$ref": "#/components/schemas/client-identity"
|
|
4601
5231
|
}
|
|
4602
5232
|
}
|
|
4603
5233
|
}
|
|
4604
5234
|
},
|
|
4605
5235
|
"default": {
|
|
4606
|
-
"description": "An error envelope. Codes this route is known to answer: `
|
|
5236
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`.",
|
|
4607
5237
|
"content": {
|
|
4608
5238
|
"application/json": {
|
|
4609
5239
|
"schema": {
|
|
@@ -4613,23 +5243,13 @@
|
|
|
4613
5243
|
}
|
|
4614
5244
|
}
|
|
4615
5245
|
},
|
|
4616
|
-
"description": "
|
|
4617
|
-
"requestBody": {
|
|
4618
|
-
"required": true,
|
|
4619
|
-
"content": {
|
|
4620
|
-
"application/json": {
|
|
4621
|
-
"schema": {
|
|
4622
|
-
"$ref": "#/components/schemas/client-accept-invitation-request"
|
|
4623
|
-
}
|
|
4624
|
-
}
|
|
4625
|
-
}
|
|
4626
|
-
}
|
|
5246
|
+
"description": "The one route that answers for all three caller kinds — a developer bearer, an app-user bearer and a server key — which is why the shape names each of `developer_id`, `app_user_id` and `server_key_id` and fills exactly one."
|
|
4627
5247
|
}
|
|
4628
5248
|
},
|
|
4629
|
-
"/api/client/
|
|
5249
|
+
"/api/client/two-factor/verify": {
|
|
4630
5250
|
"post": {
|
|
4631
|
-
"operationId": "
|
|
4632
|
-
"summary": "
|
|
5251
|
+
"operationId": "post_api_client_two_factor_verify",
|
|
5252
|
+
"summary": "Answers a two-factor challenge with an authenticator or recovery code, and answers the session.",
|
|
4633
5253
|
"tags": [
|
|
4634
5254
|
"client-auth"
|
|
4635
5255
|
],
|
|
@@ -4647,7 +5267,7 @@
|
|
|
4647
5267
|
}
|
|
4648
5268
|
},
|
|
4649
5269
|
"default": {
|
|
4650
|
-
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `
|
|
5270
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `invalid_code`, `token_spent`.",
|
|
4651
5271
|
"content": {
|
|
4652
5272
|
"application/json": {
|
|
4653
5273
|
"schema": {
|
|
@@ -4657,34 +5277,46 @@
|
|
|
4657
5277
|
}
|
|
4658
5278
|
}
|
|
4659
5279
|
},
|
|
4660
|
-
"description": "The
|
|
5280
|
+
"description": "The challenge is the one a sign-in step answered with `two_factor_required`; it lives five minutes and takes five wrong codes, after which it is `410 token_spent` and the sign-in starts over. A wrong code is `400 invalid_code` with `details.attempts_left`. **A code is accepted at most once**: the same authenticator code sent twice, even at the same moment, signs in exactly once. A recovery code is spent by its use and audited as `app_user.recovery_code_used`. Exactly one of `code` and `recovery_code`, or `400 validation_error`.",
|
|
4661
5281
|
"requestBody": {
|
|
4662
5282
|
"required": true,
|
|
4663
5283
|
"content": {
|
|
4664
5284
|
"application/json": {
|
|
4665
5285
|
"schema": {
|
|
4666
|
-
"$ref": "#/components/schemas/client-
|
|
5286
|
+
"$ref": "#/components/schemas/client-two-factor-verify-request"
|
|
4667
5287
|
}
|
|
4668
5288
|
}
|
|
4669
5289
|
}
|
|
4670
5290
|
}
|
|
4671
5291
|
}
|
|
4672
5292
|
},
|
|
4673
|
-
"/api/client/
|
|
5293
|
+
"/api/client/two-factor/setup": {
|
|
4674
5294
|
"post": {
|
|
4675
|
-
"operationId": "
|
|
4676
|
-
"summary": "
|
|
5295
|
+
"operationId": "post_api_client_two_factor_setup",
|
|
5296
|
+
"summary": "Starts an authenticator setup and answers its secret and otpauth URL.",
|
|
4677
5297
|
"tags": [
|
|
4678
5298
|
"client-auth"
|
|
4679
5299
|
],
|
|
4680
|
-
"security": [
|
|
5300
|
+
"security": [
|
|
5301
|
+
{
|
|
5302
|
+
"clientToken": []
|
|
5303
|
+
},
|
|
5304
|
+
{}
|
|
5305
|
+
],
|
|
4681
5306
|
"parameters": [],
|
|
4682
5307
|
"responses": {
|
|
4683
|
-
"
|
|
4684
|
-
"description": "Success."
|
|
5308
|
+
"200": {
|
|
5309
|
+
"description": "Success.",
|
|
5310
|
+
"content": {
|
|
5311
|
+
"application/json": {
|
|
5312
|
+
"schema": {
|
|
5313
|
+
"$ref": "#/components/schemas/two-factor-setup-response"
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
4685
5317
|
},
|
|
4686
5318
|
"default": {
|
|
4687
|
-
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`.",
|
|
5319
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `token_spent`, `unauthorized`, `target_state_conflict`.",
|
|
4688
5320
|
"content": {
|
|
4689
5321
|
"application/json": {
|
|
4690
5322
|
"schema": {
|
|
@@ -4694,36 +5326,31 @@
|
|
|
4694
5326
|
}
|
|
4695
5327
|
}
|
|
4696
5328
|
},
|
|
4697
|
-
"description": "
|
|
5329
|
+
"description": "**Two ways in, decided in the handler.** During sign-in the body carries the `two_factor_setup_required` challenge, and that is the credential; from the app's own account settings the app user's bearer is, with no challenge and an empty or missing body. Neither is `401 unauthorized`, and a dead challenge is `410 token_spent`. `409 target_state_conflict` names `two_factor` with rule `off` when the app's policy is `off`. The secret is not in use until `POST /api/client/two-factor/setup/confirm` accepts a code from it; a second call replaces a pending secret, and an account that already has an authenticator keeps it until the new one is confirmed.",
|
|
4698
5330
|
"requestBody": {
|
|
4699
|
-
"required":
|
|
5331
|
+
"required": false,
|
|
4700
5332
|
"content": {
|
|
4701
5333
|
"application/json": {
|
|
4702
5334
|
"schema": {
|
|
4703
|
-
"$ref": "#/components/schemas/client-
|
|
5335
|
+
"$ref": "#/components/schemas/client-two-factor-setup-request"
|
|
4704
5336
|
}
|
|
4705
5337
|
}
|
|
4706
5338
|
}
|
|
4707
5339
|
}
|
|
4708
5340
|
}
|
|
4709
5341
|
},
|
|
4710
|
-
"/api/client/
|
|
5342
|
+
"/api/client/two-factor/setup/confirm": {
|
|
4711
5343
|
"post": {
|
|
4712
|
-
"operationId": "
|
|
4713
|
-
"summary": "
|
|
5344
|
+
"operationId": "post_api_client_two_factor_setup_confirm",
|
|
5345
|
+
"summary": "Confirms the new authenticator with a code and answers the recovery codes and a session.",
|
|
4714
5346
|
"tags": [
|
|
4715
5347
|
"client-auth"
|
|
4716
5348
|
],
|
|
4717
5349
|
"security": [
|
|
4718
|
-
{
|
|
4719
|
-
"developerSession": []
|
|
4720
|
-
},
|
|
4721
5350
|
{
|
|
4722
5351
|
"clientToken": []
|
|
4723
5352
|
},
|
|
4724
|
-
{
|
|
4725
|
-
"serverKey": []
|
|
4726
|
-
}
|
|
5353
|
+
{}
|
|
4727
5354
|
],
|
|
4728
5355
|
"parameters": [],
|
|
4729
5356
|
"responses": {
|
|
@@ -4732,13 +5359,13 @@
|
|
|
4732
5359
|
"content": {
|
|
4733
5360
|
"application/json": {
|
|
4734
5361
|
"schema": {
|
|
4735
|
-
"$ref": "#/components/schemas/
|
|
5362
|
+
"$ref": "#/components/schemas/client-two-factor-setup-confirm-response"
|
|
4736
5363
|
}
|
|
4737
5364
|
}
|
|
4738
5365
|
}
|
|
4739
5366
|
},
|
|
4740
5367
|
"default": {
|
|
4741
|
-
"description": "An error envelope. Codes this route is known to answer: `
|
|
5368
|
+
"description": "An error envelope. Codes this route is known to answer: `rate_limited`, `validation_error`, `invalid_code`, `token_spent`, `unauthorized`.",
|
|
4742
5369
|
"content": {
|
|
4743
5370
|
"application/json": {
|
|
4744
5371
|
"schema": {
|
|
@@ -4748,23 +5375,23 @@
|
|
|
4748
5375
|
}
|
|
4749
5376
|
}
|
|
4750
5377
|
},
|
|
4751
|
-
"description": "The
|
|
5378
|
+
"description": "The same two ways in as `setup`. A code that does not match the pending secret is `400 invalid_code`; no pending setup, or a dead challenge, is `410 token_spent`. On success the authenticator is on, ten recovery codes are issued — shown this once, any earlier set void — and the answer carries a session: the one the sign-in was waiting for, or, from account settings, a fresh one while every other session of the account ends. Audited as `app_user.two_factor_enabled`.",
|
|
4752
5379
|
"requestBody": {
|
|
4753
5380
|
"required": true,
|
|
4754
5381
|
"content": {
|
|
4755
5382
|
"application/json": {
|
|
4756
5383
|
"schema": {
|
|
4757
|
-
"$ref": "#/components/schemas/
|
|
5384
|
+
"$ref": "#/components/schemas/client-two-factor-setup-confirm-request"
|
|
4758
5385
|
}
|
|
4759
5386
|
}
|
|
4760
5387
|
}
|
|
4761
5388
|
}
|
|
4762
5389
|
}
|
|
4763
5390
|
},
|
|
4764
|
-
"/api/client/
|
|
4765
|
-
"
|
|
4766
|
-
"operationId": "
|
|
4767
|
-
"summary": "
|
|
5391
|
+
"/api/client/two-factor": {
|
|
5392
|
+
"delete": {
|
|
5393
|
+
"operationId": "delete_api_client_two_factor",
|
|
5394
|
+
"summary": "Turns the signed-in app user's authenticator off.",
|
|
4768
5395
|
"tags": [
|
|
4769
5396
|
"client-auth"
|
|
4770
5397
|
],
|
|
@@ -4781,18 +5408,11 @@
|
|
|
4781
5408
|
],
|
|
4782
5409
|
"parameters": [],
|
|
4783
5410
|
"responses": {
|
|
4784
|
-
"
|
|
4785
|
-
"description": "Success."
|
|
4786
|
-
"content": {
|
|
4787
|
-
"application/json": {
|
|
4788
|
-
"schema": {
|
|
4789
|
-
"$ref": "#/components/schemas/client-identity"
|
|
4790
|
-
}
|
|
4791
|
-
}
|
|
4792
|
-
}
|
|
5411
|
+
"204": {
|
|
5412
|
+
"description": "Success."
|
|
4793
5413
|
},
|
|
4794
5414
|
"default": {
|
|
4795
|
-
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`.",
|
|
5415
|
+
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`, `rate_limited`, `validation_error`, `invalid_code`, `target_state_conflict`.",
|
|
4796
5416
|
"content": {
|
|
4797
5417
|
"application/json": {
|
|
4798
5418
|
"schema": {
|
|
@@ -4802,7 +5422,17 @@
|
|
|
4802
5422
|
}
|
|
4803
5423
|
}
|
|
4804
5424
|
},
|
|
4805
|
-
"description": "The
|
|
5425
|
+
"description": "The app user's own door; a developer bearer or a server key is `401 unauthorized`, because the factor is the person's. A current code proves they still hold the authenticator: a stolen session alone cannot remove it. The authenticator and every recovery code go. `409 target_state_conflict` names `two_factor` with rule `required` while the app requires two-factor, and with rule `off` when there is none to remove. Audited as `app_user.two_factor_disabled`. The developer's support door is `DELETE /api/apps/:id/users/:userId/two-factor`.",
|
|
5426
|
+
"requestBody": {
|
|
5427
|
+
"required": true,
|
|
5428
|
+
"content": {
|
|
5429
|
+
"application/json": {
|
|
5430
|
+
"schema": {
|
|
5431
|
+
"$ref": "#/components/schemas/client-two-factor-disable-request"
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
4806
5436
|
}
|
|
4807
5437
|
},
|
|
4808
5438
|
"/api/client/providers": {
|
|
@@ -4997,7 +5627,7 @@
|
|
|
4997
5627
|
}
|
|
4998
5628
|
}
|
|
4999
5629
|
},
|
|
5000
|
-
"description": "**One callback URL for every app and every provider**, and the value of `appAuthConfig.oidc_callback_url` — the string a developer registers at their IdP. `CLIENT_OIDC_CALLBACK_PATH` in `client-auth.ts` is the single spelling of this path; the URL is that path on the cloud's canonical public base, never a friendly alias, because the provider compares the redirect target against the one string it was given. \n\n**Rate limited per ip, generously.** The first draft left this route unlimited on the argument that the caller is an identity provider redirecting somebody's browser, so a limiter would drop real sign-ins on the strength of traffic none of those people sent. The cost of that argument is a `state` obtained from one `start` being replayable for the interaction's full ten minutes, unbounded and unauthenticated, with every replay driving a server-side POST to the developer's token endpoint and one audit row into their org — an amplifier against a third party. The interaction is now spent on **every** terminal outcome, refusals included, which closes the replay itself; the limiter is the ceiling on how fast the attempts may arrive at all. It is per ip and sized for a browser, so a person completing a sign-in never meets it. `429 rate_limited` is the one `apiError` this route can answer, and it is not a sign-in outcome — it is a refusal to begin the work, which is why it does not ride back to the app as an `?error=`. \n\nThe query is the **provider's** rather than a Fleetless shape, and `clientOidcCallbackQuery` describes it **without being strict**: `state` always, `code` on success, `error` and `error_description` on the provider's own refusal, and whatever else that provider adds — RFC 9207's `iss`, a `session_state`, a vendor field. Refusing those would refuse conforming providers, the trap `POST /mcp/oauth/register` documents avoiding. `state` is the required field because it is the only one Fleetless minted. \n\n**It lists `rate_limited` and no other code, because every sign-in outcome it has is a redirect.** Success and failure alike are a `302` to the app's own `redirect_uri`: `?code=…&state=…` when a session was resolved, `?error=<clientOidcErrorCode>&state=…` when it was not, so the app renders its own message and can bind either answer to the request it started.
|
|
5630
|
+
"description": "**One callback URL for every app and every provider**, and the value of `appAuthConfig.oidc_callback_url` — the string a developer registers at their IdP. `CLIENT_OIDC_CALLBACK_PATH` in `client-auth.ts` is the single spelling of this path; the URL is that path on the cloud's canonical public base, never a friendly alias, because the provider compares the redirect target against the one string it was given. \n\n**Rate limited per ip, generously.** The first draft left this route unlimited on the argument that the caller is an identity provider redirecting somebody's browser, so a limiter would drop real sign-ins on the strength of traffic none of those people sent. The cost of that argument is a `state` obtained from one `start` being replayable for the interaction's full ten minutes, unbounded and unauthenticated, with every replay driving a server-side POST to the developer's token endpoint and one audit row into their org — an amplifier against a third party. The interaction is now spent on **every** terminal outcome, refusals included, which closes the replay itself; the limiter is the ceiling on how fast the attempts may arrive at all. It is per ip and sized for a browser, so a person completing a sign-in never meets it. `429 rate_limited` is the one `apiError` this route can answer, and it is not a sign-in outcome — it is a refusal to begin the work, which is why it does not ride back to the app as an `?error=`. \n\nThe query is the **provider's** rather than a Fleetless shape, and `clientOidcCallbackQuery` describes it **without being strict**: `state` always, `code` on success, `error` and `error_description` on the provider's own refusal, and whatever else that provider adds — RFC 9207's `iss`, a `session_state`, a vendor field. Refusing those would refuse conforming providers, the trap `POST /mcp/oauth/register` documents avoiding. `state` is the required field because it is the only one Fleetless minted. \n\n**It lists `rate_limited` and no other code, because every sign-in outcome it has is a redirect.** Success and failure alike are a `302` to the app's own `redirect_uri`: `?code=…&state=…` when a session was resolved, `?error=<clientOidcErrorCode>&state=…` when it was not, so the app renders its own message and can bind either answer to the request it started. This route renders no page for an outcome. \n\n**The one exception is a `state` that resolves to no interaction** — unknown, hand-edited, or past its ten minutes. Then there is no confirmed redirect target to carry the answer to, and bouncing a browser to an unvalidated one is the hole the whole flow is arranged to avoid, so the cloud renders an HTML problem page at `400`. It is HTML rather than an `apiError`, which is why no code is listed: a code here would document an envelope no caller receives, and this manifest's other HTML pages (`GET /mcp/oauth/interaction/:id`, `GET /console/oauth/interaction/:id`) say their status in prose for the same reason."
|
|
5001
5631
|
}
|
|
5002
5632
|
},
|
|
5003
5633
|
"/api/client/oidc/exchange": {
|
|
@@ -8624,54 +9254,6 @@
|
|
|
8624
9254
|
},
|
|
8625
9255
|
"description": "A window longer than `USAGE_WINDOW_MAX_DAYS` is refused naming the field, not silently capped: a caller who asked for more than the platform will answer is owed a refusal, not a shorter answer they will mistake for the whole picture. `from_day <= to_day` is a cross-field rule no JSON Schema can express and is enforced here. The window is echoed back."
|
|
8626
9256
|
}
|
|
8627
|
-
},
|
|
8628
|
-
"/api/feedback": {
|
|
8629
|
-
"post": {
|
|
8630
|
-
"operationId": "post_api_feedback",
|
|
8631
|
-
"summary": "Sends a message from a developer to the people who build Fleetless.",
|
|
8632
|
-
"tags": [
|
|
8633
|
-
"org"
|
|
8634
|
-
],
|
|
8635
|
-
"security": [
|
|
8636
|
-
{
|
|
8637
|
-
"developerSession": []
|
|
8638
|
-
}
|
|
8639
|
-
],
|
|
8640
|
-
"parameters": [],
|
|
8641
|
-
"responses": {
|
|
8642
|
-
"202": {
|
|
8643
|
-
"description": "Success.",
|
|
8644
|
-
"content": {
|
|
8645
|
-
"application/json": {
|
|
8646
|
-
"schema": {
|
|
8647
|
-
"$ref": "#/components/schemas/feedback-response"
|
|
8648
|
-
}
|
|
8649
|
-
}
|
|
8650
|
-
}
|
|
8651
|
-
},
|
|
8652
|
-
"default": {
|
|
8653
|
-
"description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `validation_error`, `rate_limited`.",
|
|
8654
|
-
"content": {
|
|
8655
|
-
"application/json": {
|
|
8656
|
-
"schema": {
|
|
8657
|
-
"$ref": "#/components/schemas/api-error"
|
|
8658
|
-
}
|
|
8659
|
-
}
|
|
8660
|
-
}
|
|
8661
|
-
}
|
|
8662
|
-
},
|
|
8663
|
-
"description": "The message is stored before any mail is tried, so `202` means it is kept whatever `mail` says: `sent`, `failed`, or `not_configured` when this cloud has no feedback address. At most 10 messages per developer per hour; the 11th answers `429 rate_limited` with `retry_after_ms`. Replies come by mail, to the sender's address.",
|
|
8664
|
-
"requestBody": {
|
|
8665
|
-
"required": true,
|
|
8666
|
-
"content": {
|
|
8667
|
-
"application/json": {
|
|
8668
|
-
"schema": {
|
|
8669
|
-
"$ref": "#/components/schemas/feedback-request"
|
|
8670
|
-
}
|
|
8671
|
-
}
|
|
8672
|
-
}
|
|
8673
|
-
}
|
|
8674
|
-
}
|
|
8675
9257
|
}
|
|
8676
9258
|
},
|
|
8677
9259
|
"components": {
|
|
@@ -8701,16 +9283,22 @@
|
|
|
8701
9283
|
"minLength": 1,
|
|
8702
9284
|
"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."
|
|
8703
9285
|
},
|
|
8704
|
-
"
|
|
8705
|
-
"
|
|
8706
|
-
"
|
|
8707
|
-
|
|
8708
|
-
|
|
9286
|
+
"display_name": {
|
|
9287
|
+
"description": "An optional name, overriding whatever the invitation pre-filled. Absent keeps it.",
|
|
9288
|
+
"anyOf": [
|
|
9289
|
+
{
|
|
9290
|
+
"type": "string",
|
|
9291
|
+
"minLength": 1,
|
|
9292
|
+
"maxLength": 120
|
|
9293
|
+
},
|
|
9294
|
+
{
|
|
9295
|
+
"type": "null"
|
|
9296
|
+
}
|
|
9297
|
+
]
|
|
8709
9298
|
}
|
|
8710
9299
|
},
|
|
8711
9300
|
"required": [
|
|
8712
|
-
"token"
|
|
8713
|
-
"password"
|
|
9301
|
+
"token"
|
|
8714
9302
|
],
|
|
8715
9303
|
"additionalProperties": false
|
|
8716
9304
|
},
|
|
@@ -9013,7 +9601,7 @@
|
|
|
9013
9601
|
"type": "null"
|
|
9014
9602
|
}
|
|
9015
9603
|
],
|
|
9016
|
-
"description": "The page in the developer's app that accepts an invitation, with `{token}` where the token goes. `null`
|
|
9604
|
+
"description": "The page in the developer's app that accepts an invitation, with `{token}` where the token goes. `null` means the hosted page in `hosted_pages` is used."
|
|
9017
9605
|
},
|
|
9018
9606
|
"verify_url": {
|
|
9019
9607
|
"anyOf": [
|
|
@@ -9025,7 +9613,7 @@
|
|
|
9025
9613
|
"type": "null"
|
|
9026
9614
|
}
|
|
9027
9615
|
],
|
|
9028
|
-
"description": "The page that confirms a new address, with `{token}` where the token goes.
|
|
9616
|
+
"description": "The page that confirms a new address, with `{token}` where the token goes. `null` means the hosted page in `hosted_pages` is used."
|
|
9029
9617
|
},
|
|
9030
9618
|
"reset_url": {
|
|
9031
9619
|
"anyOf": [
|
|
@@ -9037,19 +9625,117 @@
|
|
|
9037
9625
|
"type": "null"
|
|
9038
9626
|
}
|
|
9039
9627
|
],
|
|
9040
|
-
"description": "The page that takes a new password, with `{token}` where the token goes."
|
|
9628
|
+
"description": "The page that takes a new password, with `{token}` where the token goes. `null` means the hosted page in `hosted_pages` is used."
|
|
9629
|
+
},
|
|
9630
|
+
"mcp_login_url": {
|
|
9631
|
+
"anyOf": [
|
|
9632
|
+
{
|
|
9633
|
+
"type": "string",
|
|
9634
|
+
"maxLength": 500
|
|
9635
|
+
},
|
|
9636
|
+
{
|
|
9637
|
+
"type": "null"
|
|
9638
|
+
}
|
|
9639
|
+
],
|
|
9640
|
+
"description": "The page an MCP authorization redirects to, with `{interaction}` where the interaction id goes. Not a token: the id names a pending request the server already holds, and the app authenticates the user itself before approving it. `null` means the hosted MCP sign-in in `hosted_pages` is used."
|
|
9641
|
+
},
|
|
9642
|
+
"app_url": {
|
|
9643
|
+
"anyOf": [
|
|
9644
|
+
{
|
|
9645
|
+
"type": "string",
|
|
9646
|
+
"maxLength": 500,
|
|
9647
|
+
"format": "uri"
|
|
9648
|
+
},
|
|
9649
|
+
{
|
|
9650
|
+
"type": "null"
|
|
9651
|
+
}
|
|
9652
|
+
],
|
|
9653
|
+
"description": "The app's own home page, linked as `Open <app>` when a hosted flow is done. `null` makes the hosted done page say `You can close this tab`."
|
|
9654
|
+
},
|
|
9655
|
+
"sign_in_methods": {
|
|
9656
|
+
"type": "object",
|
|
9657
|
+
"properties": {
|
|
9658
|
+
"password": {
|
|
9659
|
+
"type": "boolean",
|
|
9660
|
+
"description": "Whether app users may sign in with a password. Off refuses `POST /api/client/login` with `method_not_allowed`, and registration and invitations then take no password."
|
|
9661
|
+
},
|
|
9662
|
+
"email_code": {
|
|
9663
|
+
"type": "boolean",
|
|
9664
|
+
"description": "Whether app users may sign in with a six-digit code mailed to them, valid ten minutes. A code needs no URL, so it works in local development and in an app with no web UI."
|
|
9665
|
+
}
|
|
9666
|
+
},
|
|
9667
|
+
"required": [
|
|
9668
|
+
"password",
|
|
9669
|
+
"email_code"
|
|
9670
|
+
],
|
|
9671
|
+
"additionalProperties": false,
|
|
9672
|
+
"description": "Which sign-in methods the app offers: password, emailed code, or both — at least one. Identity providers stay on top of either. The default is password only."
|
|
9673
|
+
},
|
|
9674
|
+
"two_factor": {
|
|
9675
|
+
"type": "string",
|
|
9676
|
+
"enum": [
|
|
9677
|
+
"off",
|
|
9678
|
+
"optional",
|
|
9679
|
+
"required"
|
|
9680
|
+
],
|
|
9681
|
+
"description": "Whether the app asks for an authenticator code: `off` (the default), `optional` or `required`. A person with a confirmed authenticator is asked at every sign-in whatever the policy; a sign-in through an identity provider is never asked."
|
|
9682
|
+
},
|
|
9683
|
+
"hosted_logo_url": {
|
|
9684
|
+
"anyOf": [
|
|
9685
|
+
{
|
|
9686
|
+
"type": "string",
|
|
9687
|
+
"format": "uri"
|
|
9688
|
+
},
|
|
9689
|
+
{
|
|
9690
|
+
"type": "null"
|
|
9691
|
+
}
|
|
9692
|
+
],
|
|
9693
|
+
"description": "Where the hosted pages load the app's logo from, `<portal>/app/<identifier>/logo`, or `null` when no logo is stored. **Read-only** — the logo is written through `PUT /api/apps/:id/auth-config/logo`."
|
|
9694
|
+
},
|
|
9695
|
+
"hosted_accent": {
|
|
9696
|
+
"anyOf": [
|
|
9697
|
+
{
|
|
9698
|
+
"type": "string",
|
|
9699
|
+
"pattern": "^#[0-9a-f]{6}$"
|
|
9700
|
+
},
|
|
9701
|
+
{
|
|
9702
|
+
"type": "null"
|
|
9703
|
+
}
|
|
9704
|
+
],
|
|
9705
|
+
"description": "The accent colour of the hosted pages, `#rrggbb` in lowercase, or `null` for the neutral shell's own."
|
|
9041
9706
|
},
|
|
9042
|
-
"
|
|
9043
|
-
"
|
|
9044
|
-
|
|
9707
|
+
"hosted_pages": {
|
|
9708
|
+
"type": "object",
|
|
9709
|
+
"properties": {
|
|
9710
|
+
"invite_url": {
|
|
9045
9711
|
"type": "string",
|
|
9046
|
-
"
|
|
9712
|
+
"format": "uri",
|
|
9713
|
+
"description": "The hosted invitation page, `<portal>/app/<identifier>/invite/{token}`."
|
|
9047
9714
|
},
|
|
9048
|
-
{
|
|
9049
|
-
"type": "
|
|
9715
|
+
"verify_url": {
|
|
9716
|
+
"type": "string",
|
|
9717
|
+
"format": "uri",
|
|
9718
|
+
"description": "The hosted email-confirmation page, `<portal>/app/<identifier>/verify/{token}`."
|
|
9719
|
+
},
|
|
9720
|
+
"reset_url": {
|
|
9721
|
+
"type": "string",
|
|
9722
|
+
"format": "uri",
|
|
9723
|
+
"description": "The hosted new-password page, `<portal>/app/<identifier>/reset/{token}`."
|
|
9724
|
+
},
|
|
9725
|
+
"mcp_login_url": {
|
|
9726
|
+
"type": "string",
|
|
9727
|
+
"format": "uri",
|
|
9728
|
+
"description": "The hosted MCP sign-in, `<portal>/app/<identifier>/mcp/{interaction}`."
|
|
9050
9729
|
}
|
|
9730
|
+
},
|
|
9731
|
+
"required": [
|
|
9732
|
+
"invite_url",
|
|
9733
|
+
"verify_url",
|
|
9734
|
+
"reset_url",
|
|
9735
|
+
"mcp_login_url"
|
|
9051
9736
|
],
|
|
9052
|
-
"
|
|
9737
|
+
"additionalProperties": false,
|
|
9738
|
+
"description": "The Fleetless-hosted pages an unset URL falls back to, as templates. **Read-only**: minted by the cloud from the auth portal and the app's identifier."
|
|
9053
9739
|
},
|
|
9054
9740
|
"oidc_callback_url": {
|
|
9055
9741
|
"type": "string",
|
|
@@ -9072,6 +9758,12 @@
|
|
|
9072
9758
|
"verify_url",
|
|
9073
9759
|
"reset_url",
|
|
9074
9760
|
"mcp_login_url",
|
|
9761
|
+
"app_url",
|
|
9762
|
+
"sign_in_methods",
|
|
9763
|
+
"two_factor",
|
|
9764
|
+
"hosted_logo_url",
|
|
9765
|
+
"hosted_accent",
|
|
9766
|
+
"hosted_pages",
|
|
9075
9767
|
"oidc_callback_url",
|
|
9076
9768
|
"updated_at"
|
|
9077
9769
|
],
|
|
@@ -9161,17 +9853,10 @@
|
|
|
9161
9853
|
"description": "When the token stops working. Seven days from issue; an expired token answers exactly as an unknown one does."
|
|
9162
9854
|
},
|
|
9163
9855
|
"accept_url": {
|
|
9164
|
-
"
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
"format": "uri"
|
|
9169
|
-
},
|
|
9170
|
-
{
|
|
9171
|
-
"type": "null"
|
|
9172
|
-
}
|
|
9173
|
-
],
|
|
9174
|
-
"description": "The link to give the invitee, built from the app's `invite_url` with the token substituted for `{token}`. **`null` when the app has configured no `invite_url`** — there is nowhere for the link to point, and Fleetless serves no page of its own for an app user. Bounded like every other URL that gets mailed, logged and rendered."
|
|
9856
|
+
"type": "string",
|
|
9857
|
+
"maxLength": 500,
|
|
9858
|
+
"format": "uri",
|
|
9859
|
+
"description": "The link to give the invitee: the app's `invite_url` with the token substituted for `{token}`, or the Fleetless-hosted invitation page when the app has configured none. Bounded like every other URL that gets mailed, logged and rendered."
|
|
9175
9860
|
},
|
|
9176
9861
|
"mail": {
|
|
9177
9862
|
"type": "string",
|
|
@@ -9181,7 +9866,7 @@
|
|
|
9181
9866
|
"not_configured",
|
|
9182
9867
|
"failed"
|
|
9183
9868
|
],
|
|
9184
|
-
"description": "What happened to the mail: `sent` means the SMTP server accepted it, not that it was delivered; `not_requested` means none was attempted
|
|
9869
|
+
"description": "What happened to the mail: `sent` means the SMTP server accepted it, not that it was delivered; `not_requested` means none was attempted because the caller asked for none; `not_configured` is an expected state and not a failure; `failed` is the one worth somebody's attention."
|
|
9185
9870
|
}
|
|
9186
9871
|
},
|
|
9187
9872
|
"required": [
|
|
@@ -9340,9 +10025,10 @@
|
|
|
9340
10025
|
"enum": [
|
|
9341
10026
|
"invite",
|
|
9342
10027
|
"verify",
|
|
9343
|
-
"reset"
|
|
10028
|
+
"reset",
|
|
10029
|
+
"login_code"
|
|
9344
10030
|
],
|
|
9345
|
-
"description": "Which of the
|
|
10031
|
+
"description": "Which of the four mails this template replaces."
|
|
9346
10032
|
},
|
|
9347
10033
|
"subject": {
|
|
9348
10034
|
"type": "string",
|
|
@@ -9389,7 +10075,7 @@
|
|
|
9389
10075
|
"type": "object",
|
|
9390
10076
|
"properties": {
|
|
9391
10077
|
"templates": {
|
|
9392
|
-
"maxItems":
|
|
10078
|
+
"maxItems": 4,
|
|
9393
10079
|
"type": "array",
|
|
9394
10080
|
"items": {
|
|
9395
10081
|
"type": "object",
|
|
@@ -9399,9 +10085,10 @@
|
|
|
9399
10085
|
"enum": [
|
|
9400
10086
|
"invite",
|
|
9401
10087
|
"verify",
|
|
9402
|
-
"reset"
|
|
10088
|
+
"reset",
|
|
10089
|
+
"login_code"
|
|
9403
10090
|
],
|
|
9404
|
-
"description": "Which of the
|
|
10091
|
+
"description": "Which of the four mails this template replaces."
|
|
9405
10092
|
},
|
|
9406
10093
|
"subject": {
|
|
9407
10094
|
"type": "string",
|
|
@@ -9699,6 +10386,41 @@
|
|
|
9699
10386
|
],
|
|
9700
10387
|
"description": "When this user last signed in, or `null` if they never have. Required and nullable rather than optional, so *never logged in* stays distinguishable from *this field was not loaded*."
|
|
9701
10388
|
},
|
|
10389
|
+
"two_factor": {
|
|
10390
|
+
"type": "object",
|
|
10391
|
+
"properties": {
|
|
10392
|
+
"enabled": {
|
|
10393
|
+
"type": "boolean",
|
|
10394
|
+
"description": "Whether the account has a confirmed authenticator app (TOTP). When it has, every sign-in that yields a session asks for a code as well — whatever the app's policy — except a sign-in through an identity provider, which owns that sign-in."
|
|
10395
|
+
},
|
|
10396
|
+
"enabled_at": {
|
|
10397
|
+
"anyOf": [
|
|
10398
|
+
{
|
|
10399
|
+
"type": "string",
|
|
10400
|
+
"format": "date-time",
|
|
10401
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
|
10402
|
+
},
|
|
10403
|
+
{
|
|
10404
|
+
"type": "null"
|
|
10405
|
+
}
|
|
10406
|
+
],
|
|
10407
|
+
"description": "When the authenticator was confirmed, or `null` while `enabled` is `false`."
|
|
10408
|
+
},
|
|
10409
|
+
"recovery_codes_left": {
|
|
10410
|
+
"type": "integer",
|
|
10411
|
+
"minimum": 0,
|
|
10412
|
+
"maximum": 10,
|
|
10413
|
+
"description": "How many of the ten single-use recovery codes are still unspent. `0` while `enabled` is `false`."
|
|
10414
|
+
}
|
|
10415
|
+
},
|
|
10416
|
+
"required": [
|
|
10417
|
+
"enabled",
|
|
10418
|
+
"enabled_at",
|
|
10419
|
+
"recovery_codes_left"
|
|
10420
|
+
],
|
|
10421
|
+
"additionalProperties": false,
|
|
10422
|
+
"description": "The account's second factor, as a developer's user list shows it. No secret and no code travels here; resetting it is `DELETE /api/apps/:id/users/:userId/two-factor`."
|
|
10423
|
+
},
|
|
9702
10424
|
"created_at": {
|
|
9703
10425
|
"type": "string",
|
|
9704
10426
|
"format": "date-time",
|
|
@@ -9716,6 +10438,7 @@
|
|
|
9716
10438
|
"has_password",
|
|
9717
10439
|
"providers",
|
|
9718
10440
|
"last_login_at",
|
|
10441
|
+
"two_factor",
|
|
9719
10442
|
"created_at"
|
|
9720
10443
|
],
|
|
9721
10444
|
"additionalProperties": false
|
|
@@ -9801,6 +10524,41 @@
|
|
|
9801
10524
|
],
|
|
9802
10525
|
"description": "When this user last signed in, or `null` if they never have. Required and nullable rather than optional, so *never logged in* stays distinguishable from *this field was not loaded*."
|
|
9803
10526
|
},
|
|
10527
|
+
"two_factor": {
|
|
10528
|
+
"type": "object",
|
|
10529
|
+
"properties": {
|
|
10530
|
+
"enabled": {
|
|
10531
|
+
"type": "boolean",
|
|
10532
|
+
"description": "Whether the account has a confirmed authenticator app (TOTP). When it has, every sign-in that yields a session asks for a code as well — whatever the app's policy — except a sign-in through an identity provider, which owns that sign-in."
|
|
10533
|
+
},
|
|
10534
|
+
"enabled_at": {
|
|
10535
|
+
"anyOf": [
|
|
10536
|
+
{
|
|
10537
|
+
"type": "string",
|
|
10538
|
+
"format": "date-time",
|
|
10539
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
|
10540
|
+
},
|
|
10541
|
+
{
|
|
10542
|
+
"type": "null"
|
|
10543
|
+
}
|
|
10544
|
+
],
|
|
10545
|
+
"description": "When the authenticator was confirmed, or `null` while `enabled` is `false`."
|
|
10546
|
+
},
|
|
10547
|
+
"recovery_codes_left": {
|
|
10548
|
+
"type": "integer",
|
|
10549
|
+
"minimum": 0,
|
|
10550
|
+
"maximum": 10,
|
|
10551
|
+
"description": "How many of the ten single-use recovery codes are still unspent. `0` while `enabled` is `false`."
|
|
10552
|
+
}
|
|
10553
|
+
},
|
|
10554
|
+
"required": [
|
|
10555
|
+
"enabled",
|
|
10556
|
+
"enabled_at",
|
|
10557
|
+
"recovery_codes_left"
|
|
10558
|
+
],
|
|
10559
|
+
"additionalProperties": false,
|
|
10560
|
+
"description": "The account's second factor, as a developer's user list shows it. No secret and no code travels here; resetting it is `DELETE /api/apps/:id/users/:userId/two-factor`."
|
|
10561
|
+
},
|
|
9804
10562
|
"created_at": {
|
|
9805
10563
|
"type": "string",
|
|
9806
10564
|
"format": "date-time",
|
|
@@ -9818,6 +10576,7 @@
|
|
|
9818
10576
|
"has_password",
|
|
9819
10577
|
"providers",
|
|
9820
10578
|
"last_login_at",
|
|
10579
|
+
"two_factor",
|
|
9821
10580
|
"created_at"
|
|
9822
10581
|
],
|
|
9823
10582
|
"additionalProperties": false
|
|
@@ -10558,6 +11317,10 @@
|
|
|
10558
11317
|
"maxLength": 120,
|
|
10559
11318
|
"description": "The organisation's display name. Free text, changed through `PATCH /api/org`."
|
|
10560
11319
|
},
|
|
11320
|
+
"require_two_factor": {
|
|
11321
|
+
"type": "boolean",
|
|
11322
|
+
"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`."
|
|
11323
|
+
},
|
|
10561
11324
|
"created_at": {
|
|
10562
11325
|
"type": "string",
|
|
10563
11326
|
"format": "date-time",
|
|
@@ -10568,6 +11331,7 @@
|
|
|
10568
11331
|
"required": [
|
|
10569
11332
|
"id",
|
|
10570
11333
|
"name",
|
|
11334
|
+
"require_two_factor",
|
|
10571
11335
|
"created_at"
|
|
10572
11336
|
],
|
|
10573
11337
|
"additionalProperties": false
|
|
@@ -10614,6 +11378,27 @@
|
|
|
10614
11378
|
],
|
|
10615
11379
|
"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."
|
|
10616
11380
|
},
|
|
11381
|
+
"two_factor": {
|
|
11382
|
+
"type": "object",
|
|
11383
|
+
"properties": {
|
|
11384
|
+
"passkeys": {
|
|
11385
|
+
"type": "integer",
|
|
11386
|
+
"minimum": 0,
|
|
11387
|
+
"maximum": 9007199254740991,
|
|
11388
|
+
"description": "How many passkeys the person has registered."
|
|
11389
|
+
},
|
|
11390
|
+
"authenticator": {
|
|
11391
|
+
"type": "boolean",
|
|
11392
|
+
"description": "Whether the person has a confirmed authenticator app."
|
|
11393
|
+
}
|
|
11394
|
+
},
|
|
11395
|
+
"required": [
|
|
11396
|
+
"passkeys",
|
|
11397
|
+
"authenticator"
|
|
11398
|
+
],
|
|
11399
|
+
"additionalProperties": false,
|
|
11400
|
+
"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`."
|
|
11401
|
+
},
|
|
10617
11402
|
"created_at": {
|
|
10618
11403
|
"type": "string",
|
|
10619
11404
|
"format": "date-time",
|
|
@@ -10627,6 +11412,7 @@
|
|
|
10627
11412
|
"email",
|
|
10628
11413
|
"display_name",
|
|
10629
11414
|
"tier",
|
|
11415
|
+
"two_factor",
|
|
10630
11416
|
"created_at"
|
|
10631
11417
|
],
|
|
10632
11418
|
"additionalProperties": false
|
|
@@ -10802,10 +11588,10 @@
|
|
|
10802
11588
|
"description": "The opaque token from the invitation link, valid seven days. Unknown, expired, revoked and already-accepted all answer `410 token_spent`."
|
|
10803
11589
|
},
|
|
10804
11590
|
"password": {
|
|
11591
|
+
"description": "The password the new account will use, at least 12 characters. **Required while the app's password method is on, refused while it is off** — both as `400 validation_error` naming `password`. An email-code-only app accepts invitations without one.",
|
|
10805
11592
|
"type": "string",
|
|
10806
11593
|
"minLength": 12,
|
|
10807
|
-
"maxLength": 256
|
|
10808
|
-
"description": "The password the new account will use."
|
|
11594
|
+
"maxLength": 256
|
|
10809
11595
|
},
|
|
10810
11596
|
"display_name": {
|
|
10811
11597
|
"description": "An optional name, overriding whatever the invitation pre-filled.",
|
|
@@ -10822,8 +11608,7 @@
|
|
|
10822
11608
|
}
|
|
10823
11609
|
},
|
|
10824
11610
|
"required": [
|
|
10825
|
-
"token"
|
|
10826
|
-
"password"
|
|
11611
|
+
"token"
|
|
10827
11612
|
],
|
|
10828
11613
|
"additionalProperties": false
|
|
10829
11614
|
},
|
|
@@ -10916,6 +11701,17 @@
|
|
|
10916
11701
|
}
|
|
10917
11702
|
],
|
|
10918
11703
|
"description": "The address of the Fleetless user or app user behind this session, and `null` for a server key, which is not a person."
|
|
11704
|
+
},
|
|
11705
|
+
"two_factor_enabled": {
|
|
11706
|
+
"anyOf": [
|
|
11707
|
+
{
|
|
11708
|
+
"type": "boolean"
|
|
11709
|
+
},
|
|
11710
|
+
{
|
|
11711
|
+
"type": "null"
|
|
11712
|
+
}
|
|
11713
|
+
],
|
|
11714
|
+
"description": "Whether the app user has a confirmed authenticator, so the app's account settings can offer to turn it on or off. `null` unless `kind` is `app_user`."
|
|
10919
11715
|
}
|
|
10920
11716
|
},
|
|
10921
11717
|
"required": [
|
|
@@ -10925,10 +11721,63 @@
|
|
|
10925
11721
|
"server_key_id",
|
|
10926
11722
|
"app_id",
|
|
10927
11723
|
"role_id",
|
|
11724
|
+
"email",
|
|
11725
|
+
"two_factor_enabled"
|
|
11726
|
+
],
|
|
11727
|
+
"additionalProperties": false
|
|
11728
|
+
},
|
|
11729
|
+
"client-login-code-request": {
|
|
11730
|
+
"type": "object",
|
|
11731
|
+
"properties": {
|
|
11732
|
+
"app_identifier": {
|
|
11733
|
+
"type": "string",
|
|
11734
|
+
"minLength": 2,
|
|
11735
|
+
"maxLength": 63,
|
|
11736
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
11737
|
+
"description": "The app to sign in to. An identifier no app carries is `404 not_found`; the address is never the subject of a refusal."
|
|
11738
|
+
},
|
|
11739
|
+
"email": {
|
|
11740
|
+
"type": "string",
|
|
11741
|
+
"format": "email",
|
|
11742
|
+
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
|
|
11743
|
+
"description": "The address to mail the code to, trimmed and compared case-insensitively. `202` whether or not it names an account of this app."
|
|
11744
|
+
}
|
|
11745
|
+
},
|
|
11746
|
+
"required": [
|
|
11747
|
+
"app_identifier",
|
|
10928
11748
|
"email"
|
|
10929
11749
|
],
|
|
10930
11750
|
"additionalProperties": false
|
|
10931
11751
|
},
|
|
11752
|
+
"client-login-code-verify-request": {
|
|
11753
|
+
"type": "object",
|
|
11754
|
+
"properties": {
|
|
11755
|
+
"app_identifier": {
|
|
11756
|
+
"type": "string",
|
|
11757
|
+
"minLength": 2,
|
|
11758
|
+
"maxLength": 63,
|
|
11759
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
11760
|
+
"description": "The app the code was requested for."
|
|
11761
|
+
},
|
|
11762
|
+
"email": {
|
|
11763
|
+
"type": "string",
|
|
11764
|
+
"format": "email",
|
|
11765
|
+
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
|
|
11766
|
+
"description": "The address the code was mailed to, as typed when it was requested; trimmed and compared case-insensitively."
|
|
11767
|
+
},
|
|
11768
|
+
"code": {
|
|
11769
|
+
"type": "string",
|
|
11770
|
+
"pattern": "^\\d{6}$",
|
|
11771
|
+
"description": "The six digits from the mail, exactly — leading zeros included, no spaces."
|
|
11772
|
+
}
|
|
11773
|
+
},
|
|
11774
|
+
"required": [
|
|
11775
|
+
"app_identifier",
|
|
11776
|
+
"email",
|
|
11777
|
+
"code"
|
|
11778
|
+
],
|
|
11779
|
+
"additionalProperties": false
|
|
11780
|
+
},
|
|
10932
11781
|
"client-login-request": {
|
|
10933
11782
|
"type": "object",
|
|
10934
11783
|
"properties": {
|
|
@@ -11131,11 +11980,31 @@
|
|
|
11131
11980
|
],
|
|
11132
11981
|
"additionalProperties": false
|
|
11133
11982
|
},
|
|
11134
|
-
"description": "The app's **enabled** providers, slug and display name only. An app with none answers an empty array, which is the state of an app that offers password
|
|
11983
|
+
"description": "The app's **enabled** providers, slug and display name only. An app with none answers an empty array, which is the state of an app that offers password or code sign-in alone."
|
|
11984
|
+
},
|
|
11985
|
+
"sign_in_methods": {
|
|
11986
|
+
"type": "object",
|
|
11987
|
+
"properties": {
|
|
11988
|
+
"password": {
|
|
11989
|
+
"type": "boolean",
|
|
11990
|
+
"description": "Whether app users may sign in with a password. Off refuses `POST /api/client/login` with `method_not_allowed`, and registration and invitations then take no password."
|
|
11991
|
+
},
|
|
11992
|
+
"email_code": {
|
|
11993
|
+
"type": "boolean",
|
|
11994
|
+
"description": "Whether app users may sign in with a six-digit code mailed to them, valid ten minutes. A code needs no URL, so it works in local development and in an app with no web UI."
|
|
11995
|
+
}
|
|
11996
|
+
},
|
|
11997
|
+
"required": [
|
|
11998
|
+
"password",
|
|
11999
|
+
"email_code"
|
|
12000
|
+
],
|
|
12001
|
+
"additionalProperties": false,
|
|
12002
|
+
"description": "Which of password and emailed code the app accepts, so its sign-in page draws the right fields without guessing. The same value the developer set; public, like the provider buttons."
|
|
11135
12003
|
}
|
|
11136
12004
|
},
|
|
11137
12005
|
"required": [
|
|
11138
|
-
"providers"
|
|
12006
|
+
"providers",
|
|
12007
|
+
"sign_in_methods"
|
|
11139
12008
|
],
|
|
11140
12009
|
"additionalProperties": false
|
|
11141
12010
|
},
|
|
@@ -11169,10 +12038,10 @@
|
|
|
11169
12038
|
"description": "The address to register. Unique per app, case-insensitively. An address this app already knows still answers `202`, without a mail — the answer may not say whether an account exists."
|
|
11170
12039
|
},
|
|
11171
12040
|
"password": {
|
|
12041
|
+
"description": "The password for the new account, at least 12 characters. **Required while the app's password method is on, refused while it is off** — both as `400 validation_error` naming `password`. An email-code-only app registers people without one.",
|
|
11172
12042
|
"type": "string",
|
|
11173
12043
|
"minLength": 12,
|
|
11174
|
-
"maxLength": 256
|
|
11175
|
-
"description": "The password for the new account. At least 12 characters; length only, because a rule a user cannot predict is a rule they work around."
|
|
12044
|
+
"maxLength": 256
|
|
11176
12045
|
},
|
|
11177
12046
|
"display_name": {
|
|
11178
12047
|
"description": "An optional human name for the account. The developer's own UI decides whether to ask for it.",
|
|
@@ -11190,8 +12059,7 @@
|
|
|
11190
12059
|
},
|
|
11191
12060
|
"required": [
|
|
11192
12061
|
"app_identifier",
|
|
11193
|
-
"email"
|
|
11194
|
-
"password"
|
|
12062
|
+
"email"
|
|
11195
12063
|
],
|
|
11196
12064
|
"additionalProperties": false
|
|
11197
12065
|
},
|
|
@@ -11297,11 +12165,181 @@
|
|
|
11297
12165
|
],
|
|
11298
12166
|
"additionalProperties": false
|
|
11299
12167
|
},
|
|
11300
|
-
"description": "Every robot the caller reaches, in name order with the id as the tiebreak. An app user reaches the robots their app attaches on which their role grants at least one slug or capability; a server key reaches every robot its app attaches; a developer reaches every robot of the organisation."
|
|
12168
|
+
"description": "Every robot the caller reaches, in name order with the id as the tiebreak. An app user reaches the robots their app attaches on which their role grants at least one slug or capability; a server key reaches every robot its app attaches; a developer reaches every robot of the organisation."
|
|
12169
|
+
}
|
|
12170
|
+
},
|
|
12171
|
+
"required": [
|
|
12172
|
+
"robots"
|
|
12173
|
+
],
|
|
12174
|
+
"additionalProperties": false
|
|
12175
|
+
},
|
|
12176
|
+
"client-sign-in-result": {
|
|
12177
|
+
"anyOf": [
|
|
12178
|
+
{
|
|
12179
|
+
"type": "object",
|
|
12180
|
+
"properties": {
|
|
12181
|
+
"access_token": {
|
|
12182
|
+
"type": "string",
|
|
12183
|
+
"minLength": 1,
|
|
12184
|
+
"description": "The token to send as `Authorization: Bearer <token>` on every call. Short-lived: read `expires_in` rather than assuming a lifetime."
|
|
12185
|
+
},
|
|
12186
|
+
"refresh_token": {
|
|
12187
|
+
"type": "string",
|
|
12188
|
+
"minLength": 1,
|
|
12189
|
+
"description": "The token that buys the next access token. It rotates on every use, so a value presented twice is detectable theft and ends the whole family."
|
|
12190
|
+
},
|
|
12191
|
+
"expires_in": {
|
|
12192
|
+
"type": "integer",
|
|
12193
|
+
"exclusiveMinimum": 0,
|
|
12194
|
+
"maximum": 9007199254740991,
|
|
12195
|
+
"description": "How long the access token stays valid, in **seconds** from now. Not a timestamp, and not milliseconds."
|
|
12196
|
+
}
|
|
12197
|
+
},
|
|
12198
|
+
"required": [
|
|
12199
|
+
"access_token",
|
|
12200
|
+
"refresh_token",
|
|
12201
|
+
"expires_in"
|
|
12202
|
+
],
|
|
12203
|
+
"additionalProperties": false
|
|
12204
|
+
},
|
|
12205
|
+
{
|
|
12206
|
+
"type": "object",
|
|
12207
|
+
"properties": {
|
|
12208
|
+
"status": {
|
|
12209
|
+
"type": "string",
|
|
12210
|
+
"enum": [
|
|
12211
|
+
"two_factor_required",
|
|
12212
|
+
"two_factor_setup_required"
|
|
12213
|
+
],
|
|
12214
|
+
"description": "`two_factor_required`: ask for the authenticator code. `two_factor_setup_required`: the app requires two-factor and the person has none yet, so set one up before any session exists."
|
|
12215
|
+
},
|
|
12216
|
+
"challenge": {
|
|
12217
|
+
"type": "string",
|
|
12218
|
+
"minLength": 1,
|
|
12219
|
+
"description": "The handle the next step spends. Valid five minutes; afterwards it answers `410 token_spent` and the sign-in starts over."
|
|
12220
|
+
}
|
|
12221
|
+
},
|
|
12222
|
+
"required": [
|
|
12223
|
+
"status",
|
|
12224
|
+
"challenge"
|
|
12225
|
+
],
|
|
12226
|
+
"additionalProperties": false
|
|
12227
|
+
}
|
|
12228
|
+
]
|
|
12229
|
+
},
|
|
12230
|
+
"client-two-factor-disable-request": {
|
|
12231
|
+
"type": "object",
|
|
12232
|
+
"properties": {
|
|
12233
|
+
"code": {
|
|
12234
|
+
"type": "string",
|
|
12235
|
+
"pattern": "^\\d{6}$",
|
|
12236
|
+
"description": "A code the authenticator shows now."
|
|
12237
|
+
}
|
|
12238
|
+
},
|
|
12239
|
+
"required": [
|
|
12240
|
+
"code"
|
|
12241
|
+
],
|
|
12242
|
+
"additionalProperties": false
|
|
12243
|
+
},
|
|
12244
|
+
"client-two-factor-setup-confirm-request": {
|
|
12245
|
+
"type": "object",
|
|
12246
|
+
"properties": {
|
|
12247
|
+
"challenge": {
|
|
12248
|
+
"description": "The same challenge as at `setup`, during sign-in; absent with a bearer.",
|
|
12249
|
+
"type": "string",
|
|
12250
|
+
"minLength": 1
|
|
12251
|
+
},
|
|
12252
|
+
"code": {
|
|
12253
|
+
"type": "string",
|
|
12254
|
+
"pattern": "^\\d{6}$",
|
|
12255
|
+
"description": "A code the new authenticator shows now. It proves the secret was copied correctly before anything depends on it."
|
|
12256
|
+
}
|
|
12257
|
+
},
|
|
12258
|
+
"required": [
|
|
12259
|
+
"code"
|
|
12260
|
+
],
|
|
12261
|
+
"additionalProperties": false
|
|
12262
|
+
},
|
|
12263
|
+
"client-two-factor-setup-confirm-response": {
|
|
12264
|
+
"type": "object",
|
|
12265
|
+
"properties": {
|
|
12266
|
+
"recovery_codes": {
|
|
12267
|
+
"minItems": 10,
|
|
12268
|
+
"maxItems": 10,
|
|
12269
|
+
"type": "array",
|
|
12270
|
+
"items": {
|
|
12271
|
+
"type": "string",
|
|
12272
|
+
"pattern": "^[a-z2-7]{5}-[a-z2-7]{5}$"
|
|
12273
|
+
},
|
|
12274
|
+
"description": "The ten single-use recovery codes, lowercase, shown once. Any earlier set is void."
|
|
12275
|
+
},
|
|
12276
|
+
"session": {
|
|
12277
|
+
"type": "object",
|
|
12278
|
+
"properties": {
|
|
12279
|
+
"access_token": {
|
|
12280
|
+
"type": "string",
|
|
12281
|
+
"minLength": 1,
|
|
12282
|
+
"description": "The token to send as `Authorization: Bearer <token>` on every call. Short-lived: read `expires_in` rather than assuming a lifetime."
|
|
12283
|
+
},
|
|
12284
|
+
"refresh_token": {
|
|
12285
|
+
"type": "string",
|
|
12286
|
+
"minLength": 1,
|
|
12287
|
+
"description": "The token that buys the next access token. It rotates on every use, so a value presented twice is detectable theft and ends the whole family."
|
|
12288
|
+
},
|
|
12289
|
+
"expires_in": {
|
|
12290
|
+
"type": "integer",
|
|
12291
|
+
"exclusiveMinimum": 0,
|
|
12292
|
+
"maximum": 9007199254740991,
|
|
12293
|
+
"description": "How long the access token stays valid, in **seconds** from now. Not a timestamp, and not milliseconds."
|
|
12294
|
+
}
|
|
12295
|
+
},
|
|
12296
|
+
"required": [
|
|
12297
|
+
"access_token",
|
|
12298
|
+
"refresh_token",
|
|
12299
|
+
"expires_in"
|
|
12300
|
+
],
|
|
12301
|
+
"additionalProperties": false,
|
|
12302
|
+
"description": "The session the sign-in was waiting for, or a fresh one for the account settings."
|
|
11301
12303
|
}
|
|
11302
12304
|
},
|
|
11303
12305
|
"required": [
|
|
11304
|
-
"
|
|
12306
|
+
"recovery_codes",
|
|
12307
|
+
"session"
|
|
12308
|
+
],
|
|
12309
|
+
"additionalProperties": false
|
|
12310
|
+
},
|
|
12311
|
+
"client-two-factor-setup-request": {
|
|
12312
|
+
"type": "object",
|
|
12313
|
+
"properties": {
|
|
12314
|
+
"challenge": {
|
|
12315
|
+
"description": "The `two_factor_setup_required` challenge, during sign-in. Absent when the call carries the app user's bearer instead.",
|
|
12316
|
+
"type": "string",
|
|
12317
|
+
"minLength": 1
|
|
12318
|
+
}
|
|
12319
|
+
},
|
|
12320
|
+
"additionalProperties": false
|
|
12321
|
+
},
|
|
12322
|
+
"client-two-factor-verify-request": {
|
|
12323
|
+
"type": "object",
|
|
12324
|
+
"properties": {
|
|
12325
|
+
"challenge": {
|
|
12326
|
+
"type": "string",
|
|
12327
|
+
"minLength": 1,
|
|
12328
|
+
"description": "The challenge the sign-in step answered."
|
|
12329
|
+
},
|
|
12330
|
+
"code": {
|
|
12331
|
+
"description": "The six-digit code the authenticator shows now. A code already accepted once is refused, so a replay of a seen code does not sign anybody in.",
|
|
12332
|
+
"type": "string",
|
|
12333
|
+
"pattern": "^\\d{6}$"
|
|
12334
|
+
},
|
|
12335
|
+
"recovery_code": {
|
|
12336
|
+
"description": "One of the ten recovery codes, `xxxxx-xxxxx`, in either case. Spent by its use.",
|
|
12337
|
+
"type": "string",
|
|
12338
|
+
"pattern": "^[a-zA-Z2-7]{5}-[a-zA-Z2-7]{5}$"
|
|
12339
|
+
}
|
|
12340
|
+
},
|
|
12341
|
+
"required": [
|
|
12342
|
+
"challenge"
|
|
11305
12343
|
],
|
|
11306
12344
|
"additionalProperties": false
|
|
11307
12345
|
},
|
|
@@ -13635,7 +14673,7 @@
|
|
|
13635
14673
|
},
|
|
13636
14674
|
"send_mail": {
|
|
13637
14675
|
"type": "boolean",
|
|
13638
|
-
"description": "Whether Fleetless mails the invitation.
|
|
14676
|
+
"description": "Whether Fleetless mails the invitation. The link points at the app's `invite_url`, or at the Fleetless-hosted invitation page when the app has configured none — so the mail always leads somewhere, and nothing is refused for a missing URL."
|
|
13639
14677
|
}
|
|
13640
14678
|
},
|
|
13641
14679
|
"required": [
|
|
@@ -13783,6 +14821,113 @@
|
|
|
13783
14821
|
],
|
|
13784
14822
|
"additionalProperties": false
|
|
13785
14823
|
},
|
|
14824
|
+
"create-passkey-request": {
|
|
14825
|
+
"type": "object",
|
|
14826
|
+
"properties": {
|
|
14827
|
+
"name": {
|
|
14828
|
+
"type": "string",
|
|
14829
|
+
"minLength": 1,
|
|
14830
|
+
"maxLength": 80,
|
|
14831
|
+
"description": "What to call the passkey, such as the device it lives on."
|
|
14832
|
+
},
|
|
14833
|
+
"credential": {
|
|
14834
|
+
"type": "object",
|
|
14835
|
+
"propertyNames": {
|
|
14836
|
+
"type": "string"
|
|
14837
|
+
},
|
|
14838
|
+
"additionalProperties": {},
|
|
14839
|
+
"description": "The browser's `RegistrationResponseJSON` for the options `POST /api/auth/passkeys/options` answered."
|
|
14840
|
+
}
|
|
14841
|
+
},
|
|
14842
|
+
"required": [
|
|
14843
|
+
"name",
|
|
14844
|
+
"credential"
|
|
14845
|
+
],
|
|
14846
|
+
"additionalProperties": false
|
|
14847
|
+
},
|
|
14848
|
+
"create-passkey-response": {
|
|
14849
|
+
"type": "object",
|
|
14850
|
+
"properties": {
|
|
14851
|
+
"passkey": {
|
|
14852
|
+
"type": "object",
|
|
14853
|
+
"properties": {
|
|
14854
|
+
"id": {
|
|
14855
|
+
"type": "string",
|
|
14856
|
+
"format": "uuid",
|
|
14857
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
14858
|
+
"description": "The passkey in the API, as renamed and removed through `/api/auth/passkeys/:id`."
|
|
14859
|
+
},
|
|
14860
|
+
"name": {
|
|
14861
|
+
"type": "string",
|
|
14862
|
+
"minLength": 1,
|
|
14863
|
+
"maxLength": 80,
|
|
14864
|
+
"description": "What the person called it, such as the device it lives on."
|
|
14865
|
+
},
|
|
14866
|
+
"created_at": {
|
|
14867
|
+
"type": "string",
|
|
14868
|
+
"format": "date-time",
|
|
14869
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
14870
|
+
"description": "When it was registered."
|
|
14871
|
+
},
|
|
14872
|
+
"last_used_at": {
|
|
14873
|
+
"anyOf": [
|
|
14874
|
+
{
|
|
14875
|
+
"type": "string",
|
|
14876
|
+
"format": "date-time",
|
|
14877
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
|
14878
|
+
},
|
|
14879
|
+
{
|
|
14880
|
+
"type": "null"
|
|
14881
|
+
}
|
|
14882
|
+
],
|
|
14883
|
+
"description": "When it last signed the person in or confirmed a sign-in, or `null` if never."
|
|
14884
|
+
},
|
|
14885
|
+
"synced": {
|
|
14886
|
+
"anyOf": [
|
|
14887
|
+
{
|
|
14888
|
+
"type": "boolean"
|
|
14889
|
+
},
|
|
14890
|
+
{
|
|
14891
|
+
"type": "null"
|
|
14892
|
+
}
|
|
14893
|
+
],
|
|
14894
|
+
"description": "Whether the authenticator reported the passkey as syncable across the person's devices (the backup-eligible flag), or `null` when it said nothing."
|
|
14895
|
+
}
|
|
14896
|
+
},
|
|
14897
|
+
"required": [
|
|
14898
|
+
"id",
|
|
14899
|
+
"name",
|
|
14900
|
+
"created_at",
|
|
14901
|
+
"last_used_at",
|
|
14902
|
+
"synced"
|
|
14903
|
+
],
|
|
14904
|
+
"additionalProperties": false,
|
|
14905
|
+
"description": "The passkey as it is now stored."
|
|
14906
|
+
},
|
|
14907
|
+
"recovery_codes": {
|
|
14908
|
+
"anyOf": [
|
|
14909
|
+
{
|
|
14910
|
+
"minItems": 10,
|
|
14911
|
+
"maxItems": 10,
|
|
14912
|
+
"type": "array",
|
|
14913
|
+
"items": {
|
|
14914
|
+
"type": "string",
|
|
14915
|
+
"pattern": "^[a-z2-7]{5}-[a-z2-7]{5}$"
|
|
14916
|
+
}
|
|
14917
|
+
},
|
|
14918
|
+
{
|
|
14919
|
+
"type": "null"
|
|
14920
|
+
}
|
|
14921
|
+
],
|
|
14922
|
+
"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."
|
|
14923
|
+
}
|
|
14924
|
+
},
|
|
14925
|
+
"required": [
|
|
14926
|
+
"passkey",
|
|
14927
|
+
"recovery_codes"
|
|
14928
|
+
],
|
|
14929
|
+
"additionalProperties": false
|
|
14930
|
+
},
|
|
13786
14931
|
"create-robot-request": {
|
|
13787
14932
|
"type": "object",
|
|
13788
14933
|
"properties": {
|
|
@@ -13939,96 +15084,255 @@
|
|
|
13939
15084
|
}
|
|
13940
15085
|
},
|
|
13941
15086
|
"required": [
|
|
13942
|
-
"email",
|
|
13943
|
-
"tier",
|
|
13944
|
-
"send_mail"
|
|
15087
|
+
"email",
|
|
15088
|
+
"tier",
|
|
15089
|
+
"send_mail"
|
|
15090
|
+
],
|
|
15091
|
+
"additionalProperties": false
|
|
15092
|
+
},
|
|
15093
|
+
"datapoint-list-response": {
|
|
15094
|
+
"type": "object",
|
|
15095
|
+
"properties": {
|
|
15096
|
+
"datapoints": {
|
|
15097
|
+
"type": "array",
|
|
15098
|
+
"items": {
|
|
15099
|
+
"type": "object",
|
|
15100
|
+
"properties": {
|
|
15101
|
+
"slug": {
|
|
15102
|
+
"type": "string",
|
|
15103
|
+
"minLength": 2,
|
|
15104
|
+
"maxLength": 63,
|
|
15105
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
15106
|
+
"description": "The name a client reads this datapoint by."
|
|
15107
|
+
},
|
|
15108
|
+
"builtin": {
|
|
15109
|
+
"type": "boolean",
|
|
15110
|
+
"description": "`true` for the datapoints every robot has — `bridge_state` and `robot_details` — and `false` for everything the published configuration adds."
|
|
15111
|
+
},
|
|
15112
|
+
"unit": {
|
|
15113
|
+
"anyOf": [
|
|
15114
|
+
{
|
|
15115
|
+
"type": "string"
|
|
15116
|
+
},
|
|
15117
|
+
{
|
|
15118
|
+
"type": "null"
|
|
15119
|
+
}
|
|
15120
|
+
],
|
|
15121
|
+
"description": "The unit the value carries **after** any scale and offset, shown beside the number so nobody has to guess whether `15` means percent, volts or minutes. `null` when the configuration names none."
|
|
15122
|
+
},
|
|
15123
|
+
"rate_throttle_hz": {
|
|
15124
|
+
"anyOf": [
|
|
15125
|
+
{
|
|
15126
|
+
"type": "number",
|
|
15127
|
+
"minimum": 0,
|
|
15128
|
+
"maximum": 20
|
|
15129
|
+
},
|
|
15130
|
+
{
|
|
15131
|
+
"type": "null"
|
|
15132
|
+
}
|
|
15133
|
+
],
|
|
15134
|
+
"description": "The ceiling on how often this datapoint is sent, in hertz. `null` means no ceiling is configured, which is also the answer for every built-in. A ceiling, not a clock: a slow topic stays slow and no value is repeated to manufacture a rate."
|
|
15135
|
+
}
|
|
15136
|
+
},
|
|
15137
|
+
"required": [
|
|
15138
|
+
"slug",
|
|
15139
|
+
"builtin",
|
|
15140
|
+
"unit",
|
|
15141
|
+
"rate_throttle_hz"
|
|
15142
|
+
],
|
|
15143
|
+
"additionalProperties": false
|
|
15144
|
+
},
|
|
15145
|
+
"description": "Everything a client may read on this robot: the two built-ins, plus every datapoint the published configuration exposes and the caller's role grants."
|
|
15146
|
+
}
|
|
15147
|
+
},
|
|
15148
|
+
"required": [
|
|
15149
|
+
"datapoints"
|
|
15150
|
+
],
|
|
15151
|
+
"additionalProperties": false
|
|
15152
|
+
},
|
|
15153
|
+
"datapoint-value": {
|
|
15154
|
+
"type": "object",
|
|
15155
|
+
"properties": {
|
|
15156
|
+
"slug": {
|
|
15157
|
+
"type": "string",
|
|
15158
|
+
"minLength": 2,
|
|
15159
|
+
"maxLength": 63,
|
|
15160
|
+
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
15161
|
+
"description": "The datapoint this value belongs to."
|
|
15162
|
+
},
|
|
15163
|
+
"value": {
|
|
15164
|
+
"description": "The value, shaped by the datapoint: a number, a boolean, a string, or the whole ROS message where the configuration names no field inside it. Any `scale` and `offset` the configuration declares have already been applied, at the robot."
|
|
15165
|
+
},
|
|
15166
|
+
"timestamp_ms": {
|
|
15167
|
+
"type": "integer",
|
|
15168
|
+
"minimum": 0,
|
|
15169
|
+
"maximum": 9007199254740991,
|
|
15170
|
+
"description": "When the value was captured, as a unix timestamp in milliseconds. The **bridge's capture time**, never the time the cloud received it — the one exception is the built-in `bridge_state`, which the cloud observes by construction."
|
|
15171
|
+
}
|
|
15172
|
+
},
|
|
15173
|
+
"required": [
|
|
15174
|
+
"slug",
|
|
15175
|
+
"value",
|
|
15176
|
+
"timestamp_ms"
|
|
15177
|
+
],
|
|
15178
|
+
"additionalProperties": false
|
|
15179
|
+
},
|
|
15180
|
+
"developer-passkey": {
|
|
15181
|
+
"type": "object",
|
|
15182
|
+
"properties": {
|
|
15183
|
+
"id": {
|
|
15184
|
+
"type": "string",
|
|
15185
|
+
"format": "uuid",
|
|
15186
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
15187
|
+
"description": "The passkey in the API, as renamed and removed through `/api/auth/passkeys/:id`."
|
|
15188
|
+
},
|
|
15189
|
+
"name": {
|
|
15190
|
+
"type": "string",
|
|
15191
|
+
"minLength": 1,
|
|
15192
|
+
"maxLength": 80,
|
|
15193
|
+
"description": "What the person called it, such as the device it lives on."
|
|
15194
|
+
},
|
|
15195
|
+
"created_at": {
|
|
15196
|
+
"type": "string",
|
|
15197
|
+
"format": "date-time",
|
|
15198
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
15199
|
+
"description": "When it was registered."
|
|
15200
|
+
},
|
|
15201
|
+
"last_used_at": {
|
|
15202
|
+
"anyOf": [
|
|
15203
|
+
{
|
|
15204
|
+
"type": "string",
|
|
15205
|
+
"format": "date-time",
|
|
15206
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
|
15207
|
+
},
|
|
15208
|
+
{
|
|
15209
|
+
"type": "null"
|
|
15210
|
+
}
|
|
15211
|
+
],
|
|
15212
|
+
"description": "When it last signed the person in or confirmed a sign-in, or `null` if never."
|
|
15213
|
+
},
|
|
15214
|
+
"synced": {
|
|
15215
|
+
"anyOf": [
|
|
15216
|
+
{
|
|
15217
|
+
"type": "boolean"
|
|
15218
|
+
},
|
|
15219
|
+
{
|
|
15220
|
+
"type": "null"
|
|
15221
|
+
}
|
|
15222
|
+
],
|
|
15223
|
+
"description": "Whether the authenticator reported the passkey as syncable across the person's devices (the backup-eligible flag), or `null` when it said nothing."
|
|
15224
|
+
}
|
|
15225
|
+
},
|
|
15226
|
+
"required": [
|
|
15227
|
+
"id",
|
|
15228
|
+
"name",
|
|
15229
|
+
"created_at",
|
|
15230
|
+
"last_used_at",
|
|
15231
|
+
"synced"
|
|
13945
15232
|
],
|
|
13946
15233
|
"additionalProperties": false
|
|
13947
15234
|
},
|
|
13948
|
-
"
|
|
15235
|
+
"developer-two-factor": {
|
|
13949
15236
|
"type": "object",
|
|
13950
15237
|
"properties": {
|
|
13951
|
-
"
|
|
15238
|
+
"passkeys": {
|
|
13952
15239
|
"type": "array",
|
|
13953
15240
|
"items": {
|
|
13954
15241
|
"type": "object",
|
|
13955
15242
|
"properties": {
|
|
13956
|
-
"
|
|
15243
|
+
"id": {
|
|
13957
15244
|
"type": "string",
|
|
13958
|
-
"
|
|
13959
|
-
"
|
|
13960
|
-
"
|
|
13961
|
-
"description": "The name a client reads this datapoint by."
|
|
15245
|
+
"format": "uuid",
|
|
15246
|
+
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
15247
|
+
"description": "The passkey in the API, as renamed and removed through `/api/auth/passkeys/:id`."
|
|
13962
15248
|
},
|
|
13963
|
-
"
|
|
13964
|
-
"type": "
|
|
13965
|
-
"
|
|
15249
|
+
"name": {
|
|
15250
|
+
"type": "string",
|
|
15251
|
+
"minLength": 1,
|
|
15252
|
+
"maxLength": 80,
|
|
15253
|
+
"description": "What the person called it, such as the device it lives on."
|
|
13966
15254
|
},
|
|
13967
|
-
"
|
|
15255
|
+
"created_at": {
|
|
15256
|
+
"type": "string",
|
|
15257
|
+
"format": "date-time",
|
|
15258
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
15259
|
+
"description": "When it was registered."
|
|
15260
|
+
},
|
|
15261
|
+
"last_used_at": {
|
|
13968
15262
|
"anyOf": [
|
|
13969
15263
|
{
|
|
13970
|
-
"type": "string"
|
|
15264
|
+
"type": "string",
|
|
15265
|
+
"format": "date-time",
|
|
15266
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
|
|
13971
15267
|
},
|
|
13972
15268
|
{
|
|
13973
15269
|
"type": "null"
|
|
13974
15270
|
}
|
|
13975
15271
|
],
|
|
13976
|
-
"description": "
|
|
15272
|
+
"description": "When it last signed the person in or confirmed a sign-in, or `null` if never."
|
|
13977
15273
|
},
|
|
13978
|
-
"
|
|
15274
|
+
"synced": {
|
|
13979
15275
|
"anyOf": [
|
|
13980
15276
|
{
|
|
13981
|
-
"type": "
|
|
13982
|
-
"minimum": 0,
|
|
13983
|
-
"maximum": 20
|
|
15277
|
+
"type": "boolean"
|
|
13984
15278
|
},
|
|
13985
15279
|
{
|
|
13986
15280
|
"type": "null"
|
|
13987
15281
|
}
|
|
13988
15282
|
],
|
|
13989
|
-
"description": "
|
|
15283
|
+
"description": "Whether the authenticator reported the passkey as syncable across the person's devices (the backup-eligible flag), or `null` when it said nothing."
|
|
13990
15284
|
}
|
|
13991
15285
|
},
|
|
13992
15286
|
"required": [
|
|
13993
|
-
"
|
|
13994
|
-
"
|
|
13995
|
-
"
|
|
13996
|
-
"
|
|
15287
|
+
"id",
|
|
15288
|
+
"name",
|
|
15289
|
+
"created_at",
|
|
15290
|
+
"last_used_at",
|
|
15291
|
+
"synced"
|
|
13997
15292
|
],
|
|
13998
15293
|
"additionalProperties": false
|
|
13999
15294
|
},
|
|
14000
|
-
"description": "
|
|
14001
|
-
}
|
|
14002
|
-
},
|
|
14003
|
-
"required": [
|
|
14004
|
-
"datapoints"
|
|
14005
|
-
],
|
|
14006
|
-
"additionalProperties": false
|
|
14007
|
-
},
|
|
14008
|
-
"datapoint-value": {
|
|
14009
|
-
"type": "object",
|
|
14010
|
-
"properties": {
|
|
14011
|
-
"slug": {
|
|
14012
|
-
"type": "string",
|
|
14013
|
-
"minLength": 2,
|
|
14014
|
-
"maxLength": 63,
|
|
14015
|
-
"pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
|
|
14016
|
-
"description": "The datapoint this value belongs to."
|
|
15295
|
+
"description": "Every passkey the caller has registered, oldest first. Empty when none."
|
|
14017
15296
|
},
|
|
14018
|
-
"
|
|
14019
|
-
"
|
|
15297
|
+
"authenticator": {
|
|
15298
|
+
"anyOf": [
|
|
15299
|
+
{
|
|
15300
|
+
"type": "object",
|
|
15301
|
+
"properties": {
|
|
15302
|
+
"created_at": {
|
|
15303
|
+
"type": "string",
|
|
15304
|
+
"format": "date-time",
|
|
15305
|
+
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
15306
|
+
"description": "When the authenticator was confirmed."
|
|
15307
|
+
}
|
|
15308
|
+
},
|
|
15309
|
+
"required": [
|
|
15310
|
+
"created_at"
|
|
15311
|
+
],
|
|
15312
|
+
"additionalProperties": false
|
|
15313
|
+
},
|
|
15314
|
+
{
|
|
15315
|
+
"type": "null"
|
|
15316
|
+
}
|
|
15317
|
+
],
|
|
15318
|
+
"description": "The confirmed authenticator app, or `null` when there is none. At most one."
|
|
14020
15319
|
},
|
|
14021
|
-
"
|
|
15320
|
+
"recovery_codes_left": {
|
|
14022
15321
|
"type": "integer",
|
|
14023
15322
|
"minimum": 0,
|
|
14024
|
-
"maximum":
|
|
14025
|
-
"description": "
|
|
15323
|
+
"maximum": 10,
|
|
15324
|
+
"description": "How many of the ten recovery codes are unspent. `0` while the caller has no second factor."
|
|
15325
|
+
},
|
|
15326
|
+
"required_by_org": {
|
|
15327
|
+
"type": "boolean",
|
|
15328
|
+
"description": "Whether the organisation requires a second factor. While it does, the last one cannot be removed."
|
|
14026
15329
|
}
|
|
14027
15330
|
},
|
|
14028
15331
|
"required": [
|
|
14029
|
-
"
|
|
14030
|
-
"
|
|
14031
|
-
"
|
|
15332
|
+
"passkeys",
|
|
15333
|
+
"authenticator",
|
|
15334
|
+
"recovery_codes_left",
|
|
15335
|
+
"required_by_org"
|
|
14032
15336
|
],
|
|
14033
15337
|
"additionalProperties": false
|
|
14034
15338
|
},
|
|
@@ -14200,65 +15504,6 @@
|
|
|
14200
15504
|
],
|
|
14201
15505
|
"additionalProperties": false
|
|
14202
15506
|
},
|
|
14203
|
-
"feedback-request": {
|
|
14204
|
-
"type": "object",
|
|
14205
|
-
"properties": {
|
|
14206
|
-
"kind": {
|
|
14207
|
-
"type": "string",
|
|
14208
|
-
"enum": [
|
|
14209
|
-
"idea",
|
|
14210
|
-
"problem",
|
|
14211
|
-
"question",
|
|
14212
|
-
"other"
|
|
14213
|
-
],
|
|
14214
|
-
"description": "What the message is: an `idea`, a `problem`, a `question` or `other`. It only sorts the inbox; it changes nothing about how the message is handled."
|
|
14215
|
-
},
|
|
14216
|
-
"message": {
|
|
14217
|
-
"type": "string",
|
|
14218
|
-
"minLength": 1,
|
|
14219
|
-
"maxLength": 5000,
|
|
14220
|
-
"description": "What the developer wrote, trimmed. At most 5000 characters; a message that is only whitespace is refused."
|
|
14221
|
-
},
|
|
14222
|
-
"page": {
|
|
14223
|
-
"type": "string",
|
|
14224
|
-
"maxLength": 512,
|
|
14225
|
-
"pattern": "^\\/.*",
|
|
14226
|
-
"description": "The console path the message was sent from, e.g. `/robots/:id/jobs` with its real id. A path, never a full URL, so no host and no query string reach the inbox by accident."
|
|
14227
|
-
}
|
|
14228
|
-
},
|
|
14229
|
-
"required": [
|
|
14230
|
-
"kind",
|
|
14231
|
-
"message",
|
|
14232
|
-
"page"
|
|
14233
|
-
],
|
|
14234
|
-
"additionalProperties": false
|
|
14235
|
-
},
|
|
14236
|
-
"feedback-response": {
|
|
14237
|
-
"type": "object",
|
|
14238
|
-
"properties": {
|
|
14239
|
-
"id": {
|
|
14240
|
-
"type": "string",
|
|
14241
|
-
"format": "uuid",
|
|
14242
|
-
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
14243
|
-
"description": "The stored message. It exists whatever `mail` says."
|
|
14244
|
-
},
|
|
14245
|
-
"mail": {
|
|
14246
|
-
"type": "string",
|
|
14247
|
-
"enum": [
|
|
14248
|
-
"sent",
|
|
14249
|
-
"not_requested",
|
|
14250
|
-
"not_configured",
|
|
14251
|
-
"failed"
|
|
14252
|
-
],
|
|
14253
|
-
"description": "What happened to the notification mail: `sent`, `failed`, or `not_configured` when this cloud has no feedback address. The message is stored in every case, so a client shows success for all three."
|
|
14254
|
-
}
|
|
14255
|
-
},
|
|
14256
|
-
"required": [
|
|
14257
|
-
"id",
|
|
14258
|
-
"mail"
|
|
14259
|
-
],
|
|
14260
|
-
"additionalProperties": false
|
|
14261
|
-
},
|
|
14262
15507
|
"fetch-types-request": {
|
|
14263
15508
|
"type": "object",
|
|
14264
15509
|
"properties": {
|
|
@@ -14479,6 +15724,27 @@
|
|
|
14479
15724
|
],
|
|
14480
15725
|
"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."
|
|
14481
15726
|
},
|
|
15727
|
+
"two_factor": {
|
|
15728
|
+
"type": "object",
|
|
15729
|
+
"properties": {
|
|
15730
|
+
"passkeys": {
|
|
15731
|
+
"type": "integer",
|
|
15732
|
+
"minimum": 0,
|
|
15733
|
+
"maximum": 9007199254740991,
|
|
15734
|
+
"description": "How many passkeys the person has registered."
|
|
15735
|
+
},
|
|
15736
|
+
"authenticator": {
|
|
15737
|
+
"type": "boolean",
|
|
15738
|
+
"description": "Whether the person has a confirmed authenticator app."
|
|
15739
|
+
}
|
|
15740
|
+
},
|
|
15741
|
+
"required": [
|
|
15742
|
+
"passkeys",
|
|
15743
|
+
"authenticator"
|
|
15744
|
+
],
|
|
15745
|
+
"additionalProperties": false,
|
|
15746
|
+
"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`."
|
|
15747
|
+
},
|
|
14482
15748
|
"created_at": {
|
|
14483
15749
|
"type": "string",
|
|
14484
15750
|
"format": "date-time",
|
|
@@ -14492,6 +15758,7 @@
|
|
|
14492
15758
|
"email",
|
|
14493
15759
|
"display_name",
|
|
14494
15760
|
"tier",
|
|
15761
|
+
"two_factor",
|
|
14495
15762
|
"created_at"
|
|
14496
15763
|
],
|
|
14497
15764
|
"additionalProperties": false
|
|
@@ -14543,6 +15810,27 @@
|
|
|
14543
15810
|
],
|
|
14544
15811
|
"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."
|
|
14545
15812
|
},
|
|
15813
|
+
"two_factor": {
|
|
15814
|
+
"type": "object",
|
|
15815
|
+
"properties": {
|
|
15816
|
+
"passkeys": {
|
|
15817
|
+
"type": "integer",
|
|
15818
|
+
"minimum": 0,
|
|
15819
|
+
"maximum": 9007199254740991,
|
|
15820
|
+
"description": "How many passkeys the person has registered."
|
|
15821
|
+
},
|
|
15822
|
+
"authenticator": {
|
|
15823
|
+
"type": "boolean",
|
|
15824
|
+
"description": "Whether the person has a confirmed authenticator app."
|
|
15825
|
+
}
|
|
15826
|
+
},
|
|
15827
|
+
"required": [
|
|
15828
|
+
"passkeys",
|
|
15829
|
+
"authenticator"
|
|
15830
|
+
],
|
|
15831
|
+
"additionalProperties": false,
|
|
15832
|
+
"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`."
|
|
15833
|
+
},
|
|
14546
15834
|
"created_at": {
|
|
14547
15835
|
"type": "string",
|
|
14548
15836
|
"format": "date-time",
|
|
@@ -14556,6 +15844,7 @@
|
|
|
14556
15844
|
"email",
|
|
14557
15845
|
"display_name",
|
|
14558
15846
|
"tier",
|
|
15847
|
+
"two_factor",
|
|
14559
15848
|
"created_at"
|
|
14560
15849
|
],
|
|
14561
15850
|
"additionalProperties": false
|
|
@@ -15281,26 +16570,12 @@
|
|
|
15281
16570
|
"minLength": 1,
|
|
15282
16571
|
"maxLength": 200,
|
|
15283
16572
|
"description": "A display name taken at invoke time — the email for a Fleetless user or an app user, the key's own name for a server key. Storing it rather than joining is the point: renaming a key afterwards does not rewrite history."
|
|
15284
|
-
},
|
|
15285
|
-
"name": {
|
|
15286
|
-
"anyOf": [
|
|
15287
|
-
{
|
|
15288
|
-
"type": "string",
|
|
15289
|
-
"minLength": 1,
|
|
15290
|
-
"maxLength": 200
|
|
15291
|
-
},
|
|
15292
|
-
{
|
|
15293
|
-
"type": "null"
|
|
15294
|
-
}
|
|
15295
|
-
],
|
|
15296
|
-
"description": "The person's display name when the job started: the Fleetless user's `display_name` for a developer, the app user's `display_name` for an app user. `null` for a server key, when the person had no name set, and for runs recorded before contracts 5.3.0. Show `label` when it is null."
|
|
15297
16573
|
}
|
|
15298
16574
|
},
|
|
15299
16575
|
"required": [
|
|
15300
16576
|
"kind",
|
|
15301
16577
|
"id",
|
|
15302
|
-
"label"
|
|
15303
|
-
"name"
|
|
16578
|
+
"label"
|
|
15304
16579
|
],
|
|
15305
16580
|
"additionalProperties": false,
|
|
15306
16581
|
"description": "Who invoked the run, and what they were acting as at the time."
|
|
@@ -16479,37 +17754,6 @@
|
|
|
16479
17754
|
"new_password"
|
|
16480
17755
|
]
|
|
16481
17756
|
},
|
|
16482
|
-
"password-reset-confirm": {
|
|
16483
|
-
"type": "object",
|
|
16484
|
-
"properties": {
|
|
16485
|
-
"token": {
|
|
16486
|
-
"type": "string",
|
|
16487
|
-
"minLength": 1
|
|
16488
|
-
},
|
|
16489
|
-
"new_password": {
|
|
16490
|
-
"type": "string",
|
|
16491
|
-
"minLength": 12,
|
|
16492
|
-
"maxLength": 256
|
|
16493
|
-
}
|
|
16494
|
-
},
|
|
16495
|
-
"required": [
|
|
16496
|
-
"token",
|
|
16497
|
-
"new_password"
|
|
16498
|
-
]
|
|
16499
|
-
},
|
|
16500
|
-
"password-reset-request": {
|
|
16501
|
-
"type": "object",
|
|
16502
|
-
"properties": {
|
|
16503
|
-
"email": {
|
|
16504
|
-
"type": "string",
|
|
16505
|
-
"format": "email",
|
|
16506
|
-
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
|
16507
|
-
}
|
|
16508
|
-
},
|
|
16509
|
-
"required": [
|
|
16510
|
-
"email"
|
|
16511
|
-
]
|
|
16512
|
-
},
|
|
16513
17757
|
"patch-app-oidc-provider-request": {
|
|
16514
17758
|
"type": "object",
|
|
16515
17759
|
"properties": {
|
|
@@ -16636,14 +17880,16 @@
|
|
|
16636
17880
|
"type": "object",
|
|
16637
17881
|
"properties": {
|
|
16638
17882
|
"name": {
|
|
17883
|
+
"description": "The organisation's new display name. Absent leaves it alone.",
|
|
16639
17884
|
"type": "string",
|
|
16640
17885
|
"minLength": 1,
|
|
16641
17886
|
"maxLength": 120
|
|
17887
|
+
},
|
|
17888
|
+
"require_two_factor": {
|
|
17889
|
+
"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.",
|
|
17890
|
+
"type": "boolean"
|
|
16642
17891
|
}
|
|
16643
17892
|
},
|
|
16644
|
-
"required": [
|
|
16645
|
-
"name"
|
|
16646
|
-
],
|
|
16647
17893
|
"additionalProperties": false
|
|
16648
17894
|
},
|
|
16649
17895
|
"patch-org-response": {
|
|
@@ -16664,6 +17910,10 @@
|
|
|
16664
17910
|
"maxLength": 120,
|
|
16665
17911
|
"description": "The organisation's display name. Free text, changed through `PATCH /api/org`."
|
|
16666
17912
|
},
|
|
17913
|
+
"require_two_factor": {
|
|
17914
|
+
"type": "boolean",
|
|
17915
|
+
"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`."
|
|
17916
|
+
},
|
|
16667
17917
|
"created_at": {
|
|
16668
17918
|
"type": "string",
|
|
16669
17919
|
"format": "date-time",
|
|
@@ -16674,6 +17924,7 @@
|
|
|
16674
17924
|
"required": [
|
|
16675
17925
|
"id",
|
|
16676
17926
|
"name",
|
|
17927
|
+
"require_two_factor",
|
|
16677
17928
|
"created_at"
|
|
16678
17929
|
],
|
|
16679
17930
|
"additionalProperties": false,
|
|
@@ -16865,29 +18116,37 @@
|
|
|
16865
18116
|
"message"
|
|
16866
18117
|
]
|
|
16867
18118
|
},
|
|
16868
|
-
"put-app-auth-
|
|
18119
|
+
"put-app-auth-look-request": {
|
|
16869
18120
|
"type": "object",
|
|
16870
18121
|
"properties": {
|
|
16871
|
-
"
|
|
16872
|
-
"type": "boolean",
|
|
16873
|
-
"description": "Whether this app serves an MCP endpoint at `/mcp/<identifier>`. Off refuses the whole OAuth surface for the app, not merely the tool calls, and is re-read on every request rather than cached off a token."
|
|
16874
|
-
},
|
|
16875
|
-
"mcp_login_url": {
|
|
18122
|
+
"hosted_accent": {
|
|
16876
18123
|
"anyOf": [
|
|
16877
18124
|
{
|
|
16878
18125
|
"type": "string",
|
|
16879
|
-
"
|
|
18126
|
+
"pattern": "^#[0-9a-f]{6}$"
|
|
16880
18127
|
},
|
|
16881
18128
|
{
|
|
16882
18129
|
"type": "null"
|
|
16883
18130
|
}
|
|
16884
18131
|
],
|
|
16885
|
-
"description": "The
|
|
18132
|
+
"description": "The accent colour of the hosted pages, `#rrggbb` in lowercase, or `null` for the neutral shell's own."
|
|
16886
18133
|
}
|
|
16887
18134
|
},
|
|
16888
18135
|
"required": [
|
|
16889
|
-
"
|
|
16890
|
-
|
|
18136
|
+
"hosted_accent"
|
|
18137
|
+
],
|
|
18138
|
+
"additionalProperties": false
|
|
18139
|
+
},
|
|
18140
|
+
"put-app-auth-mcp-request": {
|
|
18141
|
+
"type": "object",
|
|
18142
|
+
"properties": {
|
|
18143
|
+
"mcp_enabled": {
|
|
18144
|
+
"type": "boolean",
|
|
18145
|
+
"description": "Whether this app serves an MCP endpoint at `/mcp/<identifier>`. Off refuses the whole OAuth surface for the app, not merely the tool calls, and is re-read on every request rather than cached off a token."
|
|
18146
|
+
}
|
|
18147
|
+
},
|
|
18148
|
+
"required": [
|
|
18149
|
+
"mcp_enabled"
|
|
16891
18150
|
],
|
|
16892
18151
|
"additionalProperties": false
|
|
16893
18152
|
},
|
|
@@ -16926,9 +18185,60 @@
|
|
|
16926
18185
|
],
|
|
16927
18186
|
"additionalProperties": false
|
|
16928
18187
|
},
|
|
18188
|
+
"put-app-auth-sign-in-request": {
|
|
18189
|
+
"type": "object",
|
|
18190
|
+
"properties": {
|
|
18191
|
+
"sign_in_methods": {
|
|
18192
|
+
"type": "object",
|
|
18193
|
+
"properties": {
|
|
18194
|
+
"password": {
|
|
18195
|
+
"type": "boolean",
|
|
18196
|
+
"description": "Whether app users may sign in with a password. Off refuses `POST /api/client/login` with `method_not_allowed`, and registration and invitations then take no password."
|
|
18197
|
+
},
|
|
18198
|
+
"email_code": {
|
|
18199
|
+
"type": "boolean",
|
|
18200
|
+
"description": "Whether app users may sign in with a six-digit code mailed to them, valid ten minutes. A code needs no URL, so it works in local development and in an app with no web UI."
|
|
18201
|
+
}
|
|
18202
|
+
},
|
|
18203
|
+
"required": [
|
|
18204
|
+
"password",
|
|
18205
|
+
"email_code"
|
|
18206
|
+
],
|
|
18207
|
+
"additionalProperties": false,
|
|
18208
|
+
"description": "Which sign-in methods the app offers: password, emailed code, or both — at least one. Identity providers stay on top of either. The default is password only."
|
|
18209
|
+
},
|
|
18210
|
+
"two_factor": {
|
|
18211
|
+
"type": "string",
|
|
18212
|
+
"enum": [
|
|
18213
|
+
"off",
|
|
18214
|
+
"optional",
|
|
18215
|
+
"required"
|
|
18216
|
+
],
|
|
18217
|
+
"description": "Whether the app asks for an authenticator code: `off` (the default), `optional` or `required`. A person with a confirmed authenticator is asked at every sign-in whatever the policy; a sign-in through an identity provider is never asked."
|
|
18218
|
+
}
|
|
18219
|
+
},
|
|
18220
|
+
"required": [
|
|
18221
|
+
"sign_in_methods",
|
|
18222
|
+
"two_factor"
|
|
18223
|
+
],
|
|
18224
|
+
"additionalProperties": false
|
|
18225
|
+
},
|
|
16929
18226
|
"put-app-auth-urls-request": {
|
|
16930
18227
|
"type": "object",
|
|
16931
18228
|
"properties": {
|
|
18229
|
+
"app_url": {
|
|
18230
|
+
"anyOf": [
|
|
18231
|
+
{
|
|
18232
|
+
"type": "string",
|
|
18233
|
+
"maxLength": 500,
|
|
18234
|
+
"format": "uri"
|
|
18235
|
+
},
|
|
18236
|
+
{
|
|
18237
|
+
"type": "null"
|
|
18238
|
+
}
|
|
18239
|
+
],
|
|
18240
|
+
"description": "The app's own home page, linked as `Open <app>` when a hosted flow is done. `null` makes the hosted done page say `You can close this tab`."
|
|
18241
|
+
},
|
|
16932
18242
|
"invite_url": {
|
|
16933
18243
|
"anyOf": [
|
|
16934
18244
|
{
|
|
@@ -16939,7 +18249,7 @@
|
|
|
16939
18249
|
"type": "null"
|
|
16940
18250
|
}
|
|
16941
18251
|
],
|
|
16942
|
-
"description": "The page in the developer's app that accepts an invitation, with `{token}` where the token goes. `null`
|
|
18252
|
+
"description": "The page in the developer's app that accepts an invitation, with `{token}` where the token goes. `null` means the hosted page in `hosted_pages` is used."
|
|
16943
18253
|
},
|
|
16944
18254
|
"verify_url": {
|
|
16945
18255
|
"anyOf": [
|
|
@@ -16951,7 +18261,7 @@
|
|
|
16951
18261
|
"type": "null"
|
|
16952
18262
|
}
|
|
16953
18263
|
],
|
|
16954
|
-
"description": "The page that confirms a new address, with `{token}` where the token goes.
|
|
18264
|
+
"description": "The page that confirms a new address, with `{token}` where the token goes. `null` means the hosted page in `hosted_pages` is used."
|
|
16955
18265
|
},
|
|
16956
18266
|
"reset_url": {
|
|
16957
18267
|
"anyOf": [
|
|
@@ -16963,13 +18273,27 @@
|
|
|
16963
18273
|
"type": "null"
|
|
16964
18274
|
}
|
|
16965
18275
|
],
|
|
16966
|
-
"description": "The page that takes a new password, with `{token}` where the token goes."
|
|
18276
|
+
"description": "The page that takes a new password, with `{token}` where the token goes. `null` means the hosted page in `hosted_pages` is used."
|
|
18277
|
+
},
|
|
18278
|
+
"mcp_login_url": {
|
|
18279
|
+
"anyOf": [
|
|
18280
|
+
{
|
|
18281
|
+
"type": "string",
|
|
18282
|
+
"maxLength": 500
|
|
18283
|
+
},
|
|
18284
|
+
{
|
|
18285
|
+
"type": "null"
|
|
18286
|
+
}
|
|
18287
|
+
],
|
|
18288
|
+
"description": "The page an MCP authorization redirects to, with `{interaction}` where the interaction id goes. Not a token: the id names a pending request the server already holds, and the app authenticates the user itself before approving it. `null` means the hosted MCP sign-in in `hosted_pages` is used."
|
|
16967
18289
|
}
|
|
16968
18290
|
},
|
|
16969
18291
|
"required": [
|
|
18292
|
+
"app_url",
|
|
16970
18293
|
"invite_url",
|
|
16971
18294
|
"verify_url",
|
|
16972
|
-
"reset_url"
|
|
18295
|
+
"reset_url",
|
|
18296
|
+
"mcp_login_url"
|
|
16973
18297
|
],
|
|
16974
18298
|
"additionalProperties": false
|
|
16975
18299
|
},
|
|
@@ -17101,6 +18425,25 @@
|
|
|
17101
18425
|
],
|
|
17102
18426
|
"additionalProperties": false
|
|
17103
18427
|
},
|
|
18428
|
+
"recovery-codes-response": {
|
|
18429
|
+
"type": "object",
|
|
18430
|
+
"properties": {
|
|
18431
|
+
"recovery_codes": {
|
|
18432
|
+
"minItems": 10,
|
|
18433
|
+
"maxItems": 10,
|
|
18434
|
+
"type": "array",
|
|
18435
|
+
"items": {
|
|
18436
|
+
"type": "string",
|
|
18437
|
+
"pattern": "^[a-z2-7]{5}-[a-z2-7]{5}$"
|
|
18438
|
+
},
|
|
18439
|
+
"description": "The ten new recovery codes, lowercase, shown once. Every earlier code is void."
|
|
18440
|
+
}
|
|
18441
|
+
},
|
|
18442
|
+
"required": [
|
|
18443
|
+
"recovery_codes"
|
|
18444
|
+
],
|
|
18445
|
+
"additionalProperties": false
|
|
18446
|
+
},
|
|
17104
18447
|
"refresh-request": {
|
|
17105
18448
|
"type": "object",
|
|
17106
18449
|
"properties": {
|
|
@@ -17113,6 +18456,21 @@
|
|
|
17113
18456
|
"refresh_token"
|
|
17114
18457
|
]
|
|
17115
18458
|
},
|
|
18459
|
+
"rename-passkey-request": {
|
|
18460
|
+
"type": "object",
|
|
18461
|
+
"properties": {
|
|
18462
|
+
"name": {
|
|
18463
|
+
"type": "string",
|
|
18464
|
+
"minLength": 1,
|
|
18465
|
+
"maxLength": 80,
|
|
18466
|
+
"description": "The new name."
|
|
18467
|
+
}
|
|
18468
|
+
},
|
|
18469
|
+
"required": [
|
|
18470
|
+
"name"
|
|
18471
|
+
],
|
|
18472
|
+
"additionalProperties": false
|
|
18473
|
+
},
|
|
17116
18474
|
"rename-slug-request": {
|
|
17117
18475
|
"type": "object",
|
|
17118
18476
|
"properties": {
|
|
@@ -17906,7 +19264,7 @@
|
|
|
17906
19264
|
},
|
|
17907
19265
|
"builtin": {
|
|
17908
19266
|
"type": "boolean",
|
|
17909
|
-
"description": "`true` for the two roles every app starts with. Their **rights may be re-scoped** exactly like a custom role's, through `PUT /api/apps/:id/roles/:roleId/permissions` — the flag exists so the console can explain where they came from, not to protect them.
|
|
19267
|
+
"description": "`true` for the two roles every app starts with. Their **rights may be re-scoped** exactly like a custom role's, through `PUT /api/apps/:id/roles/:roleId/permissions` — the flag exists so the console can explain where they came from, not to protect them. It does not make them renamable or deletable — no route does that for any role."
|
|
17910
19268
|
}
|
|
17911
19269
|
},
|
|
17912
19270
|
"required": [
|
|
@@ -17945,7 +19303,7 @@
|
|
|
17945
19303
|
},
|
|
17946
19304
|
"builtin": {
|
|
17947
19305
|
"type": "boolean",
|
|
17948
|
-
"description": "`true` for the two roles every app starts with. Their **rights may be re-scoped** exactly like a custom role's, through `PUT /api/apps/:id/roles/:roleId/permissions` — the flag exists so the console can explain where they came from, not to protect them.
|
|
19306
|
+
"description": "`true` for the two roles every app starts with. Their **rights may be re-scoped** exactly like a custom role's, through `PUT /api/apps/:id/roles/:roleId/permissions` — the flag exists so the console can explain where they came from, not to protect them. It does not make them renamable or deletable — no route does that for any role."
|
|
17949
19307
|
}
|
|
17950
19308
|
},
|
|
17951
19309
|
"required": [
|
|
@@ -18024,21 +19382,6 @@
|
|
|
18024
19382
|
"capabilities"
|
|
18025
19383
|
]
|
|
18026
19384
|
},
|
|
18027
|
-
"role-rename-request": {
|
|
18028
|
-
"type": "object",
|
|
18029
|
-
"properties": {
|
|
18030
|
-
"name": {
|
|
18031
|
-
"type": "string",
|
|
18032
|
-
"minLength": 1,
|
|
18033
|
-
"maxLength": 60,
|
|
18034
|
-
"description": "The new name, trimmed, 1 to 60 characters. Unique per app: another role of this app with the same name answers `409 role_name_taken`. The role's users keep it under its new name."
|
|
18035
|
-
}
|
|
18036
|
-
},
|
|
18037
|
-
"required": [
|
|
18038
|
-
"name"
|
|
18039
|
-
],
|
|
18040
|
-
"additionalProperties": false
|
|
18041
|
-
},
|
|
18042
19385
|
"server-key-list-response": {
|
|
18043
19386
|
"type": "object",
|
|
18044
19387
|
"properties": {
|
|
@@ -18129,157 +19472,6 @@
|
|
|
18129
19472
|
],
|
|
18130
19473
|
"additionalProperties": false
|
|
18131
19474
|
},
|
|
18132
|
-
"sign-up-request": {
|
|
18133
|
-
"type": "object",
|
|
18134
|
-
"properties": {
|
|
18135
|
-
"org_name": {
|
|
18136
|
-
"type": "string",
|
|
18137
|
-
"minLength": 1,
|
|
18138
|
-
"maxLength": 120
|
|
18139
|
-
},
|
|
18140
|
-
"email": {
|
|
18141
|
-
"type": "string",
|
|
18142
|
-
"format": "email",
|
|
18143
|
-
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
|
|
18144
|
-
},
|
|
18145
|
-
"password": {
|
|
18146
|
-
"type": "string",
|
|
18147
|
-
"minLength": 12,
|
|
18148
|
-
"maxLength": 256
|
|
18149
|
-
}
|
|
18150
|
-
},
|
|
18151
|
-
"required": [
|
|
18152
|
-
"org_name",
|
|
18153
|
-
"email",
|
|
18154
|
-
"password"
|
|
18155
|
-
]
|
|
18156
|
-
},
|
|
18157
|
-
"sign-up-response": {
|
|
18158
|
-
"type": "object",
|
|
18159
|
-
"properties": {
|
|
18160
|
-
"org": {
|
|
18161
|
-
"type": "object",
|
|
18162
|
-
"properties": {
|
|
18163
|
-
"id": {
|
|
18164
|
-
"type": "string",
|
|
18165
|
-
"format": "uuid",
|
|
18166
|
-
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
18167
|
-
"description": "The organisation. Every developer route is scoped to the caller's org already, so a client rarely has to send this anywhere."
|
|
18168
|
-
},
|
|
18169
|
-
"name": {
|
|
18170
|
-
"type": "string",
|
|
18171
|
-
"minLength": 1,
|
|
18172
|
-
"maxLength": 120,
|
|
18173
|
-
"description": "The organisation's display name. Free text, changed through `PATCH /api/org`."
|
|
18174
|
-
},
|
|
18175
|
-
"created_at": {
|
|
18176
|
-
"type": "string",
|
|
18177
|
-
"format": "date-time",
|
|
18178
|
-
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
18179
|
-
"description": "When the organisation was created, as an ISO 8601 timestamp."
|
|
18180
|
-
}
|
|
18181
|
-
},
|
|
18182
|
-
"required": [
|
|
18183
|
-
"id",
|
|
18184
|
-
"name",
|
|
18185
|
-
"created_at"
|
|
18186
|
-
],
|
|
18187
|
-
"additionalProperties": false
|
|
18188
|
-
},
|
|
18189
|
-
"user": {
|
|
18190
|
-
"type": "object",
|
|
18191
|
-
"properties": {
|
|
18192
|
-
"id": {
|
|
18193
|
-
"type": "string",
|
|
18194
|
-
"format": "uuid",
|
|
18195
|
-
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
18196
|
-
"description": "The Fleetless user in the API, assigned by the cloud and stable for the life of the account."
|
|
18197
|
-
},
|
|
18198
|
-
"org_id": {
|
|
18199
|
-
"type": "string",
|
|
18200
|
-
"format": "uuid",
|
|
18201
|
-
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
|
|
18202
|
-
"description": "The organisation this person belongs to. Every developer route is already scoped to the caller's org, so this confirms what a client is looking at, not a filter it applies."
|
|
18203
|
-
},
|
|
18204
|
-
"email": {
|
|
18205
|
-
"type": "string",
|
|
18206
|
-
"format": "email",
|
|
18207
|
-
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
|
|
18208
|
-
"description": "The address the account is identified by, **globally unique** across every organisation. Immutable after creation: it is what every invitation, reset link and audit line names."
|
|
18209
|
-
},
|
|
18210
|
-
"display_name": {
|
|
18211
|
-
"anyOf": [
|
|
18212
|
-
{
|
|
18213
|
-
"type": "string",
|
|
18214
|
-
"minLength": 1,
|
|
18215
|
-
"maxLength": 120
|
|
18216
|
-
},
|
|
18217
|
-
{
|
|
18218
|
-
"type": "null"
|
|
18219
|
-
}
|
|
18220
|
-
],
|
|
18221
|
-
"description": "Optional human name, shown by the console instead of the address where present. Self-service through `PATCH /api/auth/me`; never used for authentication. `null` when the person never supplied one."
|
|
18222
|
-
},
|
|
18223
|
-
"tier": {
|
|
18224
|
-
"type": "string",
|
|
18225
|
-
"enum": [
|
|
18226
|
-
"owner",
|
|
18227
|
-
"developer"
|
|
18228
|
-
],
|
|
18229
|
-
"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."
|
|
18230
|
-
},
|
|
18231
|
-
"created_at": {
|
|
18232
|
-
"type": "string",
|
|
18233
|
-
"format": "date-time",
|
|
18234
|
-
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
|
18235
|
-
"description": "When the account was created, as an ISO 8601 timestamp."
|
|
18236
|
-
}
|
|
18237
|
-
},
|
|
18238
|
-
"required": [
|
|
18239
|
-
"id",
|
|
18240
|
-
"org_id",
|
|
18241
|
-
"email",
|
|
18242
|
-
"display_name",
|
|
18243
|
-
"tier",
|
|
18244
|
-
"created_at"
|
|
18245
|
-
],
|
|
18246
|
-
"additionalProperties": false
|
|
18247
|
-
},
|
|
18248
|
-
"tokens": {
|
|
18249
|
-
"type": "object",
|
|
18250
|
-
"properties": {
|
|
18251
|
-
"access_token": {
|
|
18252
|
-
"type": "string",
|
|
18253
|
-
"minLength": 1,
|
|
18254
|
-
"description": "The token to send as `Authorization: Bearer <token>` on every call. Short-lived: read `expires_in` rather than assuming a lifetime."
|
|
18255
|
-
},
|
|
18256
|
-
"refresh_token": {
|
|
18257
|
-
"type": "string",
|
|
18258
|
-
"minLength": 1,
|
|
18259
|
-
"description": "The token that buys the next access token. It rotates on every use, so a value presented twice is detectable theft and ends the whole family."
|
|
18260
|
-
},
|
|
18261
|
-
"expires_in": {
|
|
18262
|
-
"type": "integer",
|
|
18263
|
-
"exclusiveMinimum": 0,
|
|
18264
|
-
"maximum": 9007199254740991,
|
|
18265
|
-
"description": "How long the access token stays valid, in **seconds** from now. Not a timestamp, and not milliseconds."
|
|
18266
|
-
}
|
|
18267
|
-
},
|
|
18268
|
-
"required": [
|
|
18269
|
-
"access_token",
|
|
18270
|
-
"refresh_token",
|
|
18271
|
-
"expires_in"
|
|
18272
|
-
],
|
|
18273
|
-
"additionalProperties": false
|
|
18274
|
-
}
|
|
18275
|
-
},
|
|
18276
|
-
"required": [
|
|
18277
|
-
"org",
|
|
18278
|
-
"user",
|
|
18279
|
-
"tokens"
|
|
18280
|
-
],
|
|
18281
|
-
"additionalProperties": false
|
|
18282
|
-
},
|
|
18283
19475
|
"slug-usage-response": {
|
|
18284
19476
|
"type": "object",
|
|
18285
19477
|
"properties": {
|
|
@@ -18467,6 +19659,66 @@
|
|
|
18467
19659
|
],
|
|
18468
19660
|
"additionalProperties": false
|
|
18469
19661
|
},
|
|
19662
|
+
"totp-confirm-request": {
|
|
19663
|
+
"type": "object",
|
|
19664
|
+
"properties": {
|
|
19665
|
+
"code": {
|
|
19666
|
+
"type": "string",
|
|
19667
|
+
"pattern": "^\\d{6}$",
|
|
19668
|
+
"description": "A code the new authenticator shows now. It proves the secret was copied correctly before anything depends on it."
|
|
19669
|
+
}
|
|
19670
|
+
},
|
|
19671
|
+
"required": [
|
|
19672
|
+
"code"
|
|
19673
|
+
],
|
|
19674
|
+
"additionalProperties": false
|
|
19675
|
+
},
|
|
19676
|
+
"totp-confirm-response": {
|
|
19677
|
+
"type": "object",
|
|
19678
|
+
"properties": {
|
|
19679
|
+
"recovery_codes": {
|
|
19680
|
+
"anyOf": [
|
|
19681
|
+
{
|
|
19682
|
+
"minItems": 10,
|
|
19683
|
+
"maxItems": 10,
|
|
19684
|
+
"type": "array",
|
|
19685
|
+
"items": {
|
|
19686
|
+
"type": "string",
|
|
19687
|
+
"pattern": "^[a-z2-7]{5}-[a-z2-7]{5}$"
|
|
19688
|
+
}
|
|
19689
|
+
},
|
|
19690
|
+
{
|
|
19691
|
+
"type": "null"
|
|
19692
|
+
}
|
|
19693
|
+
],
|
|
19694
|
+
"description": "The ten recovery codes, shown once, when this is the account's first second factor; `null` otherwise."
|
|
19695
|
+
}
|
|
19696
|
+
},
|
|
19697
|
+
"required": [
|
|
19698
|
+
"recovery_codes"
|
|
19699
|
+
],
|
|
19700
|
+
"additionalProperties": false
|
|
19701
|
+
},
|
|
19702
|
+
"two-factor-setup-response": {
|
|
19703
|
+
"type": "object",
|
|
19704
|
+
"properties": {
|
|
19705
|
+
"secret": {
|
|
19706
|
+
"type": "string",
|
|
19707
|
+
"minLength": 1,
|
|
19708
|
+
"description": "The shared secret, base32, for an authenticator app that cannot scan a QR code. Shown once; the cloud stores it encrypted."
|
|
19709
|
+
},
|
|
19710
|
+
"otpauth_url": {
|
|
19711
|
+
"type": "string",
|
|
19712
|
+
"pattern": "^otpauth:\\/\\/totp\\/.*",
|
|
19713
|
+
"description": "The same secret as an `otpauth://totp/` URL, to render as a QR code. It carries the secret: never log it."
|
|
19714
|
+
}
|
|
19715
|
+
},
|
|
19716
|
+
"required": [
|
|
19717
|
+
"secret",
|
|
19718
|
+
"otpauth_url"
|
|
19719
|
+
],
|
|
19720
|
+
"additionalProperties": false
|
|
19721
|
+
},
|
|
18470
19722
|
"types-response": {
|
|
18471
19723
|
"type": "object",
|
|
18472
19724
|
"properties": {
|
|
@@ -18664,6 +19916,23 @@
|
|
|
18664
19916
|
"required": [
|
|
18665
19917
|
"email"
|
|
18666
19918
|
]
|
|
19919
|
+
},
|
|
19920
|
+
"webauthn-options-response": {
|
|
19921
|
+
"type": "object",
|
|
19922
|
+
"properties": {
|
|
19923
|
+
"options": {
|
|
19924
|
+
"type": "object",
|
|
19925
|
+
"propertyNames": {
|
|
19926
|
+
"type": "string"
|
|
19927
|
+
},
|
|
19928
|
+
"additionalProperties": {},
|
|
19929
|
+
"description": "The `PublicKeyCredentialCreationOptionsJSON` or `PublicKeyCredentialRequestOptionsJSON` to pass to the browser. Its challenge is single-use and short-lived."
|
|
19930
|
+
}
|
|
19931
|
+
},
|
|
19932
|
+
"required": [
|
|
19933
|
+
"options"
|
|
19934
|
+
],
|
|
19935
|
+
"additionalProperties": false
|
|
18667
19936
|
}
|
|
18668
19937
|
}
|
|
18669
19938
|
}
|