@fleetless/contracts 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/CHANGELOG.md +17 -2
  2. package/CONTRIBUTING.md +100 -75
  3. package/README.md +69 -83
  4. package/SECURITY.md +24 -24
  5. package/artifacts/openapi.json +359 -65
  6. package/artifacts/routes.json +74 -3
  7. package/artifacts/schema/app-list-response.schema.json +2 -2
  8. package/artifacts/schema/app-oidc-provider-list-response.schema.json +1 -1
  9. package/artifacts/schema/app-oidc-provider.schema.json +1 -1
  10. package/artifacts/schema/app-user-list-response.schema.json +2 -2
  11. package/artifacts/schema/app-user.schema.json +2 -2
  12. package/artifacts/schema/app.schema.json +2 -2
  13. package/artifacts/schema/asset-list-response.schema.json +7 -7
  14. package/artifacts/schema/asset-sync-request.schema.json +1 -1
  15. package/artifacts/schema/asset-sync-status.schema.json +1 -1
  16. package/artifacts/schema/asset.schema.json +3 -3
  17. package/artifacts/schema/auth-me-response.schema.json +2 -2
  18. package/artifacts/schema/auth-ok.schema.json +1 -1
  19. package/artifacts/schema/authorization-server-metadata.schema.json +1 -1
  20. package/artifacts/schema/bridge-asset-progress.schema.json +1 -1
  21. package/artifacts/schema/busy-details.schema.json +3 -3
  22. package/artifacts/schema/client-identity.schema.json +1 -1
  23. package/artifacts/schema/client-login-request.schema.json +1 -1
  24. package/artifacts/schema/client-logout-request.schema.json +1 -1
  25. package/artifacts/schema/client-mcp-interaction.schema.json +1 -1
  26. package/artifacts/schema/client-robot-list-item.schema.json +70 -0
  27. package/artifacts/schema/client-robot-list-response.schema.json +83 -0
  28. package/artifacts/schema/cloud-config.schema.json +1 -1
  29. package/artifacts/schema/command-result.schema.json +3 -3
  30. package/artifacts/schema/config-draft-response.schema.json +1 -1
  31. package/artifacts/schema/config-version-response.schema.json +1 -1
  32. package/artifacts/schema/create-server-key-response.schema.json +1 -1
  33. package/artifacts/schema/datapoint-config.schema.json +1 -1
  34. package/artifacts/schema/datapoint-value.schema.json +2 -2
  35. package/artifacts/schema/dynamic-client-registration-request.schema.json +2 -2
  36. package/artifacts/schema/fleetless-user-list-response.schema.json +2 -2
  37. package/artifacts/schema/fleetless-user.schema.json +2 -2
  38. package/artifacts/schema/invoke-or-service-response.schema.json +4 -4
  39. package/artifacts/schema/invoke-response.schema.json +3 -3
  40. package/artifacts/schema/job-actor.schema.json +1 -1
  41. package/artifacts/schema/job-event.schema.json +3 -3
  42. package/artifacts/schema/job-response.schema.json +3 -3
  43. package/artifacts/schema/job-run-list-response.schema.json +2 -2
  44. package/artifacts/schema/job-run.schema.json +2 -2
  45. package/artifacts/schema/job.schema.json +3 -3
  46. package/artifacts/schema/mcp-consent-grant-list-response.schema.json +2 -2
  47. package/artifacts/schema/mcp-consent-grant.schema.json +2 -2
  48. package/artifacts/schema/oauth-authorize-query.schema.json +1 -1
  49. package/artifacts/schema/oauth-token-request.schema.json +1 -1
  50. package/artifacts/schema/patch-org-response.schema.json +1 -1
  51. package/artifacts/schema/patch-robot-response.schema.json +1 -1
  52. package/artifacts/schema/robot-config-doc.schema.json +1 -1
  53. package/artifacts/schema/robot-jobs-response.schema.json +3 -3
  54. package/artifacts/schema/role-list-response.schema.json +1 -1
  55. package/artifacts/schema/role.schema.json +1 -1
  56. package/artifacts/schema/server-key-list-response.schema.json +2 -2
  57. package/artifacts/schema/server-key.schema.json +1 -1
  58. package/artifacts/schema/service-call-response.schema.json +1 -1
  59. package/artifacts/schema/sign-up-response.schema.json +2 -2
  60. package/artifacts/schema/urdf-completeness.schema.json +2 -2
  61. package/artifacts/schema-outgoing/bridge-asset-progress.schema.json +1 -1
  62. package/dist/alerts.d.ts +15 -17
  63. package/dist/alerts.js +15 -17
  64. package/dist/app-users.d.ts +5 -6
  65. package/dist/app-users.js +9 -10
  66. package/dist/apps.d.ts +5 -6
  67. package/dist/apps.js +11 -12
  68. package/dist/assets.js +10 -13
  69. package/dist/audit.d.ts +10 -12
  70. package/dist/audit.js +14 -17
  71. package/dist/client-auth.d.ts +8 -9
  72. package/dist/client-auth.js +14 -15
  73. package/dist/client-robots.d.ts +37 -0
  74. package/dist/client-robots.js +30 -0
  75. package/dist/config-issues.d.ts +3 -3
  76. package/dist/config-issues.js +3 -3
  77. package/dist/config.d.ts +5 -6
  78. package/dist/config.js +7 -8
  79. package/dist/errors.d.ts +4 -4
  80. package/dist/errors.js +8 -9
  81. package/dist/identity.d.ts +4 -5
  82. package/dist/identity.js +7 -8
  83. package/dist/index.d.ts +3 -1
  84. package/dist/index.js +2 -1
  85. package/dist/jobs.js +5 -5
  86. package/dist/mcp.d.ts +11 -9
  87. package/dist/mcp.js +8 -3
  88. package/dist/oauth.d.ts +8 -10
  89. package/dist/oauth.js +13 -15
  90. package/dist/protocol.d.ts +7 -8
  91. package/dist/protocol.js +20 -22
  92. package/dist/realtime.d.ts +2 -2
  93. package/dist/realtime.js +4 -4
  94. package/dist/rest.js +4 -4
  95. package/dist/routes.js +40 -4
  96. package/package.json +1 -1
@@ -1822,7 +1822,7 @@
1822
1822
  "name": "clientId",
1823
1823
  "in": "path",
1824
1824
  "required": true,
1825
- "description": "The MCP client, as `GET /api/apps/:id/users/:userId/mcp-grants` reports its `client_id`. Not a uuid — it is the identifier the dynamic registration issued.",
1825
+ "description": "The MCP client, as `GET /api/apps/:id/users/:userId/mcp-grants` reports its `client_id`. Not a uuid — the identifier dynamic registration issued.",
1826
1826
  "schema": {
1827
1827
  "type": "string"
1828
1828
  }
@@ -3441,7 +3441,7 @@
3441
3441
  "schema": {
3442
3442
  "type": "string",
3443
3443
  "minLength": 1,
3444
- "description": "The PKCE challenge; the verifier is presented at the token endpoint. Only non-emptiness is checked here — length and alphabet are not — since the verifier is what actually has to match."
3444
+ "description": "The PKCE challenge; the verifier is presented at the token endpoint. Only non-emptiness is checked here — length and alphabet are not — since the verifier is what has to match."
3445
3445
  }
3446
3446
  },
3447
3447
  {
@@ -3882,7 +3882,7 @@
3882
3882
  "schema": {
3883
3883
  "type": "string",
3884
3884
  "minLength": 1,
3885
- "description": "The PKCE challenge; the verifier is presented at the token endpoint. Only non-emptiness is checked here — length and alphabet are not — since the verifier is what actually has to match."
3885
+ "description": "The PKCE challenge; the verifier is presented at the token endpoint. Only non-emptiness is checked here — length and alphabet are not — since the verifier is what has to match."
3886
3886
  }
3887
3887
  },
3888
3888
  {
@@ -4915,7 +4915,7 @@
4915
4915
  "name": "clientId",
4916
4916
  "in": "path",
4917
4917
  "required": true,
4918
- "description": "The MCP client, as `GET /api/client/mcp/grants` reports its `client_id`. Not a uuid — it is the identifier the dynamic registration issued.",
4918
+ "description": "The MCP client, as `GET /api/client/mcp/grants` reports its `client_id`. Not a uuid — the identifier dynamic registration issued.",
4919
4919
  "schema": {
4920
4920
  "type": "string"
4921
4921
  }
@@ -5446,6 +5446,104 @@
5446
5446
  "description": "For a client caller the grant check runs **before** any existence lookup, with no extra query on either path to time: a denied slug and a nonexistent one must be one answer. That is why an ungranted slug is `403 forbidden` while a granted-but-unconfigured one is `404 unknown_datapoint` and a configured one with no sample yet is `404 no_data` — three facts a caller who is entitled to them needs told apart. The plane built-ins (`bridge_state`, `robot_details`) answer here too, without appearing in any document."
5447
5447
  }
5448
5448
  },
5449
+ "/api/client/robots": {
5450
+ "get": {
5451
+ "operationId": "get_api_client_robots",
5452
+ "summary": "Lists the robots the caller reaches, with bridge state and the published configuration version.",
5453
+ "tags": [
5454
+ "robots"
5455
+ ],
5456
+ "security": [
5457
+ {
5458
+ "developerSession": []
5459
+ },
5460
+ {
5461
+ "clientToken": []
5462
+ },
5463
+ {
5464
+ "serverKey": []
5465
+ }
5466
+ ],
5467
+ "parameters": [],
5468
+ "responses": {
5469
+ "200": {
5470
+ "description": "Success.",
5471
+ "content": {
5472
+ "application/json": {
5473
+ "schema": {
5474
+ "$ref": "#/components/schemas/client-robot-list-response"
5475
+ }
5476
+ }
5477
+ }
5478
+ },
5479
+ "default": {
5480
+ "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`.",
5481
+ "content": {
5482
+ "application/json": {
5483
+ "schema": {
5484
+ "$ref": "#/components/schemas/api-error"
5485
+ }
5486
+ }
5487
+ }
5488
+ }
5489
+ },
5490
+ "description": "**The REST twin of the MCP tool `robots_list`**, and the one robot question no robot-scoped route can answer: which robots may I name at all. An app user sees the robots their app attaches on which their role grants at least one slug or capability; a server key sees every robot its app attaches; a developer bearer sees the organisation's robots. Name order, id as the tiebreak. A robot on which the role grants nothing is absent rather than listed empty — the same answer `robots_list` gives, for the same reason: reach is a grant, not an attachment. Under `/api/client/` because it names no robot; every robot-scoped read stays under `/api/robots/:id/…`."
5491
+ }
5492
+ },
5493
+ "/api/robots/{id}/datasheet": {
5494
+ "get": {
5495
+ "operationId": "get_api_robots_id_datasheet",
5496
+ "summary": "Describes everything the caller's role lets them do on one robot, with parameter schemas.",
5497
+ "tags": [
5498
+ "robots"
5499
+ ],
5500
+ "security": [
5501
+ {
5502
+ "developerSession": []
5503
+ },
5504
+ {
5505
+ "clientToken": []
5506
+ },
5507
+ {
5508
+ "serverKey": []
5509
+ }
5510
+ ],
5511
+ "parameters": [
5512
+ {
5513
+ "name": "id",
5514
+ "in": "path",
5515
+ "required": true,
5516
+ "description": "The robot's uuid, as `GET /api/client/robots` lists it.",
5517
+ "schema": {
5518
+ "type": "string"
5519
+ }
5520
+ }
5521
+ ],
5522
+ "responses": {
5523
+ "200": {
5524
+ "description": "Success.",
5525
+ "content": {
5526
+ "application/json": {
5527
+ "schema": {
5528
+ "$ref": "#/components/schemas/mcp-robot-datasheet"
5529
+ }
5530
+ }
5531
+ }
5532
+ },
5533
+ "default": {
5534
+ "description": "An error envelope. Codes this route is known to answer: `unauthorized`, `token_expired`, `token_revoked`, `forbidden`, `invalid_uuid`, `not_found`.",
5535
+ "content": {
5536
+ "application/json": {
5537
+ "schema": {
5538
+ "$ref": "#/components/schemas/api-error"
5539
+ }
5540
+ }
5541
+ }
5542
+ }
5543
+ },
5544
+ "description": "**The REST twin of the MCP tool `robot_describe`**: one answer per robot — every datapoint, action, service, publisher and camera the role grants, each with its `input_schema` where it takes parameters, plus the two capabilities that gate whole features, `action_history` and `assets`. A robot with nothing published answers an empty `exposures` list, never a refusal. A robot the caller does not reach — not attached to their app, or attached with a role that grants nothing on it — answers `404` exactly as one that does not exist. The app-user datapoint and camera listings under this prefix stay; this is the one read that also names actions, services, publishers and capabilities, which is what an app needs before it can draw a screen."
5545
+ }
5546
+ },
5449
5547
  "/api/robots/{id}/config/draft": {
5450
5548
  "get": {
5451
5549
  "operationId": "get_api_robots_id_config_draft",
@@ -6441,7 +6539,7 @@
6441
6539
  "name": "slug",
6442
6540
  "in": "path",
6443
6541
  "required": true,
6444
- "description": "The action or service slug from the published configuration; the cloud already knows which kind it is.",
6542
+ "description": "The action or service slug from the published configuration — the cloud already knows which kind.",
6445
6543
  "schema": {
6446
6544
  "type": "string"
6447
6545
  }
@@ -8271,7 +8369,7 @@
8271
8369
  "minLength": 2,
8272
8370
  "maxLength": 63,
8273
8371
  "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
8274
- "description": "The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context to disambiguate with, so a collision is refused with `identifier_taken`."
8372
+ "description": "The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context, so a collision is refused with `identifier_taken`."
8275
8373
  },
8276
8374
  "robot_ids": {
8277
8375
  "type": "array",
@@ -8293,7 +8391,7 @@
8293
8391
  "type": "null"
8294
8392
  }
8295
8393
  ],
8296
- "description": "The role an app user gets when they are created or invited without an explicit one. `null` means this app has not chosen a default, the normal state of an app created before its roles were configured — and then a create or invite that omits `role_id` is a `validation_error` rather than a user with no role. An invitation resolves the role when it is issued, so changing this never re-aims an outstanding one. The role must belong to this app, which `PATCH /api/apps/:id` checks and the schema cannot."
8394
+ "description": "The role an app user gets when created or invited without an explicit one. `null` means this app has not chosen a default, the normal state of an app created before its roles were configured — and then a create or invite that omits `role_id` gets `validation_error`, not a user with no role. An invitation resolves the role when issued, so changing this never re-aims an outstanding one. The role must belong to this app, which `PATCH /api/apps/:id` checks and the schema cannot."
8297
8395
  },
8298
8396
  "created_at": {
8299
8397
  "type": "string",
@@ -8573,7 +8671,7 @@
8573
8671
  "minLength": 2,
8574
8672
  "maxLength": 63,
8575
8673
  "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
8576
- "description": "The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context to disambiguate with, so a collision is refused with `identifier_taken`."
8674
+ "description": "The stable handle a client sends at login, lowercase and underscore-separated. **Globally unique, not per organisation** — `clientLoginRequest` carries no org context, so a collision is refused with `identifier_taken`."
8577
8675
  },
8578
8676
  "robot_ids": {
8579
8677
  "type": "array",
@@ -8595,7 +8693,7 @@
8595
8693
  "type": "null"
8596
8694
  }
8597
8695
  ],
8598
- "description": "The role an app user gets when they are created or invited without an explicit one. `null` means this app has not chosen a default, the normal state of an app created before its roles were configured — and then a create or invite that omits `role_id` is a `validation_error` rather than a user with no role. An invitation resolves the role when it is issued, so changing this never re-aims an outstanding one. The role must belong to this app, which `PATCH /api/apps/:id` checks and the schema cannot."
8696
+ "description": "The role an app user gets when created or invited without an explicit one. `null` means this app has not chosen a default, the normal state of an app created before its roles were configured — and then a create or invite that omits `role_id` gets `validation_error`, not a user with no role. An invitation resolves the role when issued, so changing this never re-aims an outstanding one. The role must belong to this app, which `PATCH /api/apps/:id` checks and the schema cannot."
8599
8697
  },
8600
8698
  "created_at": {
8601
8699
  "type": "string",
@@ -8799,7 +8897,7 @@
8799
8897
  },
8800
8898
  "enabled": {
8801
8899
  "type": "boolean",
8802
- "description": "Whether this provider is offered at all. A disabled provider disappears from `GET /api/client/providers` and refuses a start with `provider_disabled`, without the row and its linked identities being deleted."
8900
+ "description": "Whether this provider is offered. A disabled provider disappears from `GET /api/client/providers` and refuses a start with `provider_disabled`, without the row and its linked identities being deleted."
8803
8901
  },
8804
8902
  "created_at": {
8805
8903
  "type": "string",
@@ -8883,7 +8981,7 @@
8883
8981
  },
8884
8982
  "enabled": {
8885
8983
  "type": "boolean",
8886
- "description": "Whether this provider is offered at all. A disabled provider disappears from `GET /api/client/providers` and refuses a start with `provider_disabled`, without the row and its linked identities being deleted."
8984
+ "description": "Whether this provider is offered. A disabled provider disappears from `GET /api/client/providers` and refuses a start with `provider_disabled`, without the row and its linked identities being deleted."
8887
8985
  },
8888
8986
  "created_at": {
8889
8987
  "type": "string",
@@ -8965,7 +9063,7 @@
8965
9063
  },
8966
9064
  "has_password": {
8967
9065
  "type": "boolean",
8968
- "description": "Whether this account has a Fleetless-held password at all. `false` is an identity-provider-only account, or an invitation not yet accepted — it does not mean blocked and it does not mean without access. No hash, no algorithm and no \"last changed\" travels here, and nothing on the wire can say whether a password is strong or already known to somebody else."
9066
+ "description": "Whether this account has a Fleetless-held password. `false` is an identity-provider-only account, or an invitation not yet accepted — it does not mean blocked and it does not mean without access. No hash, no algorithm and no \"last changed\" travels here, and nothing on the wire can say whether a password is strong or already known to somebody else."
8969
9067
  },
8970
9068
  "providers": {
8971
9069
  "maxItems": 20,
@@ -8975,7 +9073,7 @@
8975
9073
  "maxLength": 40,
8976
9074
  "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
8977
9075
  },
8978
- "description": "The slugs of the identity providers this account is linked to, empty for a password-only user. It is what lets a developer's user list say where an account came from without a second request."
9076
+ "description": "The slugs of the identity providers this account is linked to, empty for a password-only user. Lets a developer's user list say where an account came from without a second request."
8979
9077
  },
8980
9078
  "last_login_at": {
8981
9079
  "anyOf": [
@@ -9067,7 +9165,7 @@
9067
9165
  },
9068
9166
  "has_password": {
9069
9167
  "type": "boolean",
9070
- "description": "Whether this account has a Fleetless-held password at all. `false` is an identity-provider-only account, or an invitation not yet accepted — it does not mean blocked and it does not mean without access. No hash, no algorithm and no \"last changed\" travels here, and nothing on the wire can say whether a password is strong or already known to somebody else."
9168
+ "description": "Whether this account has a Fleetless-held password. `false` is an identity-provider-only account, or an invitation not yet accepted — it does not mean blocked and it does not mean without access. No hash, no algorithm and no \"last changed\" travels here, and nothing on the wire can say whether a password is strong or already known to somebody else."
9071
9169
  },
9072
9170
  "providers": {
9073
9171
  "maxItems": 20,
@@ -9077,7 +9175,7 @@
9077
9175
  "maxLength": 40,
9078
9176
  "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
9079
9177
  },
9080
- "description": "The slugs of the identity providers this account is linked to, empty for a password-only user. It is what lets a developer's user list say where an account came from without a second request."
9178
+ "description": "The slugs of the identity providers this account is linked to, empty for a password-only user. Lets a developer's user list say where an account came from without a second request."
9081
9179
  },
9082
9180
  "last_login_at": {
9083
9181
  "anyOf": [
@@ -9149,13 +9247,13 @@
9149
9247
  "texture",
9150
9248
  "other"
9151
9249
  ],
9152
- "description": "What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with, or `other`. A renderer decides from this alone, before fetching anything, what it has to pre-fetch."
9250
+ "description": "What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with, or `other`. A renderer decides from this alone, before fetching anything, what to pre-fetch."
9153
9251
  },
9154
9252
  "name": {
9155
9253
  "type": "string",
9156
9254
  "minLength": 1,
9157
9255
  "maxLength": 500,
9158
- "description": "What the robot called it — for a mesh, the `package://` URI the URDF references, verbatim, which is the only string a developer can match against their own workspace. A file the URDF never names (an image a `.dae` loads for itself) is named by joining the mesh's own directory with that internal reference."
9256
+ "description": "What the robot called it — for a mesh, the `package://` URI the URDF references, verbatim, the only string a developer can match against their own workspace. A file the URDF never names (an image a `.dae` loads for itself) is named by joining the mesh's own directory with that internal reference."
9159
9257
  },
9160
9258
  "media_type": {
9161
9259
  "type": "string",
@@ -9172,7 +9270,7 @@
9172
9270
  "sha256": {
9173
9271
  "type": "string",
9174
9272
  "pattern": "^[a-f0-9]{64}$",
9175
- "description": "The content hash, lowercase hex, and the reason two robots sharing a mesh cost one copy. It is exposed because it is the only way a client can tell \"this is the same mesh I already have\" across robots."
9273
+ "description": "The content hash, lowercase hex. Exposed because it is the only way a client can tell \"this is the same mesh I already have\" across robots — the reason two robots sharing a mesh cost one copy."
9176
9274
  },
9177
9275
  "created_at": {
9178
9276
  "type": "string",
@@ -9271,7 +9369,7 @@
9271
9369
  "type": "integer",
9272
9370
  "exclusiveMinimum": 0,
9273
9371
  "maximum": 9007199254740991,
9274
- "description": "How large the refused file actually is, in bytes. With `limit_bytes` beside it a developer can tell whether to shrink the mesh or raise the limit; \"too large\" alone answers neither."
9372
+ "description": "How large the refused file is, in bytes. With `limit_bytes` beside it a developer can tell whether to shrink the mesh or raise the limit; \"too large\" alone answers neither."
9275
9373
  }
9276
9374
  },
9277
9375
  "required": [
@@ -9360,7 +9458,7 @@
9360
9458
  "type": "string",
9361
9459
  "minLength": 1,
9362
9460
  "maxLength": 500,
9363
- "description": "The reference, verbatim, that no stored asset answers — a `package://` URI the workspace does not hold, or an absolute or bare relative path nothing will ever fetch. A developer whose URDF names one of the latter is entitled to be told so."
9461
+ "description": "The reference, verbatim, that no stored asset answers — a `package://` URI the workspace does not hold, or an absolute or bare relative path nothing will ever fetch."
9364
9462
  },
9365
9463
  "element": {
9366
9464
  "type": "string",
@@ -9377,7 +9475,7 @@
9377
9475
  ],
9378
9476
  "additionalProperties": false
9379
9477
  },
9380
- "description": "The references nothing in the store answers, each with the element that asked for it. A bare count is a dead end that sends a developer hunting through a workspace by hand; the references are what they can act on, so the references travel."
9478
+ "description": "The references nothing in the store answers, each with the element that asked for it. A bare count would send a developer hunting through the workspace by hand; the references are what they can act on."
9381
9479
  }
9382
9480
  },
9383
9481
  "required": [
@@ -9397,7 +9495,7 @@
9397
9495
  "type": "null"
9398
9496
  }
9399
9497
  ],
9400
- "description": "What the connected bridge says it *could* transfer, which is deliberately separate from what has been transferred. `null` when no bridge is connected — distinct from `false`, because \"no robot is online to ask\" and \"the robot has no URDF\" send a developer to two different places. After a publisher is killed rather than shut down this can read `true` for some seconds, on the underlying DDS liveliness timeout rather than on any check made here."
9498
+ "description": "What the connected bridge says it *could* transfer — deliberately separate from what has been transferred. `null` when no bridge is connected, distinct from `false`: \"no robot is online to ask\" and \"the robot has no URDF\" send a developer to different places. After a publisher is killed rather than shut down this can read `true` for some seconds, on the underlying DDS liveliness timeout rather than on any check made here."
9401
9499
  }
9402
9500
  },
9403
9501
  "required": [
@@ -9416,7 +9514,7 @@
9416
9514
  "enum": [
9417
9515
  "bridge"
9418
9516
  ],
9419
- "description": "Where the bytes come from. `bridge` is the only value today: the connected bridge reads them from the robot's own workspace. It is validated rather than ignored, so a caller naming a source that does not exist yet learns that instead of silently getting a bridge sync."
9517
+ "description": "Where the bytes come from. `bridge` is the only value today: the connected bridge reads them from the robot's own workspace. Validated rather than ignored, so a caller naming an unknown source is told so instead of silently getting a bridge sync."
9420
9518
  }
9421
9519
  },
9422
9520
  "required": [
@@ -9513,7 +9611,7 @@
9513
9611
  "type": "integer",
9514
9612
  "exclusiveMinimum": 0,
9515
9613
  "maximum": 9007199254740991,
9516
- "description": "How large the refused file actually is, in bytes. With `limit_bytes` beside it a developer can tell whether to shrink the mesh or raise the limit; \"too large\" alone answers neither."
9614
+ "description": "How large the refused file is, in bytes. With `limit_bytes` beside it a developer can tell whether to shrink the mesh or raise the limit; \"too large\" alone answers neither."
9517
9615
  }
9518
9616
  },
9519
9617
  "required": [
@@ -9762,7 +9860,7 @@
9762
9860
  "type": "string",
9763
9861
  "format": "uuid",
9764
9862
  "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)$",
9765
- "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 rather than being a filter it applies."
9863
+ "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."
9766
9864
  },
9767
9865
  "email": {
9768
9866
  "type": "string",
@@ -9789,7 +9887,7 @@
9789
9887
  "owner",
9790
9888
  "developer"
9791
9889
  ],
9792
- "description": "The console powers this person holds. **Required** — every Fleetless user is a member of the team and has a tier; the optional version of this field existed only while the org also held people with no console powers to grade, and that pool is gone."
9890
+ "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."
9793
9891
  },
9794
9892
  "created_at": {
9795
9893
  "type": "string",
@@ -9826,7 +9924,7 @@
9826
9924
  "authorization_endpoint": {
9827
9925
  "type": "string",
9828
9926
  "format": "uri",
9829
- "description": "The URL a client sends the user to in order to authorize."
9927
+ "description": "Where a client sends the user to authorize."
9830
9928
  },
9831
9929
  "token_endpoint": {
9832
9930
  "type": "string",
@@ -10014,7 +10112,7 @@
10014
10112
  "app_user",
10015
10113
  "server_key"
10016
10114
  ],
10017
- "description": "Which of the three kinds of caller this is: a `developer` working through the console, an `app_user` holding a token from a client login, or a `server_key` used by server-side code. Stated outright rather than left to be inferred from which id happens to be set."
10115
+ "description": "Which of the three kinds of caller this is: a `developer` working through the console, an `app_user` holding a token from a client login, or a `server_key` used by server-side code. Stated outright, not inferred from which id is set."
10018
10116
  },
10019
10117
  "developer_id": {
10020
10118
  "anyOf": [
@@ -10114,7 +10212,7 @@
10114
10212
  "minLength": 2,
10115
10213
  "maxLength": 63,
10116
10214
  "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
10117
- "description": "The app being logged in to, as its globally unique identifier — the lowercase, underscore-separated string the developer chose when the app was created. There is no organisation context at login, so this is what decides which app the credentials are checked for."
10215
+ "description": "The app being logged in to: its globally unique, lowercase, underscore-separated identifier, chosen by the developer at creation. There is no organisation context at login, so this is what decides which app the credentials are checked for."
10118
10216
  },
10119
10217
  "email": {
10120
10218
  "type": "string",
@@ -10140,7 +10238,7 @@
10140
10238
  "refresh_token": {
10141
10239
  "type": "string",
10142
10240
  "minLength": 1,
10143
- "description": "Any refresh token of the session to end. The whole token family is revoked server-side, so a token stolen before this call stops working too — clearing a client-side store is a gesture, not a revocation. The answer is `204`: a token the server does not recognise gets it too, since the end state a caller asked for is the end state they get."
10241
+ "description": "Any refresh token of the session to end. The whole token family is revoked server-side, so a token stolen before this call stops working too — clearing a client-side store is a gesture, not a revocation. The answer is `204`: a token the server does not recognise gets it too, since that is the end state being asked for."
10144
10242
  }
10145
10243
  },
10146
10244
  "required": [
@@ -10174,7 +10272,7 @@
10174
10272
  "client_name_verified": {
10175
10273
  "type": "boolean",
10176
10274
  "const": false,
10177
- "description": "Always `false`. The client registered itself without authentication and chose this name about itself, so it must be rendered as a claim and never as an identity. There is no verified case, which is why this is a literal and not a boolean: a `true` branch would be dead code that looked like a safeguard."
10275
+ "description": "Always `false`. The client registered itself without authentication and named itself, so it must be rendered as a claim and never as an identity. There is no verified case, which is why this is a literal and not a boolean: a `true` branch would be dead code that looked like a safeguard."
10178
10276
  },
10179
10277
  "scopes": {
10180
10278
  "type": "array",
@@ -10395,6 +10493,88 @@
10395
10493
  ],
10396
10494
  "additionalProperties": false
10397
10495
  },
10496
+ "client-robot-list-response": {
10497
+ "type": "object",
10498
+ "properties": {
10499
+ "robots": {
10500
+ "type": "array",
10501
+ "items": {
10502
+ "type": "object",
10503
+ "properties": {
10504
+ "id": {
10505
+ "type": "string",
10506
+ "format": "uuid",
10507
+ "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)$",
10508
+ "description": "The robot, and what every robot-scoped route takes as its `:id`."
10509
+ },
10510
+ "name": {
10511
+ "type": "string",
10512
+ "minLength": 1,
10513
+ "maxLength": 63,
10514
+ "description": "The robot's display name, at most 63 characters. Free text, changed through `PATCH /api/robots/:id`."
10515
+ },
10516
+ "created_at": {
10517
+ "type": "string",
10518
+ "format": "date-time",
10519
+ "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))$",
10520
+ "description": "When the robot was created, as an ISO 8601 timestamp."
10521
+ },
10522
+ "bridge_state": {
10523
+ "type": "object",
10524
+ "properties": {
10525
+ "online": {
10526
+ "type": "boolean"
10527
+ },
10528
+ "latency_ms": {
10529
+ "anyOf": [
10530
+ {
10531
+ "type": "number",
10532
+ "minimum": 0
10533
+ },
10534
+ {
10535
+ "type": "null"
10536
+ }
10537
+ ]
10538
+ }
10539
+ },
10540
+ "required": [
10541
+ "online",
10542
+ "latency_ms"
10543
+ ],
10544
+ "additionalProperties": false,
10545
+ "description": "The built-in `bridge_state` datapoint as the cloud observes it right now: whether the bridge is connected, and its latency when it is."
10546
+ },
10547
+ "published_version": {
10548
+ "anyOf": [
10549
+ {
10550
+ "type": "integer",
10551
+ "exclusiveMinimum": 0,
10552
+ "maximum": 9007199254740991
10553
+ },
10554
+ {
10555
+ "type": "null"
10556
+ }
10557
+ ],
10558
+ "description": "The published configuration version, or `null` when nothing has been published yet. A robot with nothing published is still listed — \"not configured yet\" is a real state, and the caller is entitled to it — and its datasheet answers an empty exposure list."
10559
+ }
10560
+ },
10561
+ "required": [
10562
+ "id",
10563
+ "name",
10564
+ "created_at",
10565
+ "bridge_state",
10566
+ "published_version"
10567
+ ],
10568
+ "additionalProperties": false
10569
+ },
10570
+ "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."
10571
+ }
10572
+ },
10573
+ "required": [
10574
+ "robots"
10575
+ ],
10576
+ "additionalProperties": false
10577
+ },
10398
10578
  "client-verify-email-request": {
10399
10579
  "type": "object",
10400
10580
  "properties": {
@@ -10486,7 +10666,7 @@
10486
10666
  ]
10487
10667
  },
10488
10668
  "description": {
10489
- "description": "Prose about what this value is, for whoever meets it in the console later. It changes nothing the robot does, so a publish that touches only it pushes no configuration at all — but it is carried verbatim into `robot_describe`, where a model that has never seen this robot reads it. The datapoint is offered whenever the role grants it; without one it is offered with `description: null` and the model has less to go on, as for actions, services, publishers and cameras. Omission is the only way to say nothing; an empty string is refused, here and on all five.",
10669
+ "description": "Prose about what this value is, for whoever meets it in the console. It changes nothing the robot does, so a publish that touches only it pushes no configuration at all — but it is carried verbatim into `robot_describe`, where a model that has never seen this robot reads it. The datapoint is offered whenever the role grants it; without one it is offered with `description: null` and the model has less to go on, as for actions, services, publishers and cameras. Omission is the only way to say nothing; an empty string is refused, here and on all five.",
10490
10670
  "examples": [
10491
10671
  "What this value is, for whoever meets it in the console."
10492
10672
  ],
@@ -11583,7 +11763,7 @@
11583
11763
  ]
11584
11764
  },
11585
11765
  "description": {
11586
- "description": "Prose about what this value is, for whoever meets it in the console later. It changes nothing the robot does, so a publish that touches only it pushes no configuration at all — but it is carried verbatim into `robot_describe`, where a model that has never seen this robot reads it. The datapoint is offered whenever the role grants it; without one it is offered with `description: null` and the model has less to go on, as for actions, services, publishers and cameras. Omission is the only way to say nothing; an empty string is refused, here and on all five.",
11766
+ "description": "Prose about what this value is, for whoever meets it in the console. It changes nothing the robot does, so a publish that touches only it pushes no configuration at all — but it is carried verbatim into `robot_describe`, where a model that has never seen this robot reads it. The datapoint is offered whenever the role grants it; without one it is offered with `description: null` and the model has less to go on, as for actions, services, publishers and cameras. Omission is the only way to say nothing; an empty string is refused, here and on all five.",
11587
11767
  "examples": [
11588
11768
  "What this value is, for whoever meets it in the console."
11589
11769
  ],
@@ -12809,7 +12989,7 @@
12809
12989
  "type": "string",
12810
12990
  "format": "uuid",
12811
12991
  "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)$",
12812
- "description": "The key row, and what the rotate and delete routes address. It is not the key: the secret itself is never carried by this shape."
12992
+ "description": "The key row, and what the rotate and delete routes address. It is not the key: this shape never carries the secret."
12813
12993
  },
12814
12994
  "app_id": {
12815
12995
  "type": "string",
@@ -12976,13 +13156,13 @@
12976
13156
  "description": "The datapoint this value belongs to."
12977
13157
  },
12978
13158
  "value": {
12979
- "description": "The value itself, 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."
13159
+ "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."
12980
13160
  },
12981
13161
  "timestamp_ms": {
12982
13162
  "type": "integer",
12983
13163
  "minimum": 0,
12984
13164
  "maximum": 9007199254740991,
12985
- "description": "When the value was captured, as a unix timestamp in milliseconds. This is 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."
13165
+ "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."
12986
13166
  }
12987
13167
  },
12988
13168
  "required": [
@@ -13004,10 +13184,10 @@
13004
13184
  "minLength": 1,
13005
13185
  "maxLength": 2000
13006
13186
  },
13007
- "description": "Where the authorization code may be returned, and the one field a registration cannot omit. Each must be an `https` URL, or `http` on an explicit loopback address for a native app that cannot hold a certificate, and none may carry a fragment. There must be between `1` and `5` of them; duplicates are collapsed rather than counted twice. Matched **exactly** at the authorize step against what was registered here."
13187
+ "description": "Where the authorization code may be returned, and the one field a registration cannot omit. Each must be an `https` URL, or `http` on an explicit loopback address for a native app that cannot hold a certificate, and none may carry a fragment. Between `1` and `5` of them; duplicates are collapsed rather than counted twice. Matched **exactly** at the authorize step against what was registered here."
13008
13188
  },
13009
13189
  "client_name": {
13010
- "description": "The name the client calls itself. Optional — a registration without one is recorded under a default name, per RFC 7591's making every metadata field optional. It is **not** vouched for by Fleetless and must never be rendered as if it were: a self-registered client chooses this string, and one has called itself *\"Fleetless Official Helper\"*.",
13190
+ "description": "The name the client calls itself. Optional: RFC 7591 makes every metadata field optional, so a registration without one is recorded under a default name. It is **not** vouched for by Fleetless and must never be rendered as if it were: a self-registered client chooses this string, and one has called itself *\"Fleetless Official Helper\"*.",
13011
13191
  "type": "string",
13012
13192
  "minLength": 1,
13013
13193
  "maxLength": 200
@@ -13351,7 +13531,7 @@
13351
13531
  "type": "string",
13352
13532
  "format": "uuid",
13353
13533
  "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)$",
13354
- "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 rather than being a filter it applies."
13534
+ "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."
13355
13535
  },
13356
13536
  "email": {
13357
13537
  "type": "string",
@@ -13378,7 +13558,7 @@
13378
13558
  "owner",
13379
13559
  "developer"
13380
13560
  ],
13381
- "description": "The console powers this person holds. **Required** — every Fleetless user is a member of the team and has a tier; the optional version of this field existed only while the org also held people with no console powers to grade, and that pool is gone."
13561
+ "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."
13382
13562
  },
13383
13563
  "created_at": {
13384
13564
  "type": "string",
@@ -13415,7 +13595,7 @@
13415
13595
  "type": "string",
13416
13596
  "format": "uuid",
13417
13597
  "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)$",
13418
- "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 rather than being a filter it applies."
13598
+ "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."
13419
13599
  },
13420
13600
  "email": {
13421
13601
  "type": "string",
@@ -13442,7 +13622,7 @@
13442
13622
  "owner",
13443
13623
  "developer"
13444
13624
  ],
13445
- "description": "The console powers this person holds. **Required** — every Fleetless user is a member of the team and has a tier; the optional version of this field existed only while the org also held people with no console powers to grade, and that pool is gone."
13625
+ "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."
13446
13626
  },
13447
13627
  "created_at": {
13448
13628
  "type": "string",
@@ -13747,7 +13927,7 @@
13747
13927
  "type": "string",
13748
13928
  "format": "uuid",
13749
13929
  "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)$",
13750
- "description": "The job's id, minted by the cloud when the invocation is accepted. Informative: state is observed by slug, and this id is what a cancel names when a caller wants to stop one specific job rather than whatever is running."
13930
+ "description": "The job's id, minted by the cloud when the invocation is accepted. Informative — state is observed by slug; a cancel names this id to stop one specific job rather than whatever is running."
13751
13931
  },
13752
13932
  "robot_id": {
13753
13933
  "type": "string",
@@ -13771,13 +13951,13 @@
13771
13951
  "cancelled",
13772
13952
  "lost"
13773
13953
  ],
13774
- "description": "Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone — and is said out loud rather than left reading `running` because nobody contradicted it."
13954
+ "description": "Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone — stated rather than left reading `running` by default."
13775
13955
  },
13776
13956
  "started_at": {
13777
13957
  "type": "string",
13778
13958
  "format": "date-time",
13779
13959
  "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))$",
13780
- "description": "When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge it is **adoption time**, not the real start, because the cloud never minted it and has no honest alternative."
13960
+ "description": "When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge, this is **adoption time**, not the real start — the cloud never minted it."
13781
13961
  },
13782
13962
  "updated_at": {
13783
13963
  "type": "string",
@@ -13865,7 +14045,7 @@
13865
14045
  "type": "object",
13866
14046
  "properties": {
13867
14047
  "result": {
13868
- "description": "What the service returned, shaped by the ROS service itself. A service call is awaited to completion, so there is no job to observe afterwards and no id to hold on to."
14048
+ "description": "What the service returned, shaped by the ROS service. A service call is awaited to completion, so there is no job to observe afterwards and no id to hold on to."
13869
14049
  }
13870
14050
  },
13871
14051
  "required": [
@@ -13909,7 +14089,7 @@
13909
14089
  "type": "string",
13910
14090
  "format": "uuid",
13911
14091
  "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)$",
13912
- "description": "The job's id, minted by the cloud when the invocation is accepted. Informative: state is observed by slug, and this id is what a cancel names when a caller wants to stop one specific job rather than whatever is running."
14092
+ "description": "The job's id, minted by the cloud when the invocation is accepted. Informative — state is observed by slug; a cancel names this id to stop one specific job rather than whatever is running."
13913
14093
  },
13914
14094
  "robot_id": {
13915
14095
  "type": "string",
@@ -13933,13 +14113,13 @@
13933
14113
  "cancelled",
13934
14114
  "lost"
13935
14115
  ],
13936
- "description": "Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone — and is said out loud rather than left reading `running` because nobody contradicted it."
14116
+ "description": "Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone — stated rather than left reading `running` by default."
13937
14117
  },
13938
14118
  "started_at": {
13939
14119
  "type": "string",
13940
14120
  "format": "date-time",
13941
14121
  "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))$",
13942
- "description": "When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge it is **adoption time**, not the real start, because the cloud never minted it and has no honest alternative."
14122
+ "description": "When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge, this is **adoption time**, not the real start — the cloud never minted it."
13943
14123
  },
13944
14124
  "updated_at": {
13945
14125
  "type": "string",
@@ -14031,7 +14211,7 @@
14031
14211
  "type": "string",
14032
14212
  "format": "uuid",
14033
14213
  "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)$",
14034
- "description": "The run's id, which is the same id the invocation was answered with — so a caller that kept a job id can find its durable record here later."
14214
+ "description": "The run's id — the same id the invocation was answered with, so a caller that kept a job id can find its durable record here later."
14035
14215
  },
14036
14216
  "robot_id": {
14037
14217
  "type": "string",
@@ -14148,7 +14328,7 @@
14148
14328
  "app_user",
14149
14329
  "server_key"
14150
14330
  ],
14151
- "description": "What the caller was acting as: a `developer` in the console, an `app_user` of one app, or a `server_key` used by server-side code. A bridge invokes nothing, so it is deliberately not a case here. `end_user` appears only on runs recorded before app users replaced the organisation-wide user pool — it is kept so a history page can still render them, and nothing writes it any more."
14331
+ "description": "What the caller was acting as: a `developer` in the console, an `app_user` of one app, or a `server_key` used by server-side code. A bridge invokes nothing, so it is deliberately not a case here. `end_user` appears only on runs recorded before app users replaced the organisation-wide user pool — kept so old runs still render; nothing writes it now."
14152
14332
  },
14153
14333
  "id": {
14154
14334
  "type": "string",
@@ -14416,12 +14596,12 @@
14416
14596
  "type": "null"
14417
14597
  }
14418
14598
  ],
14419
- "description": "What the client calls itself, or `null` when its registration is gone and there is no longer anything to have named. **Unverified** — see `client_name_verified`."
14599
+ "description": "What the client calls itself, or `null` once its registration is gone. **Unverified** — see `client_name_verified`."
14420
14600
  },
14421
14601
  "client_name_verified": {
14422
14602
  "type": "boolean",
14423
14603
  "const": false,
14424
- "description": "Always `false`. The client registered itself without authentication and chose this name about itself, so it must be rendered as a claim and never as an identity. There is no verified case, which is why this is a literal and not a boolean: a `true` branch would be dead code that looked like a safeguard."
14604
+ "description": "Always `false`. The client registered itself without authentication and named itself, so it must be rendered as a claim and never as an identity. There is no verified case, which is why this is a literal and not a boolean: a `true` branch would be dead code that looked like a safeguard."
14425
14605
  },
14426
14606
  "granted_at": {
14427
14607
  "type": "string",
@@ -14446,6 +14626,120 @@
14446
14626
  ],
14447
14627
  "additionalProperties": false
14448
14628
  },
14629
+ "mcp-robot-datasheet": {
14630
+ "type": "object",
14631
+ "properties": {
14632
+ "robot_id": {
14633
+ "type": "string",
14634
+ "format": "uuid",
14635
+ "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)$"
14636
+ },
14637
+ "robot_name": {
14638
+ "type": "string",
14639
+ "minLength": 1,
14640
+ "maxLength": 200
14641
+ },
14642
+ "capabilities": {
14643
+ "type": "object",
14644
+ "properties": {
14645
+ "action_history": {
14646
+ "type": "boolean"
14647
+ },
14648
+ "assets": {
14649
+ "type": "boolean"
14650
+ }
14651
+ },
14652
+ "required": [
14653
+ "action_history",
14654
+ "assets"
14655
+ ],
14656
+ "additionalProperties": false
14657
+ },
14658
+ "exposures": {
14659
+ "maxItems": 2000,
14660
+ "type": "array",
14661
+ "items": {
14662
+ "type": "object",
14663
+ "properties": {
14664
+ "slug": {
14665
+ "type": "string",
14666
+ "minLength": 2,
14667
+ "maxLength": 63,
14668
+ "pattern": "^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$"
14669
+ },
14670
+ "kind": {
14671
+ "type": "string",
14672
+ "enum": [
14673
+ "datapoint",
14674
+ "service",
14675
+ "action",
14676
+ "publisher",
14677
+ "camera"
14678
+ ]
14679
+ },
14680
+ "description": {
14681
+ "anyOf": [
14682
+ {
14683
+ "type": "string",
14684
+ "maxLength": 2000
14685
+ },
14686
+ {
14687
+ "type": "null"
14688
+ }
14689
+ ]
14690
+ },
14691
+ "unit": {
14692
+ "anyOf": [
14693
+ {
14694
+ "type": "string",
14695
+ "maxLength": 32
14696
+ },
14697
+ {
14698
+ "type": "null"
14699
+ }
14700
+ ]
14701
+ },
14702
+ "decimals": {
14703
+ "anyOf": [
14704
+ {
14705
+ "type": "integer",
14706
+ "minimum": 0,
14707
+ "maximum": 6
14708
+ },
14709
+ {
14710
+ "type": "null"
14711
+ }
14712
+ ]
14713
+ },
14714
+ "input_schema": {
14715
+ "anyOf": [
14716
+ {},
14717
+ {
14718
+ "type": "null"
14719
+ }
14720
+ ]
14721
+ }
14722
+ },
14723
+ "required": [
14724
+ "slug",
14725
+ "kind",
14726
+ "description",
14727
+ "unit",
14728
+ "decimals",
14729
+ "input_schema"
14730
+ ],
14731
+ "additionalProperties": false
14732
+ }
14733
+ }
14734
+ },
14735
+ "required": [
14736
+ "robot_id",
14737
+ "robot_name",
14738
+ "capabilities",
14739
+ "exposures"
14740
+ ],
14741
+ "additionalProperties": false
14742
+ },
14449
14743
  "mcp-role-preview-response": {
14450
14744
  "type": "object",
14451
14745
  "properties": {
@@ -14611,7 +14905,7 @@
14611
14905
  "description": "The PKCE verifier whose `S256` hash was sent as the challenge at the authorize step. Between `43` and `128` unreserved characters, per RFC 7636 §4.1 — it is compared rather than parsed, so a length nobody checks is a length an attacker chooses. PKCE is mandatory for every client under OAuth 2.1."
14612
14906
  },
14613
14907
  "resource": {
14614
- "description": "The resource the token is being requested for, per RFC 8707. It must match the audience the code was authorized for, or the answer is `invalid_target`; omitted, the code's own audience stands. It becomes the token's `aud`, and a resource refuses a token whose audience names something else — which is what keeps a token minted for one app out of another app's endpoint.",
14908
+ "description": "The resource the token is requested for, per RFC 8707. It must match the audience the code was authorized for, or the answer is `invalid_target`; omitted, the code's own audience stands. It becomes the token's `aud`, and a resource refuses a token whose audience names something else — which is what keeps a token minted for one app out of another app's endpoint.",
14615
14909
  "type": "string",
14616
14910
  "format": "uri"
14617
14911
  }
@@ -15357,7 +15651,7 @@
15357
15651
  "created_at"
15358
15652
  ],
15359
15653
  "additionalProperties": false,
15360
- "description": "The organisation as it now stands, after the patch was applied. The whole resource comes back, not only the fields that changed."
15654
+ "description": "The organisation as it now stands, after the patch. The whole resource comes back, not only the changed fields."
15361
15655
  }
15362
15656
  },
15363
15657
  "required": [
@@ -15410,7 +15704,7 @@
15410
15704
  "created_at"
15411
15705
  ],
15412
15706
  "additionalProperties": false,
15413
- "description": "The robot as it now stands, after the patch was applied. The whole resource comes back, not only the fields that changed."
15707
+ "description": "The robot as it now stands, after the patch. The whole resource comes back, not only the changed fields."
15414
15708
  }
15415
15709
  },
15416
15710
  "required": [
@@ -16236,7 +16530,7 @@
16236
16530
  "type": "string",
16237
16531
  "format": "uuid",
16238
16532
  "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)$",
16239
- "description": "The job's id, minted by the cloud when the invocation is accepted. Informative: state is observed by slug, and this id is what a cancel names when a caller wants to stop one specific job rather than whatever is running."
16533
+ "description": "The job's id, minted by the cloud when the invocation is accepted. Informative — state is observed by slug; a cancel names this id to stop one specific job rather than whatever is running."
16240
16534
  },
16241
16535
  "robot_id": {
16242
16536
  "type": "string",
@@ -16260,13 +16554,13 @@
16260
16554
  "cancelled",
16261
16555
  "lost"
16262
16556
  ],
16263
- "description": "Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone — and is said out loud rather than left reading `running` because nobody contradicted it."
16557
+ "description": "Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `lost` is a real outcome — the bridge restarted mid-job and the result is gone — stated rather than left reading `running` by default."
16264
16558
  },
16265
16559
  "started_at": {
16266
16560
  "type": "string",
16267
16561
  "format": "date-time",
16268
16562
  "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))$",
16269
- "description": "When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge it is **adoption time**, not the real start, because the cloud never minted it and has no honest alternative."
16563
+ "description": "When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge, this is **adoption time**, not the real start — the cloud never minted it."
16270
16564
  },
16271
16565
  "updated_at": {
16272
16566
  "type": "string",
@@ -16470,7 +16764,7 @@
16470
16764
  },
16471
16765
  "builtin": {
16472
16766
  "type": "boolean",
16473
- "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, because no route renames or deletes any role."
16767
+ "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."
16474
16768
  }
16475
16769
  },
16476
16770
  "required": [
@@ -16509,7 +16803,7 @@
16509
16803
  },
16510
16804
  "builtin": {
16511
16805
  "type": "boolean",
16512
- "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, because no route renames or deletes any role."
16806
+ "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."
16513
16807
  }
16514
16808
  },
16515
16809
  "required": [
@@ -16600,7 +16894,7 @@
16600
16894
  "type": "string",
16601
16895
  "format": "uuid",
16602
16896
  "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)$",
16603
- "description": "The key row, and what the rotate and delete routes address. It is not the key: the secret itself is never carried by this shape."
16897
+ "description": "The key row, and what the rotate and delete routes address. It is not the key: this shape never carries the secret."
16604
16898
  },
16605
16899
  "app_id": {
16606
16900
  "type": "string",
@@ -16643,7 +16937,7 @@
16643
16937
  ],
16644
16938
  "additionalProperties": false
16645
16939
  },
16646
- "description": "The app's server keys as metadata, oldest first by `created_at`. The raw secret is not here and never will be: it exists once, in the answer to the request that created or rotated the key."
16940
+ "description": "The app's server keys as metadata, oldest first by `created_at`. The raw secret is not here and never will be: it exists once, in the response that created or rotated the key."
16647
16941
  }
16648
16942
  },
16649
16943
  "required": [
@@ -16748,7 +17042,7 @@
16748
17042
  "type": "string",
16749
17043
  "format": "uuid",
16750
17044
  "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)$",
16751
- "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 rather than being a filter it applies."
17045
+ "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."
16752
17046
  },
16753
17047
  "email": {
16754
17048
  "type": "string",
@@ -16775,7 +17069,7 @@
16775
17069
  "owner",
16776
17070
  "developer"
16777
17071
  ],
16778
- "description": "The console powers this person holds. **Required** — every Fleetless user is a member of the team and has a tier; the optional version of this field existed only while the org also held people with no console powers to grade, and that pool is gone."
17072
+ "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."
16779
17073
  },
16780
17074
  "created_at": {
16781
17075
  "type": "string",