From 329f15b4970a73265e360105afaaf473b79a5542 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sat, 5 Sep 2026 18:25:48 +1200 Subject: [PATCH 1/3] Generate the endpoint reference from the OpenAPI spec The hand-written resource pages covered 34 of the API's 77 operations, and one of them documented a create-device call that does not exist: the identifier goes in the path, not the body. The API Reference tab now points at the specification the server generates, so the endpoint reference is complete and cannot drift. The spec tags every operation, so it groups by resource rather than arriving as one flat list. Overview and Authentication stay hand-written, since base URLs, response envelopes and status codes are not in the spec. The four resource pages are removed and redirect to the overview. Co-Authored-By: Claude Opus 5 --- .atlas-analysis.json | 6 +- api/deployments.mdx | 328 --------------------------------- api/devices.mdx | 413 ------------------------------------------ api/firmware.mdx | 303 ------------------------------- api/organizations.mdx | 364 ------------------------------------- api/overview.mdx | 14 +- docs.json | 30 ++- 7 files changed, 36 insertions(+), 1422 deletions(-) delete mode 100644 api/deployments.mdx delete mode 100644 api/devices.mdx delete mode 100644 api/firmware.mdx delete mode 100644 api/organizations.mdx diff --git a/.atlas-analysis.json b/.atlas-analysis.json index 9b25f20..669bf31 100644 --- a/.atlas-analysis.json +++ b/.atlas-analysis.json @@ -73,11 +73,7 @@ "group": "HTTP API", "pages": [ "api/overview", - "api/authentication", - "api/devices", - "api/firmware", - "api/deployments", - "api/organizations" + "api/authentication" ] }, { diff --git a/api/deployments.mdx b/api/deployments.mdx deleted file mode 100644 index 932e8b8..0000000 --- a/api/deployments.mdx +++ /dev/null @@ -1,328 +0,0 @@ ---- -title: "Deployment Groups API" -sidebarTitle: "Deployments" -description: "HTTP API endpoints to create, list, update, and delete NervesHub deployment groups, including activation and targeting conditions." ---- - -The Deployments API lets you manage firmware rollouts programmatically — create a group that targets a subset of devices by tag and version condition, activate or deactivate it, and update which firmware version it distributes. Automating rollouts through the API is the standard pattern for CI/CD pipelines that ship firmware on every merge to main. - - - These endpoints manage what the web console calls **deployment groups**. The API paths remain `/deployments` for backwards compatibility. - - -## List Deployment Groups - -Retrieve all deployment groups configured for a product. - -``` -GET /api/orgs/{org_name}/products/{product_name}/deployments -``` - - - The slug of your organization. - - - - The slug of the product. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/deployments" -``` - -**Example response:** - -```json -{ - "data": [ - { - "name": "production-rollout", - "firmware_uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", - "state": "on", - "conditions": { - "tags": ["production"], - "tag_operator": "and", - "version": "< 1.4.2" - }, - "device_count": 1840, - "inserted_at": "2024-11-10T13:00:00Z" - }, - { - "name": "staging-canary", - "firmware_uuid": "c9d8e7f6-a5b4-3210-fedc-ba9876543210", - "state": "on", - "conditions": { - "tags": ["staging"], - "tag_operator": "and", - "version": "< 1.4.3" - }, - "device_count": 12, - "inserted_at": "2024-11-14T11:00:00Z" - } - ] -} -``` - - - The unique name of the group within the product. - - - - UUID of the firmware artifact this group's current release distributes. - - - - `on` means the group is actively pushing updates; `off` means it is inactive. - - - - Targeting conditions. `tags` is a list of device tags; `tag_operator` is `and` (require all, the default) or `or` (allow any); `version` is a semver constraint string (e.g. `< 1.4.2`, `>= 1.0.0 and < 1.4.0`). - - - - Number of devices currently matched by this group's conditions. - - -*** - -## Create a Deployment Group - -Define a new group that targets devices meeting specific tag and version conditions. - -``` -POST /api/orgs/{org_name}/products/{product_name}/deployments -``` - - - The slug of your organization. - - - - The slug of the product. - - - - A unique name for this group within the product, e.g. `production-v1-4-3`. - - - - The UUID of the firmware artifact to distribute. Its platform and architecture are recorded on the group and every later release must match them. - - - - An object with `tags` (array of strings), optional `tag_operator` (`and` or `or`, default `and`), and `version` (semver constraint string) that devices must match to receive this update. - - -**Example request:** - -```bash -curl -X POST \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "production-v1-4-3", - "firmware_uuid": "c9d8e7f6-a5b4-3210-fedc-ba9876543210", - "conditions": { - "tags": ["production"], - "version": "< 1.4.3" - } - }' \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/deployments" -``` - -**Example response (`201 Created`):** - -```json -{ - "data": { - "name": "production-v1-4-3", - "firmware_uuid": "c9d8e7f6-a5b4-3210-fedc-ba9876543210", - "state": "off", - "conditions": { - "tags": ["production"], - "tag_operator": "and", - "version": "< 1.4.3" - }, - "device_count": 1840, - "inserted_at": "2024-11-15T18:00:00Z" - } -} -``` - - - New groups are created in the `off` state. Devices will not receive updates until you activate the group by setting `state` to `on` with a PUT request. - - -*** - -## Get a Deployment Group - -Retrieve the current configuration and status of a single group. - -``` -GET /api/orgs/{org_name}/products/{product_name}/deployments/{name} -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The name of the group. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/deployments/production-v1-4-3" -``` - -*** - -## Update a Deployment Group - -Modify a group's firmware, targeting conditions, or activation state. Use this endpoint to activate a group after creation, ship a new firmware version, or halt a rollout. - -``` -PUT /api/orgs/{org_name}/products/{product_name}/deployments/{name} -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The name of the group to update. - - - - Set to `on` to activate the group or `off` to deactivate it. - - - - Ship a different firmware artifact. Its platform and architecture must match the group's. - - - - Update the `tags`, `tag_operator`, and/or `version` targeting conditions. - - -**Example — activate a group:** - -```bash -curl -X PUT \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"state": "on"}' \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/deployments/production-v1-4-3" -``` - -**Example — ship new firmware and update the version condition:** - -```bash -curl -X PUT \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "firmware_uuid": "d1e2f3a4-b5c6-7890-1234-567890abcdef", - "conditions": { - "tags": ["production"], - "version": "< 1.4.4" - } - }' \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/deployments/production-v1-4-3" -``` - -**Example response (`200 OK`):** - -```json -{ - "data": { - "name": "production-v1-4-3", - "firmware_uuid": "c9d8e7f6-a5b4-3210-fedc-ba9876543210", - "state": "on", - "conditions": { - "tags": ["production"], - "tag_operator": "and", - "version": "< 1.4.3" - }, - "device_count": 1840 - } -} -``` - -*** - -## Delete a Deployment Group - -Permanently remove a group. Devices that have already updated are unaffected; devices mid-update may experience a failed update cycle. - -``` -DELETE /api/orgs/{org_name}/products/{product_name}/deployments/{name} -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The name of the group to delete. - - -**Example request:** - -```bash -curl -X DELETE \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/deployments/production-v1-4-3" -``` - -Returns `204 No Content` on success. - - - Deactivate a group (set `state` to `off`) before deleting it to avoid interrupting in-progress firmware downloads. - - -*** - -## Typical CI/CD Workflow - -The standard automation pattern for shipping a firmware release end-to-end: - - - - Your CI pipeline runs `mix firmware` and signs the output with `fwup --sign`. - - - - POST the `.fw` file to the Firmware API. Capture the returned `uuid`. - - - - POST to create a new group referencing the firmware UUID, targeting your staging or canary tag first. - - - - PUT `{"state": "on"}` to begin distributing the update to matched devices. - - - - After verifying on staging, update the production group's `firmware_uuid` and ensure it is active. - - diff --git a/api/devices.mdx b/api/devices.mdx deleted file mode 100644 index 161078e..0000000 --- a/api/devices.mdx +++ /dev/null @@ -1,413 +0,0 @@ ---- -title: "Devices API" -sidebarTitle: "Devices" -description: "HTTP API endpoints to list, show, create, update, and delete NervesHub devices, plus certificate and identity management operations." ---- - -The Devices API lets you manage your IoT devices programmatically — list the fleet, inspect individual device state, provision new devices, update tags, and manage the X.509 certificates that authenticate each device over mTLS. - -## List Devices - -Retrieve all devices registered to a product, with optional filters. - -``` -GET /api/orgs/{org_name}/products/{product_name}/devices -``` - - - The slug of your organization. - - - - The slug of the product whose devices you want to list. - - - - Filter by device identifier (exact match). - - - - Filter devices that have this tag applied. - - - - Filter by the firmware version currently reported by the device. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/devices" -``` - -**Example response:** - -```json -{ - "data": [ - { - "identifier": "lock-00a1b2c3", - "tags": ["production", "us-east"], - "status": "online", - "firmware_metadata": { - "version": "1.4.2", - "platform": "rpi4", - "architecture": "arm" - }, - "last_communication": "2024-11-15T14:32:10Z" - }, - { - "identifier": "lock-00d4e5f6", - "tags": ["production", "us-west"], - "status": "offline", - "firmware_metadata": { - "version": "1.4.1", - "platform": "rpi4", - "architecture": "arm" - }, - "last_communication": "2024-11-14T09:17:45Z" - } - ] -} -``` - -*** - -## Get a Device - -Retrieve metadata and current state for a single device. - -``` -GET /api/orgs/{org_name}/products/{product_name}/devices/{identifier} -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The unique identifier of the device. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/devices/lock-00a1b2c3" -``` - -**Example response:** - -```json -{ - "data": { - "identifier": "lock-00a1b2c3", - "tags": ["production", "us-east"], - "status": "online", - "firmware_metadata": { - "version": "1.4.2", - "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", - "platform": "rpi4", - "architecture": "arm", - "author": "ci-builder" - }, - "last_communication": "2024-11-15T14:32:10Z", - "inserted_at": "2024-03-01T08:00:00Z" - } -} -``` - - - The unique string that identifies this device, typically set at manufacture time. - - - - List of string tags. Deployment groups use tags to target subsets of the fleet. - - - - Current connection state: `online` or `offline`. - - - - Metadata from the firmware the device last reported, including `version`, `uuid`, `platform`, `architecture`, and `author`. - - - - ISO 8601 timestamp of the device's most recent connection. - - -*** - -## Create a Device - -Register a new device in a product. - -``` -POST /api/orgs/{org_name}/products/{product_name}/devices -``` - - - The slug of your organization. - - - - The slug of the product. - - - - A unique identifier for the device. This value must match the identifier the device presents at connection time. - - - - Optional list of string tags to apply to the device at creation. - - -**Example request:** - -```bash -curl -X POST \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"identifier": "lock-00g7h8i9", "tags": ["staging"]}' \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/devices" -``` - -**Example response (`201 Created`):** - -```json -{ - "data": { - "identifier": "lock-00g7h8i9", - "tags": ["staging"], - "status": "offline", - "firmware_metadata": null, - "last_communication": null, - "inserted_at": "2024-11-15T15:00:00Z" - } -} -``` - -*** - -## Update a Device - -Update a device's mutable attributes, such as its tags. - -``` -PUT /api/orgs/{org_name}/products/{product_name}/devices/{identifier} -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The identifier of the device to update. - - - - Replaces the device's current tag list with this array. - - -**Example request:** - -```bash -curl -X PUT \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"tags": ["production", "us-east"]}' \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/devices/lock-00g7h8i9" -``` - -**Example response (`200 OK`):** - -```json -{ - "data": { - "identifier": "lock-00g7h8i9", - "tags": ["production", "us-east"], - "status": "offline" - } -} -``` - -*** - -## Delete a Device - -Permanently remove a device from a product. - -``` -DELETE /api/orgs/{org_name}/products/{product_name}/devices/{identifier} -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The identifier of the device to delete. - - -**Example request:** - -```bash -curl -X DELETE \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/devices/lock-00g7h8i9" -``` - -Returns `204 No Content` on success with no response body. - - - Deleting a device is irreversible. The device's certificates and history are removed. If the physical device reconnects, it will be rejected unless re-provisioned. - - -*** - -## List Device Certificates - -List the X.509 client certificates registered to a device. - -``` -GET /api/orgs/{org_name}/products/{product_name}/devices/{identifier}/certificates -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The device identifier. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/devices/lock-00a1b2c3/certificates" -``` - -**Example response:** - -```json -{ - "data": [ - { - "serial": "3c:a1:f2:...", - "not_before": "2024-01-01T00:00:00Z", - "not_after": "2027-01-01T00:00:00Z" - } - ] -} -``` - -*** - -## Upload a Device Certificate - -Register an additional X.509 certificate for a device. This allows the device to authenticate using the uploaded certificate. - -``` -POST /api/orgs/{org_name}/products/{product_name}/devices/{identifier}/certificates -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The device identifier. - - - - The PEM-encoded X.509 certificate string. - - -**Example request:** - -```bash -curl -X POST \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"cert\": \"$(cat device.pem)\"}" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/devices/lock-00a1b2c3/certificates" -``` - -**Example response (`201 Created`):** - -```json -{ - "data": { - "serial": "4d:b2:e3:...", - "not_before": "2024-11-01T00:00:00Z", - "not_after": "2027-11-01T00:00:00Z" - } -} -``` - - - Device certificates must be signed by a CA certificate registered to your organization. See [Organizations](/api/organizations) for how to register CA certificates. - - -*** - -## Delete a Device Certificate - -Remove a specific X.509 certificate from a device by its serial number. The device can no longer authenticate using this certificate after deletion. - -``` -DELETE /api/orgs/{org_name}/products/{product_name}/devices/{identifier}/certificates/{serial} -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The device identifier. - - - - The serial number of the certificate to remove, as returned by the list certificates endpoint. - - -**Example request:** - -```bash -curl -X DELETE \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/devices/lock-00a1b2c3/certificates/3c:a1:f2:..." -``` - -Returns `204 No Content` on success. - - - Deleting a device certificate immediately revokes the device's ability to authenticate using that certificate. Ensure the device has at least one other valid certificate, or that it can be re-provisioned, before removing its only certificate. - diff --git a/api/firmware.mdx b/api/firmware.mdx deleted file mode 100644 index be57a2c..0000000 --- a/api/firmware.mdx +++ /dev/null @@ -1,303 +0,0 @@ ---- -title: "Firmware API" -sidebarTitle: "Firmware" -description: "HTTP API endpoints to list, upload, download, and delete firmware files and manage firmware signing keys in NervesHub products." ---- - -The Firmware API lets you manage firmware files for your products programmatically — upload signed `.fw` archives, inspect metadata, and maintain the signing keys that NervesHub uses to verify firmware integrity before distributing updates to devices. - -## List Firmware - -Retrieve all firmware versions uploaded to a product. - -``` -GET /api/orgs/{org_name}/products/{product_name}/firmwares -``` - - - The slug of your organization. - - - - The slug of the product. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/firmwares" -``` - -**Example response:** - -```json -{ - "data": [ - { - "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", - "version": "1.4.2", - "architecture": "arm", - "platform": "rpi4", - "author": "ci-builder", - "size": 18457600, - "inserted_at": "2024-11-10T12:00:00Z" - }, - { - "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", - "version": "1.4.1", - "architecture": "arm", - "platform": "rpi4", - "author": "ci-builder", - "size": 18300928, - "inserted_at": "2024-10-28T09:45:00Z" - } - ] -} -``` - - - The unique identifier for this firmware artifact. Use this UUID when creating a deployment group or release. - - - - Semantic version string extracted from the firmware metadata at upload time. - - - - CPU architecture the firmware targets, e.g. `arm`, `arm64`, `x86_64`. - - - - Board or platform identifier, e.g. `rpi4`, `rpi0_2`, `bbb`. - - - - The NervesHub user or CI identity that uploaded the firmware. - - - - Size of the `.fw` file in bytes. - - -*** - -## Upload Firmware - -Upload a signed `.fw` firmware archive to a product. This is the typical step run in CI after `mix firmware` and firmware signing. - -``` -POST /api/orgs/{org_name}/products/{product_name}/firmwares -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The `.fw` binary file, sent as a `multipart/form-data` upload. - - -**Example request:** - -```bash -curl -X POST \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -F "firmware=@_build/rpi4/prod/nerves/images/smart_lock.fw" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/firmwares" -``` - -**Example response (`201 Created`):** - -```json -{ - "data": { - "uuid": "c9d8e7f6-a5b4-3210-fedc-ba9876543210", - "version": "1.4.3", - "architecture": "arm", - "platform": "rpi4", - "author": "ci-builder", - "size": 18534400, - "inserted_at": "2024-11-15T16:00:00Z" - } -} -``` - - - Firmware must be signed before uploading. NervesHub verifies the signature against the signing keys registered to your organization and rejects unsigned or unrecognized firmware. Sign your firmware with `fwup --sign` before calling this endpoint. - - -*** - -## Get Firmware Metadata - -Retrieve metadata for a specific firmware version by its UUID. - -``` -GET /api/orgs/{org_name}/products/{product_name}/firmwares/{uuid} -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The UUID of the firmware artifact. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/firmwares/f47ac10b-58cc-4372-a567-0e02b2c3d479" -``` - -**Example response:** - -```json -{ - "data": { - "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479", - "version": "1.4.2", - "architecture": "arm", - "platform": "rpi4", - "author": "ci-builder", - "size": 18457600, - "inserted_at": "2024-11-10T12:00:00Z" - } -} -``` - -*** - -## Delete Firmware - -Remove a firmware artifact from NervesHub. You cannot delete firmware that is referenced by an active deployment group. - -``` -DELETE /api/orgs/{org_name}/products/{product_name}/firmwares/{uuid} -``` - - - The slug of your organization. - - - - The slug of the product. - - - - The UUID of the firmware to delete. - - -**Example request:** - -```bash -curl -X DELETE \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock/firmwares/a1b2c3d4-e5f6-7890-abcd-ef1234567890" -``` - -Returns `204 No Content` on success. - - - Deleting firmware that devices are currently downloading will interrupt those updates. Deactivate any deployment groups referencing this firmware before deleting it. - - -*** - -## List Firmware Signing Keys - -List the public signing keys registered for an organization. NervesHub validates firmware signatures against these keys at upload time. - -``` -GET /api/orgs/{org_name}/keys -``` - - - The slug of your organization. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/keys" -``` - -**Example response:** - -```json -{ - "data": [ - { - "name": "ci-release-key", - "key": "MCowBQYDK2VwAyEA...", - "inserted_at": "2024-01-15T10:00:00Z" - } - ] -} -``` - -*** - -## Register a Firmware Signing Key - -Upload a new public signing key to your organization. Any firmware signed with the corresponding private key will pass signature verification on upload. - -``` -POST /api/orgs/{org_name}/keys -``` - - - The slug of your organization. - - - - A human-readable label for the key, e.g. `ci-release-key` or `hardware-signing-key`. - - - - The base64-encoded public key. Generate a key pair with `fwup --gen-keys` and supply the `.pub` file contents here. - - -**Example request:** - -```bash -curl -X POST \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "ci-release-key", - "key": "MCowBQYDK2VwAyEA7x3fK9mP..." - }' \ - "https://manage.nervescloud.com/api/orgs/acme/keys" -``` - -**Example response (`201 Created`):** - -```json -{ - "data": { - "name": "ci-release-key", - "key": "MCowBQYDK2VwAyEA7x3fK9mP...", - "inserted_at": "2024-11-15T17:00:00Z" - } -} -``` - - - Creating and deleting signing keys requires the `manage` role. - - - - Generate a signing key pair offline using `fwup --gen-keys`. Store the private key in your secrets manager (GitHub Actions secrets, HashiCorp Vault, etc.) and only register the public key here. The private key never leaves your build environment. - diff --git a/api/organizations.mdx b/api/organizations.mdx deleted file mode 100644 index 5bc1c23..0000000 --- a/api/organizations.mdx +++ /dev/null @@ -1,364 +0,0 @@ ---- -title: "Organizations API" -sidebarTitle: "Organizations" -description: "HTTP API endpoints for managing NervesHub organizations, products, and CA certificates used to authenticate and auto-provision devices at scale." ---- - -The Organizations API covers org-level resources including products, CA certificates, and the currently authenticated user. These endpoints establish the foundational structure — organizations own products, products own devices, and CA certificates enable automatic device provisioning when you onboard new hardware at scale. - -## Get the Current User - -Retrieve the profile of the user associated with the Bearer token in use. - -``` -GET /api/users/me -``` - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/users/me" -``` - -**Example response:** - -```json -{ - "data": { - "username": "alice", - "email": "alice@example.com" - } -} -``` - - - The account username. - - - - The verified email address associated with the account. - - -*** - -## List Products - -Retrieve all products in an organization. - -``` -GET /api/orgs/{org_name}/products -``` - - - The slug of the organization. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products" -``` - -**Example response:** - -```json -{ - "data": [ - { - "name": "smart-lock", - "inserted_at": "2023-06-01T09:00:00Z" - }, - { - "name": "smart-thermostat", - "inserted_at": "2023-09-15T14:30:00Z" - } - ] -} -``` - -*** - -## Get a Product - -Retrieve details about a single product by name. - -``` -GET /api/orgs/{org_name}/products/{product_name} -``` - - - The slug of the organization. - - - - The slug of the product to retrieve. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-lock" -``` - -**Example response:** - -```json -{ - "data": { - "name": "smart-lock", - "inserted_at": "2023-06-01T09:00:00Z" - } -} -``` - -*** - -## Create a Product - -Create a new product within an organization. Products group related devices and firmware under a single namespace. - -``` -POST /api/orgs/{org_name}/products -``` - - - The slug of the organization. - - - - The name (slug) of the product. Use lowercase letters, numbers, and hyphens. This value appears in all API paths for this product's resources. - - -**Example request:** - -```bash -curl -X POST \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name": "smart-thermostat"}' \ - "https://manage.nervescloud.com/api/orgs/acme/products" -``` - -**Example response (`201 Created`):** - -```json -{ - "data": { - "name": "smart-thermostat", - "inserted_at": "2024-11-15T19:00:00Z" - } -} -``` - - - Creating, updating, and deleting products requires the `manage` role. - - -*** - -## Update a Product - -Update the settings of an existing product. - -``` -PUT /api/orgs/{org_name}/products/{product_name} -``` - - - The slug of the organization. - - - - The current slug of the product. - - - - A new slug for the product. Renaming a product changes all its API paths — update any CI scripts or integrations that reference the old name. - - -**Example request:** - -```bash -curl -X PUT \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name": "smart-thermostat-v2"}' \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-thermostat" -``` - -*** - -## Delete a Product - -Permanently delete a product and all its associated devices, firmware, and deployment groups. - -``` -DELETE /api/orgs/{org_name}/products/{product_name} -``` - - - The slug of the organization. - - - - The slug of the product to delete. - - -**Example request:** - -```bash -curl -X DELETE \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/products/smart-thermostat" -``` - -Returns `204 No Content` on success. - - - Deleting a product is irreversible and immediately disconnects all devices in that product. All firmware, deployment groups, and device records are permanently removed. - - -*** - -## List CA Certificates - -List the CA certificates registered to an organization. NervesHub uses these certificates to automatically provision devices whose client certificates are signed by a known CA, without requiring each device to be pre-registered individually. - -``` -GET /api/orgs/{org_name}/ca_certificates -``` - - - The slug of the organization. - - -**Example request:** - -```bash -curl -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/ca_certificates" -``` - -**Example response:** - -```json -{ - "data": [ - { - "serial": "3c:a1:f2:88:de:44:bc:01", - "description": "Factory CA — Line 1", - "not_before": "2023-01-01T00:00:00Z", - "not_after": "2028-01-01T00:00:00Z" - }, - { - "serial": "7e:b9:20:cc:1f:55:aa:09", - "description": "Factory CA — Line 2", - "not_before": "2023-06-01T00:00:00Z", - "not_after": "2028-06-01T00:00:00Z" - } - ] -} -``` - - - The hex serial number of the CA certificate. Use this value when deleting a certificate. - - - - A human-readable label you assigned when registering the certificate. - - - - ISO 8601 timestamp for the start of the certificate's validity period. - - - - ISO 8601 timestamp for the end of the certificate's validity period. NervesHub will reject device certificates signed by an expired CA. - - -*** - -## Register a CA Certificate - -Upload a CA certificate to your organization. Any device certificate signed by this CA will be automatically trusted and the device provisioned on first connection. - -``` -POST /api/orgs/{org_name}/ca_certificates -``` - - - The slug of the organization. - - - - The PEM-encoded CA certificate. This is the public certificate only — do not include the private key. - - - - An optional human-readable label, e.g. `Factory CA — Line 1`, to identify this certificate in the UI and API. - - -**Example request:** - -```bash -curl -X POST \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{ - \"cert\": \"$(cat factory-ca.pem)\", - \"description\": \"Factory CA — Line 1\" - }" \ - "https://manage.nervescloud.com/api/orgs/acme/ca_certificates" -``` - -**Example response (`201 Created`):** - -```json -{ - "data": { - "serial": "3c:a1:f2:88:de:44:bc:01", - "description": "Factory CA — Line 1", - "not_before": "2023-01-01T00:00:00Z", - "not_after": "2028-01-01T00:00:00Z" - } -} -``` - - - The CA certificate must be a valid X.509 certificate in PEM format. NervesHub only stores the public certificate — your CA private key should remain in your secure key management system. Registering and removing certificate authorities requires the `admin` role. - - -*** - -## Delete a CA Certificate - -Remove a CA certificate from your organization. Existing device certificates signed by this CA will no longer be valid for auto-provisioning; devices already provisioned are not automatically removed. - -``` -DELETE /api/orgs/{org_name}/ca_certificates/{serial} -``` - - - The slug of the organization. - - - - The hex serial number of the CA certificate, as returned by the list endpoint. - - -**Example request:** - -```bash -curl -X DELETE \ - -H "Authorization: Bearer $NERVES_HUB_TOKEN" \ - "https://manage.nervescloud.com/api/orgs/acme/ca_certificates/3c:a1:f2:88:de:44:bc:01" -``` - -Returns `204 No Content` on success. - - - Before deleting a CA certificate, ensure no active devices depend on it for authentication. You can check by listing devices and filtering for those using certificates signed by this CA. Rotating rather than deleting — registering a new CA before removing the old one — ensures uninterrupted device connectivity. - diff --git a/api/overview.mdx b/api/overview.mdx index 1e32690..d34543d 100644 --- a/api/overview.mdx +++ b/api/overview.mdx @@ -4,7 +4,9 @@ sidebarTitle: "Overview" description: "Explore the NervesHub HTTP REST API: base URL, OpenAPI spec location, JSON response format, URL structure, and full HTTP status code reference." --- -NervesHub provides a REST API documented with an OpenAPI specification, giving you programmatic access to devices, firmware, deployment groups, and organizations. Whether you are automating firmware releases in CI or building a custom dashboard, the API lets you integrate NervesHub into any workflow. +NervesHub provides a REST API covering devices, firmware, deployment groups, products, organizations, signing keys, support scripts and more. Whether you are automating firmware releases in CI or building a custom dashboard, the API lets you integrate NervesHub into any workflow. + +The **Endpoints** section in the sidebar is generated directly from the server's OpenAPI specification, so it always matches what the API actually accepts. This page covers what the specification does not: base URLs, how responses are shaped, and what each status code means. ## Base URL @@ -23,12 +25,16 @@ https://manage.nervescloud.com/api/orgs/{org_name}/products/{product_name}/devic ## OpenAPI Specification -NervesHub publishes a machine-readable OpenAPI spec you can import into any compatible tooling (Postman, Insomnia, code generators, etc.): +The server generates its own OpenAPI document and serves it, so it is never out of step with the running code: -* **Spec document:** `https://manage.nervescloud.com/api/openapi` — generated by the server and served directly from your instance +* **Spec document:** `https://manage.nervescloud.com/api/openapi` * **Interactive docs:** `https://manage.nervescloud.com/api/docs` -Both paths are relative to your base URL, so a self-hosted instance serves its own spec at `https://nerveshub.example.com/api/openapi`. Fetching the spec from the instance you are targeting guarantees it matches the version you are actually calling. +Both paths are relative to your base URL, so a self-hosted instance serves its own spec at `https://nerveshub.example.com/api/openapi`. Import it into Postman, Insomnia, a code generator, or anything else that reads OpenAPI. + + + The endpoint reference in this section is built from NervesCloud's specification. A self-hosted deployment exposes the same API, but if you are running an older version, fetch the spec from your own instance to see exactly what it offers. + ## URL Structure diff --git a/docs.json b/docs.json index fc40ea9..3404dc2 100644 --- a/docs.json +++ b/docs.json @@ -96,13 +96,13 @@ "group": "HTTP API", "pages": [ "api/overview", - "api/authentication", - "api/devices", - "api/firmware", - "api/deployments", - "api/organizations" + "api/authentication" ] }, + { + "group": "Endpoints", + "openapi": "https://manage.nervescloud.com/api/openapi" + }, { "group": "Device WebSocket", "pages": [ @@ -129,6 +129,26 @@ "source": "/api/nerveshublink-client", "destination": "/integrations/nerves-hub-link", "permanent": true + }, + { + "source": "/api/devices", + "destination": "/api/overview", + "permanent": true + }, + { + "source": "/api/firmware", + "destination": "/api/overview", + "permanent": true + }, + { + "source": "/api/deployments", + "destination": "/api/overview", + "permanent": true + }, + { + "source": "/api/organizations", + "destination": "/api/overview", + "permanent": true } ], "footer": { From ef34ad6d13cd9a4f7c6adcf74bfca80c04ec3ca4 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sat, 5 Sep 2026 18:32:28 +1200 Subject: [PATCH 2/3] Fix both CI failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deployment: Mintlify could not fetch the spec from the remote URL, so it built with no OpenAPI file at all. The spec is now checked in as openapi.json and referenced by path, which is the mechanism Mintlify uses by default and removes the build's dependency on the production host being reachable. Link-rot: the anchor #device-server-events did not exist. Mintlify keeps the arrow when slugifying, so the real anchor is #device-→-server-events. The link now points at #rebooting, which is the event the sentence is actually about and has a clean slug. Co-Authored-By: Claude Opus 5 --- api/overview.mdx | 2 +- api/websocket-events.mdx | 2 +- docs.json | 2 +- openapi.json | 8865 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 8868 insertions(+), 3 deletions(-) create mode 100644 openapi.json diff --git a/api/overview.mdx b/api/overview.mdx index d34543d..75d4818 100644 --- a/api/overview.mdx +++ b/api/overview.mdx @@ -33,7 +33,7 @@ The server generates its own OpenAPI document and serves it, so it is never out Both paths are relative to your base URL, so a self-hosted instance serves its own spec at `https://nerveshub.example.com/api/openapi`. Import it into Postman, Insomnia, a code generator, or anything else that reads OpenAPI. - The endpoint reference in this section is built from NervesCloud's specification. A self-hosted deployment exposes the same API, but if you are running an older version, fetch the spec from your own instance to see exactly what it offers. + The endpoint reference in this section is built from a checked-in copy of NervesCloud's specification (`openapi.json` in this repository). A self-hosted deployment exposes the same API, but if you are running a different version, fetch the spec from your own instance to see exactly what it offers. ## URL Structure diff --git a/api/websocket-events.mdx b/api/websocket-events.mdx index 53b9b94..348ce42 100644 --- a/api/websocket-events.mdx +++ b/api/websocket-events.mdx @@ -68,7 +68,7 @@ The server is requesting that the device perform a graceful reboot. The payload {} ``` -Upon receiving this event, the device should send a `rebooting` event back to the server (see [Device → Server Events](#device-server-events)), then initiate a system reboot. This event is typically triggered from the NervesHub web console or via the management API. +Upon receiving this event, the device should send a [`rebooting`](#rebooting) event back to the server, then initiate a system reboot. This event is typically triggered from the NervesHub web console or via the management API. *** diff --git a/docs.json b/docs.json index 3404dc2..8793501 100644 --- a/docs.json +++ b/docs.json @@ -101,7 +101,7 @@ }, { "group": "Endpoints", - "openapi": "https://manage.nervescloud.com/api/openapi" + "openapi": "openapi.json" }, { "group": "Device WebSocket", diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..5e992b0 --- /dev/null +++ b/openapi.json @@ -0,0 +1,8865 @@ +{ + "components": { + "responses": { + "unprocessable_entity": { + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "schemas": { + "CACertificate": { + "example": { + "description": "Example CA", + "jitp": { + "description": "Production", + "product_name": "ExampleProduct", + "tags": [ + "prod" + ] + }, + "not_after": "2050-04-20T00:33:09Z", + "not_before": "2025-04-20T00:28:09Z", + "serial": "4016688295714810857" + }, + "properties": { + "description": { + "pattern": "[a-zA-Z][a-zA-Z0-9_]+", + "type": "string" + }, + "inserted_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "jitp": { + "properties": { + "description": { + "type": "string" + }, + "product_name": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "not_after": { + "description": "Certificate expiration timestamp", + "format": "date-time", + "type": "string" + }, + "not_before": { + "description": "Certificate valid from timestamp", + "format": "date-time", + "type": "string" + }, + "serial": { + "type": "string" + }, + "updated_at": { + "description": "Last updated timestamp", + "format": "date-time", + "type": "string" + } + }, + "title": "CACertificate", + "type": "object" + }, + "CACertificateCreationRequest": { + "description": "POST body for creating a CA Certificate", + "example": { + "cert": "base64 encoded certificate", + "description": "Example CA", + "jitp": { + "description": "Production", + "product_id": 33438, + "tags": [ + "prod" + ] + }, + "verification_cert": "base64 encoded verification certificate" + }, + "properties": { + "cert": { + "type": "string" + }, + "description": { + "type": "string" + }, + "jitp": { + "properties": { + "description": { + "type": "string" + }, + "product_id": { + "type": "integer" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "verification_cert": { + "type": "string" + } + }, + "required": [ + "cert", + "verification_cert" + ], + "title": "CACertificateCreationRequest", + "type": "object" + }, + "CACertificateListResponse": { + "description": "Response schema for multiple CA Certificates", + "example": { + "data": [ + { + "description": "Example CA", + "jitp": { + "description": "Staging", + "product_name": "StagingProduct", + "tags": [ + "staging" + ] + }, + "not_after": "2050-04-20T00:33:09Z", + "not_before": "2025-04-20T00:28:09Z", + "serial": "4016688295714810857" + }, + { + "description": "Another Example CA", + "jitp": { + "description": "QA", + "product_name": "QAProduct", + "tags": [ + "qa" + ] + }, + "not_after": "2050-04-20T00:33:09Z", + "not_before": "2025-04-20T00:28:09Z", + "serial": "8033376591429621714" + } + ] + }, + "properties": { + "data": { + "description": "The CA Certificate details", + "items": { + "$ref": "#/components/schemas/CACertificate" + }, + "type": "array" + } + }, + "title": "CACertificateListResponse", + "type": "object" + }, + "CACertificateShowResponse": { + "description": "Response schema for a single CA Certificate", + "example": { + "data": { + "description": "Example CA", + "jitp": { + "description": "Production", + "product_name": "ExampleProduct", + "tags": [ + "prod" + ] + }, + "not_after": "2050-04-20T00:33:09Z", + "not_before": "2025-04-20T00:28:09Z", + "serial": "4016688295714810857" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/CACertificate" + } + }, + "title": "CACertificateShowResponse", + "type": "object" + }, + "CACertificateVerificationToken": { + "example": { + "verification_token": "abc.doerayme.boop_snoot" + }, + "properties": { + "verification_token": { + "type": "string" + } + }, + "title": "CACertificateVerificationToken", + "type": "object" + }, + "CACertificateVerificationTokenResponse": { + "description": "Response schema for requesting a CA Certificate verification token", + "example": { + "data": { + "verification_token": "abc.doerayme.boop_snoot" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/CACertificateVerificationToken" + } + }, + "title": "CACertificateVerificationTokenResponse", + "type": "object" + }, + "CLISession": { + "description": "A new CLI auth exchange session", + "example": { + "confirmation_code": "312812", + "token": "218ed524-eb74-47d7-aedc-11e386961b72", + "url": "https://manage.nervescloud.com/auth/cli/218ed524-eb74-47d7-aedc-11e386961b72" + }, + "properties": { + "confirmation_code": { + "description": "Confirmation code to help verify a valid auth exchange", + "type": "string" + }, + "token": { + "description": "Auth token", + "type": "string" + }, + "url": { + "description": "URL to complete the auth exchange", + "type": "string" + } + }, + "required": [ + "token", + "url" + ], + "title": "CLISession", + "type": "object" + }, + "CLISessionStatus": { + "description": "CLI auth exchange session status", + "example": { + "status": "ready", + "user_token": "nhu_aaabbbccc123" + }, + "properties": { + "status": { + "description": "status of the auth exchange", + "type": "string" + }, + "user_token": { + "description": "user auth token", + "type": "string" + } + }, + "required": [ + "status" + ], + "title": "CLISessionStatus", + "type": "object" + }, + "ChangesetErrorResponse": { + "description": "Validation error response", + "example": { + "errors": { + "identifier": [ + "can't be blank" + ] + } + }, + "properties": { + "errors": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object" + } + }, + "title": "ChangesetErrorResponse", + "type": "object" + }, + "Conditions": { + "example": { + "tag_operator": "and", + "tags": [ + "beta" + ], + "version": ">= 1.0.0" + }, + "properties": { + "tag_operator": { + "default": "and", + "description": "How device tags are matched: \"and\" (require all) or \"or\" (allow any)", + "enum": [ + "and", + "or" + ], + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "version": { + "type": "string" + } + }, + "title": "Conditions", + "type": "object" + }, + "CurrentRelease": { + "example": { + "firmware": { + "architecture": "arm", + "platform": "rpi0", + "uuid": "d9f8c63a-1234-5678-abcd-ef0123456789", + "version": "1.0.0" + }, + "inserted_at": "2024-01-01T00:00:00Z", + "number": 3, + "updated_at": "2024-01-01T00:00:00Z" + }, + "properties": { + "firmware": { + "$ref": "#/components/schemas/Firmware" + }, + "inserted_at": { + "format": "date-time", + "type": "string" + }, + "number": { + "type": "integer" + }, + "updated_at": { + "format": "date-time", + "type": "string" + } + }, + "title": "CurrentRelease", + "type": "object" + }, + "DeploymentGroup": { + "example": { + "conditions": { + "tags": [ + "beta" + ], + "version": ">= 1.0.0" + }, + "current_release": { + "firmware": { + "architecture": "arm", + "platform": "rpi0", + "uuid": "d9f8c63a-1234-5678-abcd-ef0123456789", + "version": "1.0.0" + }, + "inserted_at": "2024-01-01T00:00:00Z", + "number": 3, + "updated_at": "2024-01-01T00:00:00Z" + }, + "delta_updatable": false, + "device_count": 42, + "firmware_uuid": "d9f8c63a-1234-5678-abcd-ef0123456789", + "is_active": true, + "name": "production", + "notes": "Rolled out for the summer campaign hardware batch", + "releases_count": 3, + "state": "on" + }, + "properties": { + "conditions": { + "$ref": "#/components/schemas/Conditions" + }, + "current_release": { + "$ref": "#/components/schemas/CurrentRelease" + }, + "delta_updatable": { + "type": "boolean" + }, + "device_count": { + "type": "integer" + }, + "firmware_uuid": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "notes": { + "description": "Free-text notes describing why this deployment group exists", + "nullable": true, + "type": "string" + }, + "releases_count": { + "type": "integer" + }, + "state": { + "enum": [ + "on", + "off" + ], + "type": "string" + } + }, + "title": "DeploymentGroup", + "type": "object" + }, + "DeploymentGroupCreationRequest": { + "description": "POST body for creating a Deployment Group", + "example": { + "conditions": { + "tags": [ + "prod" + ], + "version": ">= 1.0.0" + }, + "firmware": "d9f8c63a-1234-5678-abcd-ef0123456789", + "name": "production", + "notes": "Rolled out for the summer campaign hardware batch", + "state": "on" + }, + "properties": { + "conditions": { + "$ref": "#/components/schemas/Conditions" + }, + "delta_updatable": { + "type": "boolean" + }, + "firmware": { + "description": "Firmware UUID", + "type": "string" + }, + "name": { + "type": "string" + }, + "notes": { + "description": "Free-text notes describing why this deployment group is being created", + "type": "string" + }, + "state": { + "enum": [ + "on", + "off" + ], + "type": "string" + } + }, + "required": [ + "name", + "firmware" + ], + "title": "DeploymentGroupCreationRequest", + "type": "object" + }, + "DeploymentGroupListResponse": { + "description": "Response schema for multiple Deployment Groups", + "properties": { + "data": { + "items": { + "$ref": "#/components/schemas/DeploymentGroup" + }, + "type": "array" + } + }, + "title": "DeploymentGroupListResponse", + "type": "object" + }, + "DeploymentGroupResponse": { + "description": "Response schema for a single Deployment Group", + "properties": { + "data": { + "$ref": "#/components/schemas/DeploymentGroup" + } + }, + "title": "DeploymentGroupResponse", + "type": "object" + }, + "DeploymentGroupUpdateRequest": { + "description": "PUT body for updating a Deployment Group", + "example": { + "deployment": { + "conditions": { + "tags": [ + "prod" + ], + "version": ">= 1.0.0" + }, + "notes": "Rolled out for the summer campaign hardware batch", + "state": "on" + } + }, + "properties": { + "deployment": { + "properties": { + "conditions": { + "$ref": "#/components/schemas/Conditions" + }, + "delta_updatable": { + "type": "boolean" + }, + "firmware": { + "description": "Firmware UUID", + "type": "string" + }, + "notes": { + "description": "Free-text notes describing why this deployment group exists", + "type": "string" + }, + "state": { + "enum": [ + "on", + "off" + ], + "type": "string" + } + }, + "type": "object" + } + }, + "title": "DeploymentGroupUpdateRequest", + "type": "object" + }, + "Device": { + "example": { + "connection_status": "connected", + "deployment_group": { + "firmware_uuid": "6fd2bbc8-52b8-4826-5c2a-189968d0de23", + "firmware_version": "1.2.3", + "is_active": true, + "name": "Prod Deployment" + }, + "description": "A great device", + "firmware_metadata": { + "architecture": "arm", + "author": "", + "description": "Prod Firmware", + "fwup_version": "1.10.1", + "id": "3f2264c3-cc52-2ba9-b77d-e441f8bb91b6", + "misc": "extra comments", + "platform": "rpi5", + "product": "AmazingProduct", + "uuid": "6fd2bbc8-52b8-4826-5c2a-189968d0de23", + "vcs_identifier": "", + "version": "1.2.3" + }, + "identifier": "abc123", + "last_communication": "2050-04-20T00:33:09Z", + "online": true, + "org_name": "BigCompany", + "product_name": "AmazingProduct", + "tags": "prod, customerABC", + "updates_blocked_until": "2050-04-20T00:33:09Z", + "updates_enabled": true, + "version": "1.2.3" + }, + "properties": { + "connection_status": { + "enum": [ + "connected", + "disconnected" + ], + "type": "string" + }, + "deployment_group": { + "properties": { + "firmware_uuid": { + "type": "string" + }, + "firmware_version": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "description": { + "type": "string" + }, + "firmware_metadata": { + "properties": { + "architecture": { + "type": "string" + }, + "author": { + "type": "string" + }, + "description": { + "type": "string" + }, + "fwup_version": { + "type": "string" + }, + "id": { + "type": "string" + }, + "misc": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "product": { + "type": "string" + }, + "uuid": { + "type": "string" + }, + "vcs_identifier": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "identifier": { + "type": "string" + }, + "last_communication": { + "deprecated": true, + "format": "date-time", + "type": "string" + }, + "online": { + "type": "boolean" + }, + "org_name": { + "type": "string" + }, + "product_name": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "update_mode": { + "description": "How the device receives firmware. automatic: pushed by its deployment group. device_managed: the device asks for updates itself. off: neither, only a manual push.", + "enum": [ + "off", + "automatic", + "device_managed" + ], + "type": "string" + }, + "updates_blocked_until": { + "description": "Device penalty box expiration timestamp", + "format": "date-time", + "type": "string" + }, + "updates_enabled": { + "description": "Whether the device takes updates at all. False only when update_mode is off.", + "type": "boolean" + }, + "version": { + "type": "string" + } + }, + "title": "Device", + "type": "object" + }, + "DeviceBulkImport": { + "description": "POST body for bulk creating a list of Devices", + "example": [ + "Please refer to the Microchip Trust and Go Manifest documentation" + ], + "title": "DeviceBulkImport", + "type": "object" + }, + "DeviceCertificate": { + "example": { + "not_after": "2052-12-15T21:00:00Z", + "not_before": "2022-12-15T20:00:00Z", + "serial": "123456789101112" + }, + "properties": { + "not_after": { + "format": "date-time", + "type": "string" + }, + "not_before": { + "format": "date-time", + "type": "string" + }, + "serial": { + "type": "string" + } + }, + "title": "DeviceCertificate", + "type": "object" + }, + "DeviceCertificateAuthRequest": { + "description": "POST body for testing certificate auth for a Device", + "example": { + "certificate": "Base64 encoded certificate" + }, + "properties": { + "certificate": { + "type": "string" + } + }, + "required": [ + "certificate" + ], + "title": "DeviceCertificateAuthRequest", + "type": "object" + }, + "DeviceCertificateCreateRequest": { + "description": "POST body for creating a Certificate for a Device", + "example": { + "cert": "[Base64 encoded certificate]==" + }, + "properties": { + "cert": { + "description": "Base64 encoded certificate", + "type": "string" + } + }, + "required": [ + "cert" + ], + "title": "DeviceCertificateCreateRequest", + "type": "object" + }, + "DeviceCertificateListResponse": { + "description": "Device Certificate list response", + "example": { + "data": [ + { + "not_after": "2052-12-15T21:00:00Z", + "not_before": "2022-12-15T20:00:00Z", + "serial": "123456789101112" + }, + { + "not_after": "2052-12-15T21:00:00Z", + "not_before": "2022-12-15T20:00:00Z", + "serial": "123456789202122" + } + ] + }, + "properties": { + "data": { + "description": "The device certificates details", + "items": { + "$ref": "#/components/schemas/DeviceCertificate" + }, + "type": "array" + } + }, + "title": "DeviceCertificateListResponse", + "type": "object" + }, + "DeviceCertificateShowResponse": { + "description": "Device Certificate show response", + "example": { + "data": { + "not_after": "2052-12-15T21:00:00Z", + "not_before": "2022-12-15T20:00:00Z", + "serial": "123456789101112" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/DeviceCertificate" + } + }, + "title": "DeviceCertificateShowResponse", + "type": "object" + }, + "DeviceCodeRequest": { + "description": "POST body for sending Elixir code to a Device's console", + "example": { + "code": "NervesHub.version()" + }, + "properties": { + "code": { + "description": "Elixir code to execute", + "type": "string" + } + }, + "required": [ + "code" + ], + "title": "DeviceCodeRequest", + "type": "object" + }, + "DeviceCreationRequest": { + "description": "POST body for creating a Device", + "example": { + "device": { + "deployment_group_id": 1, + "description": "Example Device", + "identifier": "abc123", + "tags": "prod, customerJNK", + "updates_enabled": false + } + }, + "properties": { + "device": { + "properties": { + "deployment_group_id": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "updates_enabled": { + "type": "boolean" + } + }, + "required": [ + "identifier" + ] + } + }, + "required": [ + "device" + ], + "title": "DeviceCreationRequest", + "type": "object" + }, + "DeviceListResponse": { + "description": "Response schema for multiple Devices", + "example": { + "data": [ + { + "connection_status": "connected", + "deployment_group": { + "firmware_uuid": "6fd2bbc8-52b8-4826-5c2a-189968d0de23", + "firmware_version": "1.2.3", + "is_active": true, + "name": "Prod Deployment" + }, + "description": "A great device", + "firmware_metadata": { + "architecture": "arm", + "author": "", + "description": "Prod Firmware", + "fwup_version": "1.10.1", + "id": "3f2264c3-cc52-2ba9-b77d-e441f8bb91b6", + "misc": "extra comments", + "platform": "rpi5", + "product": "AmazingProduct", + "uuid": "6fd2bbc8-52b8-4826-5c2a-189968d0de23", + "vcs_identifier": "", + "version": "1.2.3" + }, + "identifier": "abc123", + "last_communication": "2050-04-20T00:33:09Z", + "online": true, + "org_name": "BigCompany", + "product_name": "AmazingProduct", + "tags": "prod, customerABC", + "updates_blocked_until": "2050-04-20T00:33:09Z", + "updates_enabled": true, + "version": "1.2.3" + }, + { + "connection_status": "disconnected", + "deployment_group": { + "firmware_uuid": "6fd2bbc8-52b8-4826-5c2a-189968d0de23", + "firmware_version": "1.2.3", + "is_active": true, + "name": "Prod Deployment" + }, + "description": "Another great device", + "firmware_metadata": { + "architecture": "arm", + "author": "", + "description": "Prod Firmware", + "fwup_version": "1.10.1", + "id": "3f2264c3-cc52-2ba9-b77d-e441f8bb91b6", + "misc": "extra comments", + "platform": "rpi5", + "product": "AmazingProduct", + "uuid": "6fd2bbc8-52b8-4826-5c2a-189968d0de23", + "vcs_identifier": "", + "version": "1.2.3" + }, + "identifier": "def456", + "last_communication": "2050-04-20T00:33:09Z", + "online": false, + "org_name": "BigCompany", + "product_name": "AmazingProduct", + "tags": "prod, customerDEF", + "updates_blocked_until": "2050-04-20T00:33:09Z", + "updates_enabled": true, + "version": "1.2.3" + } + ], + "pagination": { + "page_number": 1, + "page_size": 10, + "total_entries": 2, + "total_pages": 1 + } + }, + "properties": { + "data": { + "description": "The Device schema", + "items": { + "$ref": "#/components/schemas/Device" + }, + "type": "array" + }, + "pagination": { + "properties": { + "page_number": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total_entries": { + "type": "integer" + }, + "total_pages": { + "type": "integer" + } + }, + "type": "object" + } + }, + "title": "DeviceListResponse", + "type": "object" + }, + "DeviceLogLine": { + "description": "A log line a device sent over the logging extension", + "example": { + "level": "error", + "message": "Failed to reach the sensor bus", + "meta": { + "file": "lib/thermostat/sensors.ex", + "line": "42" + }, + "timestamp": "2026-08-16T09:14:00.123456Z" + }, + "properties": { + "level": { + "description": "The level the device logged at. Usually an Elixir Logger level — debug, info, notice, warning, error, critical, alert, emergency — but a device can log at any level it likes", + "example": "error", + "type": "string" + }, + "message": { + "description": "The logged message", + "type": "string" + }, + "meta": { + "additionalProperties": { + "type": "string" + }, + "description": "The Logger metadata the device attached to the line, flattened to strings", + "type": "object" + }, + "timestamp": { + "description": "When the device logged the line, to microsecond precision", + "format": "date-time", + "type": "string" + } + }, + "title": "DeviceLogLine", + "type": "object" + }, + "DeviceLogListResponse": { + "description": "Device log list response", + "example": { + "data": [ + { + "level": "error", + "message": "Failed to reach the sensor bus", + "meta": { + "file": "lib/thermostat/sensors.ex", + "line": "42" + }, + "timestamp": "2026-08-16T09:14:00.123456Z" + }, + { + "level": "info", + "message": "Starting sensor poll", + "meta": {}, + "timestamp": "2026-08-16T09:13:59.998211Z" + } + ] + }, + "properties": { + "data": { + "description": "The matching log lines, newest first unless `order=asc` was given", + "items": { + "$ref": "#/components/schemas/DeviceLogLine" + }, + "type": "array" + } + }, + "title": "DeviceLogListResponse", + "type": "object" + }, + "DeviceResponse": { + "description": "Response schema for a single Device", + "example": { + "data": { + "connection_status": "connected", + "deployment_group": { + "firmware_uuid": "6fd2bbc8-52b8-4826-5c2a-189968d0de23", + "firmware_version": "1.2.3", + "is_active": true, + "name": "Prod Deployment" + }, + "description": "A great device", + "firmware_metadata": { + "architecture": "arm", + "author": "", + "description": "Prod Firmware", + "fwup_version": "1.10.1", + "id": "3f2264c3-cc52-2ba9-b77d-e441f8bb91b6", + "misc": "extra comments", + "platform": "rpi5", + "product": "AmazingProduct", + "uuid": "6fd2bbc8-52b8-4826-5c2a-189968d0de23", + "vcs_identifier": "", + "version": "1.2.3" + }, + "identifier": "abc123", + "last_communication": "2050-04-20T00:33:09Z", + "online": true, + "org_name": "BigCompany", + "product_name": "AmazingProduct", + "tags": "prod, customerABC", + "updates_blocked_until": "2050-04-20T00:33:09Z", + "updates_enabled": true, + "version": "1.2.3" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/Device" + } + }, + "title": "DeviceResponse", + "type": "object" + }, + "DeviceUpdateRequest": { + "description": "POST body for updating a Device", + "example": { + "device": { + "deployment_group_id": 1, + "description": "Example Device", + "tags": "prod, customerJNK", + "updates_enabled": false + } + }, + "properties": { + "device": { + "properties": { + "deployment_group_id": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "updates_enabled": { + "type": "boolean" + } + } + } + }, + "title": "DeviceUpdateRequest", + "type": "object" + }, + "DeviceUpgradeRequest": { + "description": "POST body for upgrading a Device to a specific Firmware", + "example": { + "uuid": "d9f8c63a-1234-5678-abcd-ef0123456789" + }, + "properties": { + "uuid": { + "description": "Target firmware UUID", + "format": "uuid", + "type": "string" + } + }, + "required": [ + "uuid" + ], + "title": "DeviceUpgradeRequest", + "type": "object" + }, + "ErrorResponse": { + "description": "Error response", + "example": { + "errors": { + "detail": "Resource Not Found or Authorization Insufficient" + } + }, + "properties": { + "errors": { + "properties": { + "detail": { + "type": "string" + } + }, + "type": "object" + } + }, + "title": "ErrorResponse", + "type": "object" + }, + "Firmware": { + "example": { + "architecture": "arm", + "author": "NervesHub", + "platform": "rpi0", + "product": "MyProduct", + "tool": "fwup", + "uuid": "d9f8c63a-1234-5678-abcd-ef0123456789", + "version": "1.0.0" + }, + "properties": { + "architecture": { + "type": "string" + }, + "author": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "product": { + "description": "Product name", + "type": "string" + }, + "tool": { + "description": "The update tool that handles this firmware, determined from the uploaded file", + "enum": [ + "fwup", + "esp-idf" + ], + "type": "string" + }, + "uuid": { + "format": "uuid", + "type": "string" + }, + "version": { + "type": "string" + } + }, + "title": "Firmware", + "type": "object" + }, + "FirmwareListResponse": { + "description": "Response schema for multiple Firmwares", + "example": { + "data": [ + { + "architecture": "arm", + "author": "NervesHub", + "platform": "rpi0", + "product": "MyProduct", + "uuid": "d9f8c63a-1234-5678-abcd-ef0123456789", + "version": "1.0.0" + } + ] + }, + "properties": { + "data": { + "description": "The Firmware details", + "items": { + "$ref": "#/components/schemas/Firmware" + }, + "type": "array" + } + }, + "title": "FirmwareListResponse", + "type": "object" + }, + "FirmwareResponse": { + "description": "Response schema for a single Firmware", + "example": { + "data": { + "architecture": "arm", + "author": "NervesHub", + "platform": "rpi0", + "product": "MyProduct", + "tool": "fwup", + "uuid": "d9f8c63a-1234-5678-abcd-ef0123456789", + "version": "1.0.0" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/Firmware" + } + }, + "title": "FirmwareResponse", + "type": "object" + }, + "FirmwareUploadErrorResponse": { + "description": "Upload failure.\n\nChangeset failures (a duplicate UUID, say) are keyed by field. Failures\nraised before the changeset — an unrecognised format, a format this\nproduct does not accept, an unsigned image, a version that is not SemVer —\ncarry a single `detail` message instead.\n", + "example": { + "errors": { + "detail": "This ESP-IDF image is not signed. Sign it with `espsecure.py sign_data --version 2` and register the matching public key against your organization, or allow unsigned images in this product's settings." + } + }, + "oneOf": [ + { + "$ref": "#/components/schemas/ChangesetErrorResponse" + }, + { + "$ref": "#/components/schemas/ErrorResponse" + } + ], + "title": "FirmwareUploadErrorResponse", + "type": "object" + }, + "IrohEndpoint": { + "description": "An iroh endpoint id registered to an organization", + "example": { + "details": {}, + "identifier": "c8924b6c9b7a8528b1365ebec4b2e43b6edebef684f8521f12b8caaf6e1b2302", + "inserted_at": "2026-08-14T11:02:31Z", + "instance": "default", + "last_reported_at": "2026-08-16T09:14:00Z", + "owner": { + "device_identifier": "example_device", + "type": "device", + "user_email": null, + "user_name": null + }, + "service": "iroh", + "source": "device_reported", + "updated_at": "2026-08-16T09:14:00Z" + }, + "properties": { + "details": { + "additionalProperties": true, + "type": "object" + }, + "identifier": { + "description": "The endpoint id — 64 hex characters", + "type": "string" + }, + "inserted_at": { + "format": "date-time", + "type": "string" + }, + "instance": { + "description": "Which endpoint of iroh this is. `default` for anything running a single one", + "type": "string" + }, + "last_reported_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/IrohEndpointOwner" + }, + "service": { + "enum": [ + "iroh" + ], + "type": "string" + }, + "source": { + "description": "`device_reported` means a device proved this key; `operator` means it was registered by hand", + "enum": [ + "device_reported", + "operator" + ], + "type": "string" + }, + "updated_at": { + "format": "date-time", + "type": "string" + } + }, + "title": "IrohEndpoint", + "type": "object" + }, + "IrohEndpointCreateRequest": { + "description": "POST body for registering an Iroh Endpoint against an Organization", + "example": { + "identifier": "c8924b6c9b7a8528b1365ebec4b2e43b6edebef684f8521f12b8caaf6e1b2302", + "instance": "console", + "user_email": "member@example.com" + }, + "properties": { + "details": { + "additionalProperties": true, + "description": "Anything worth recording alongside the key. Non-authoritative, and never a secret", + "type": "object" + }, + "identifier": { + "description": "The endpoint id — the public key the endpoint proves it holds, not a ticket or a relay url", + "type": "string" + }, + "instance": { + "default": "default", + "description": "Names which endpoint this is, for something running more than one. Omit for a single one", + "type": "string" + }, + "user_email": { + "description": "Attach the endpoint to this member of the organization — their laptop, say. Omit for one the organization holds directly, or for a device that will claim it on its next connection. The address must belong to a member", + "nullable": true, + "type": "string" + } + }, + "required": [ + "identifier" + ], + "title": "IrohEndpointCreateRequest", + "type": "object" + }, + "IrohEndpointListResponse": { + "description": "Iroh Endpoint list response", + "example": { + "data": [ + { + "details": {}, + "identifier": "c8924b6c9b7a8528b1365ebec4b2e43b6edebef684f8521f12b8caaf6e1b2302", + "inserted_at": "2026-08-14T11:02:31Z", + "instance": "default", + "last_reported_at": "2026-08-16T09:14:00Z", + "owner": { + "device_identifier": "example_device", + "type": "device", + "user_email": null, + "user_name": null + }, + "service": "iroh", + "source": "device_reported", + "updated_at": "2026-08-16T09:14:00Z" + }, + { + "details": {}, + "identifier": "5f691e39f55415be337b2e4cc0dd7291586ab7c4356bf32bab60f46fc78f95d5", + "inserted_at": "2026-08-15T08:41:12Z", + "instance": "default", + "last_reported_at": null, + "owner": { + "device_identifier": null, + "type": "user", + "user_email": "member@example.com", + "user_name": "Alex Doe" + }, + "service": "iroh", + "source": "operator", + "updated_at": "2026-08-15T08:41:12Z" + } + ] + }, + "properties": { + "data": { + "description": "The organization's iroh endpoint ids", + "items": { + "$ref": "#/components/schemas/IrohEndpoint" + }, + "type": "array" + } + }, + "title": "IrohEndpointListResponse", + "type": "object" + }, + "IrohEndpointOwner": { + "description": "What holds this endpoint id", + "example": { + "device_identifier": "example_device", + "type": "device", + "user_email": null, + "user_name": null + }, + "properties": { + "device_identifier": { + "description": "Set when `type` is `device`", + "nullable": true, + "type": "string" + }, + "type": { + "description": "`device` for one a device proved, `user` for a member's own machine, `none` for one the organization holds directly", + "enum": [ + "device", + "user", + "none" + ], + "type": "string" + }, + "user_email": { + "description": "Set when `type` is `user`", + "nullable": true, + "type": "string" + }, + "user_name": { + "description": "Set when `type` is `user`", + "nullable": true, + "type": "string" + } + }, + "title": "IrohEndpointOwner", + "type": "object" + }, + "IrohEndpointShowResponse": { + "description": "Iroh Endpoint show response", + "example": { + "data": { + "details": {}, + "identifier": "c8924b6c9b7a8528b1365ebec4b2e43b6edebef684f8521f12b8caaf6e1b2302", + "inserted_at": "2026-08-14T11:02:31Z", + "instance": "default", + "last_reported_at": "2026-08-16T09:14:00Z", + "owner": { + "device_identifier": "example_device", + "type": "device", + "user_email": null, + "user_name": null + }, + "service": "iroh", + "source": "device_reported", + "updated_at": "2026-08-16T09:14:00Z" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/IrohEndpoint" + } + }, + "title": "IrohEndpointShowResponse", + "type": "object" + }, + "NetworkIdentity": { + "description": "A key a device holds on a network NervesHub does not run", + "example": { + "details": { + "relay": "https://relay.example.com" + }, + "identifier": "c8924b6c9b7a8528b1365ebec4b2e43b6edebef684f8521f12b8caaf6e1b2302", + "inserted_at": "2026-08-14T11:02:31Z", + "instance": "default", + "last_reported_at": "2026-08-16T09:14:00Z", + "service": "iroh", + "source": "device_reported", + "updated_at": "2026-08-16T09:14:00Z" + }, + "properties": { + "details": { + "additionalProperties": true, + "description": "Per-service and non-authoritative — relay urls, an assigned address. Never a secret", + "type": "object" + }, + "identifier": { + "description": "The value possession of which is proven — a public key, not a handle that can be reassigned", + "type": "string" + }, + "inserted_at": { + "format": "date-time", + "type": "string" + }, + "instance": { + "description": "Which endpoint of that protocol. `default` for anything running a single one", + "type": "string" + }, + "last_reported_at": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "service": { + "description": "The protocol", + "enum": [ + "iroh", + "netbird", + "tailscale", + "wireguard" + ], + "type": "string" + }, + "source": { + "description": "Whether anything proved this key. `device_reported` means a device did; `operator` means it was typed in", + "enum": [ + "device_reported", + "operator" + ], + "type": "string" + }, + "updated_at": { + "format": "date-time", + "type": "string" + } + }, + "title": "NetworkIdentity", + "type": "object" + }, + "NetworkIdentityListResponse": { + "description": "Network Identity list response", + "example": { + "data": [ + { + "details": { + "relay": "https://relay.example.com" + }, + "identifier": "c8924b6c9b7a8528b1365ebec4b2e43b6edebef684f8521f12b8caaf6e1b2302", + "inserted_at": "2026-08-14T11:02:31Z", + "instance": "default", + "last_reported_at": "2026-08-16T09:14:00Z", + "service": "iroh", + "source": "device_reported", + "updated_at": "2026-08-16T09:14:00Z" + }, + { + "details": {}, + "identifier": "5f691e39f55415be337b2e4cc0dd7291586ab7c4356bf32bab60f46fc78f95d5", + "inserted_at": "2026-08-14T11:02:31Z", + "instance": "console", + "last_reported_at": "2026-08-16T09:14:00Z", + "service": "iroh", + "source": "device_reported", + "updated_at": "2026-08-16T09:14:00Z" + } + ] + }, + "properties": { + "data": { + "description": "The identities this device holds", + "items": { + "$ref": "#/components/schemas/NetworkIdentity" + }, + "type": "array" + } + }, + "title": "NetworkIdentityListResponse", + "type": "object" + }, + "Org": { + "example": { + "inserted_at": "2024-01-01T00:00:00Z", + "name": "example_org", + "products": [ + { + "name": "MyProduct" + } + ], + "updated_at": "2024-01-01T00:00:00Z" + }, + "properties": { + "inserted_at": { + "format": "date-time", + "type": "string" + }, + "name": { + "type": "string" + }, + "products": { + "description": "Included when requested via ?include=products", + "items": { + "$ref": "#/components/schemas/Product" + }, + "nullable": true, + "type": "array" + }, + "updated_at": { + "format": "date-time", + "type": "string" + } + }, + "title": "Org", + "type": "object" + }, + "OrgListResponse": { + "description": "Response schema for multiple Organizations", + "example": { + "data": [ + { + "inserted_at": "2024-01-01T00:00:00Z", + "name": "example_org", + "updated_at": "2024-01-01T00:00:00Z" + } + ] + }, + "properties": { + "data": { + "description": "The Organizations", + "items": { + "$ref": "#/components/schemas/Org" + }, + "type": "array" + } + }, + "title": "OrgListResponse", + "type": "object" + }, + "OrgUser": { + "example": { + "email": "jane@person.com", + "name": "Jane Person", + "role": "admin" + }, + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "role": { + "enum": [ + "admin", + "manage", + "view" + ], + "type": "string" + } + }, + "title": "OrgUser", + "type": "object" + }, + "OrgUserCreationRequest": { + "description": "POST body for adding or inviting a user to an organization", + "example": { + "email": "jane@person.com", + "role": "manage" + }, + "properties": { + "email": { + "type": "string" + }, + "role": { + "enum": [ + "admin", + "manage", + "view" + ], + "type": "string" + } + }, + "required": [ + "email", + "role" + ], + "title": "OrgUserCreationRequest", + "type": "object" + }, + "OrgUserListResponse": { + "description": "Response schema for multiple Organization Users", + "example": { + "data": [ + { + "email": "jane@person.com", + "name": "Jane Person", + "role": "admin" + }, + { + "email": "jane@person.com", + "name": "Jane Person", + "role": "view" + } + ] + }, + "properties": { + "data": { + "description": "The Organization Users details", + "items": { + "$ref": "#/components/schemas/OrgUser" + }, + "type": "array" + } + }, + "title": "OrgUserListResponse", + "type": "object" + }, + "OrgUserShowResponse": { + "description": "Response schema for a single Organization User", + "example": { + "data": { + "email": "jane@person.com", + "name": "Jane Person", + "role": "admin" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/OrgUser" + } + }, + "title": "OrgUserShowResponse", + "type": "object" + }, + "OrgUserUpdateRequest": { + "description": "POST body for updating a users organization membership", + "example": { + "role": "manage" + }, + "properties": { + "role": { + "enum": [ + "admin", + "manage", + "view" + ], + "type": "string" + } + }, + "required": [ + "role" + ], + "title": "OrgUserUpdateRequest", + "type": "object" + }, + "Product": { + "example": { + "allow_unsigned_atomvm_firmware": false, + "allow_unsigned_esp_idf_firmware": false, + "allowed_update_tools": [ + "fwup" + ], + "name": "Example Product", + "require_unique_firmware_version": true + }, + "properties": { + "allow_unsigned_atomvm_firmware": { + "description": "Accept AtomVM packbeam archives that carry no signature entry.\n\nNothing in the AtomVM toolchain signs by default, so a product built\nwithout `nh-avm` has no way to sign yet.\n\nThis excuses a missing signature and nothing else: an archive that\ndoes carry one is always verified against the organization's\nregistered signing keys.\n", + "type": "boolean" + }, + "allow_unsigned_esp_idf_firmware": { + "description": "Accept ESP-IDF images that carry no Secure Boot v2 signature block.\n\nThis excuses a missing signature and nothing else: an image that does\ncarry a signature is always verified against the organization's\nregistered signing keys.\n", + "type": "boolean" + }, + "allowed_update_tools": { + "description": "The firmware formats this product accepts. `fwup` is always present.\n\nA format also has to be enabled for the instance before a product can\nlist it — see the ESP-IDF support documentation.\n", + "items": { + "enum": [ + "fwup", + "esp-idf", + "atomvm" + ], + "type": "string" + }, + "type": "array" + }, + "name": { + "pattern": "[a-zA-Z][a-zA-Z0-9_]+", + "type": "string" + }, + "require_unique_firmware_version": { + "description": "Reject uploaded firmware whose version already exists for this product's platform and architecture.", + "type": "boolean" + } + }, + "title": "Product", + "type": "object" + }, + "ProductCreationRequest": { + "description": "POST body for creating a product", + "example": { + "product": { + "name": "ExampleProduct" + } + }, + "properties": { + "product": { + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "product" + ], + "title": "ProductCreationRequest", + "type": "object" + }, + "ProductListResponse": { + "description": "Response schema for multiple products", + "example": { + "data": [ + { + "allow_unsigned_atomvm_firmware": false, + "allow_unsigned_esp_idf_firmware": false, + "allowed_update_tools": [ + "fwup" + ], + "name": "Example Product", + "require_unique_firmware_version": true + }, + { + "allow_unsigned_esp_idf_firmware": true, + "allowed_update_tools": [ + "fwup", + "esp-idf" + ], + "name": "Another Example Product", + "require_unique_firmware_version": true + } + ] + }, + "properties": { + "data": { + "description": "The products details", + "items": { + "$ref": "#/components/schemas/Product" + }, + "type": "array" + } + }, + "title": "ProductListResponse", + "type": "object" + }, + "ProductShowResponse": { + "description": "Response schema for a single Product", + "example": { + "data": { + "allow_unsigned_atomvm_firmware": false, + "allow_unsigned_esp_idf_firmware": false, + "allowed_update_tools": [ + "fwup" + ], + "name": "Example Product", + "require_unique_firmware_version": true + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/Product" + } + }, + "title": "ProductShowResponse", + "type": "object" + }, + "ProductUpdateRequest": { + "description": "PUT body for updating a product's settings.\n\nEvery field is optional; only the ones sent are changed. A product cannot\nbe renamed here — its name is its identifier in every URL — and sending\nany field not listed below is an error rather than being ignored.\n", + "example": { + "allow_unsigned_atomvm_firmware": true, + "allowed_update_tools": [ + "fwup", + "atomvm" + ] + }, + "properties": { + "allow_unsigned_atomvm_firmware": { + "type": "boolean" + }, + "allow_unsigned_esp_idf_firmware": { + "type": "boolean" + }, + "allowed_update_tools": { + "items": { + "enum": [ + "fwup", + "esp-idf", + "atomvm" + ], + "type": "string" + }, + "type": "array" + }, + "require_unique_firmware_version": { + "type": "boolean" + } + }, + "title": "ProductUpdateRequest", + "type": "object" + }, + "SigningKey": { + "example": { + "key": "abc123=", + "name": "CI", + "scheme": "ed25519" + }, + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "scheme": { + "description": "Signature scheme this key belongs to", + "enum": [ + "ed25519", + "secure_boot_v2_rsa" + ], + "type": "string" + } + }, + "title": "SigningKey", + "type": "object" + }, + "SigningKeyCreationRequest": { + "description": "POST body for adding a Signing Key to an Organization.\n\n`scheme` defaults to `ed25519` when omitted, which is what every key was\nbefore ESP-IDF support — so existing clients keep working unchanged.\n", + "example": { + "key": "-----BEGIN PUBLIC KEY-----\nMIIBoj...\n-----END PUBLIC KEY-----\n", + "name": "ESP release", + "scheme": "secure_boot_v2_rsa" + }, + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "scheme": { + "description": "Signature scheme this key belongs to", + "enum": [ + "ed25519", + "secure_boot_v2_rsa" + ], + "type": "string" + } + }, + "required": [ + "name", + "key" + ], + "title": "SigningKeyCreationRequest", + "type": "object" + }, + "SigningKeyIndexResponse": { + "description": "Response schema for multiple Signing Keys", + "example": { + "data": [ + { + "key": "abc123=", + "name": "QA", + "scheme": "ed25519" + }, + { + "key": "-----BEGIN PUBLIC KEY-----\nMIIBoj...\n-----END PUBLIC KEY-----\n", + "name": "ESP release", + "scheme": "secure_boot_v2_rsa" + } + ] + }, + "properties": { + "data": { + "description": "The Signing Key details", + "items": { + "$ref": "#/components/schemas/SigningKey" + }, + "type": "array" + } + }, + "title": "SigningKeyIndexResponse", + "type": "object" + }, + "SigningKeyShowResponse": { + "description": "Response schema for a single Signing Key", + "example": { + "data": { + "key": "abc123=", + "name": "QA", + "scheme": "ed25519" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/SigningKey" + } + }, + "title": "SigningKeyShowResponse", + "type": "object" + }, + "SupportScript": { + "example": { + "created_by": { + "email": "waffles@doggo.com", + "id": "1", + "name": "Waffles t'Doggo" + }, + "id": "1", + "inserted_at": "2026-03-28T08:10:20Z", + "name": "Clean Disk", + "tags": "cleanup", + "text": "Clean.disk()", + "updated_at": "2026-06-23T08:10:20Z" + }, + "properties": { + "created_by": { + "properties": { + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "id": { + "type": "string" + }, + "inserted_at": { + "format": "date-time", + "type": "string" + }, + "name": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "text": { + "type": "string" + }, + "updated_at": { + "format": "date-time", + "type": "string" + } + }, + "title": "SupportScript", + "type": "object" + }, + "SupportScriptCreationRequest": { + "description": "POST body for adding a Support Script to a Product", + "example": { + "name": "Clean Disk", + "tags": "cleanup", + "text": "Disk.clean()" + }, + "properties": { + "name": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "text": { + "type": "string" + } + }, + "required": [ + "name", + "text" + ], + "title": "SupportScriptCreationRequest", + "type": "object" + }, + "SupportScriptIndexResponse": { + "description": "Response schema for multiple Support Scripts", + "example": { + "data": [ + { + "id": "1", + "name": "Clean Disk", + "tags": "cleanup" + }, + { + "id": "2", + "name": "Dim the lights", + "tags": "lights" + } + ], + "pagination": { + "page_number": 1, + "page_size": 20, + "total_entries": 2, + "total_pages": 1 + } + }, + "properties": { + "data": { + "description": "The Support Script details", + "items": { + "$ref": "#/components/schemas/SupportScriptMinimal" + }, + "type": "array" + }, + "pagination": { + "properties": { + "page_number": { + "type": "integer" + }, + "page_size": { + "type": "integer" + }, + "total_entries": { + "type": "integer" + }, + "total_pages": { + "type": "integer" + } + }, + "type": "object" + } + }, + "title": "SupportScriptIndexResponse", + "type": "object" + }, + "SupportScriptMinimal": { + "example": { + "id": "1", + "name": "Clean Disk", + "tags": "cleanup" + }, + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tags": { + "type": "string" + } + }, + "title": "SupportScriptMinimal", + "type": "object" + }, + "SupportScriptShowResponse": { + "description": "Response schema for a single Support Script", + "example": { + "data": { + "created_by": { + "email": "waffles@doggo.com", + "id": "1", + "name": "Waffles t'Doggo" + }, + "id": "1", + "inserted_at": "2026-03-28T08:10:20Z", + "name": "Snoot Boop", + "tags": "snoots", + "text": "Snoot.boop()", + "updated_at": "2026-06-23T08:10:20Z" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/SupportScript" + } + }, + "title": "SupportScriptShowResponse", + "type": "object" + }, + "SupportScriptUpdateRequest": { + "description": "POST body for updating a Support Script", + "example": { + "name": "Clean Disk", + "tags": "cleanup", + "text": "Disk.clean()" + }, + "properties": { + "name": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "text": { + "type": "string" + } + }, + "title": "SupportScriptUpdateRequest", + "type": "object" + }, + "User": { + "description": "A registered user", + "example": { + "email": "jane@iot-company.com", + "id": 123, + "inserted_at": "2017-09-12T12:34:55Z", + "name": "Jane User", + "updated_at": "2017-09-13T10:11:12Z" + }, + "properties": { + "email": { + "description": "Email address", + "format": "email", + "type": "string" + }, + "id": { + "description": "User ID", + "type": "integer" + }, + "inserted_at": { + "description": "Creation timestamp", + "format": "date-time", + "type": "string" + }, + "name": { + "description": "Users name", + "pattern": "[a-zA-Z][a-zA-Z0-9_]+", + "type": "string" + }, + "updated_at": { + "description": "Update timestamp", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "name", + "email" + ], + "title": "User", + "type": "object" + }, + "UserAuthCLISessionRequest": { + "description": "POST body for authenticating a user via CLI session", + "example": { + "note": "nerves_hub_cli 2.0.0" + }, + "properties": { + "note": { + "type": "string" + } + }, + "title": "UserAuthCLISessionRequest", + "type": "object" + }, + "UserAuthCLISessionResponse": { + "description": "Response for authenticating a new CLI session", + "example": { + "data": { + "token": "218ed524-eb74-47d7-aedc-11e386961b72", + "url": "https://manage.nervescloud.com/auth/cli/218ed524-eb74-47d7-aedc-11e386961b72" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/CLISession" + } + }, + "title": "UserAuthCLISessionResponse", + "type": "object" + }, + "UserAuthCLISessionStatusResponse": { + "description": "Response for checking the status of a CLI auth exchange session", + "example": { + "data": { + "status": "ready", + "user_token": "nhu_aaabbbccc123" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/CLISessionStatus" + } + }, + "title": "UserAuthCLISessionStatusResponse", + "type": "object" + }, + "UserAuthRequest": { + "description": "POST body for authenticating a user", + "example": { + "email": "jane@iot-company.com", + "note": "Local automation", + "password": "my-secure-password" + }, + "properties": { + "email": { + "type": "string" + }, + "note": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "email", + "password" + ], + "title": "UserAuthRequest", + "type": "object" + }, + "UserResponse": { + "description": "Response schema for single user", + "example": { + "data": { + "email": "jane@iot-company.com", + "id": 123, + "inserted_at": "2017-09-12T12:34:55Z", + "name": "Jane User", + "updated_at": "2017-09-13T10:11:12Z" + } + }, + "properties": { + "data": { + "$ref": "#/components/schemas/User" + } + }, + "title": "UserResponse", + "type": "object" + } + }, + "securitySchemes": { + "bearer_auth": { + "scheme": "bearer", + "type": "http" + } + } + }, + "info": { + "description": "The NervesCloud API gives users full access to their\nOrgs, Products, and corresponding Device fleets.\n\nThe API can be used to integrate with your own systems, providing full access to your Product and Device data.\n\nThe API is documented using the OpenAPI 3.0 specification.\n", + "title": "NervesCloud API", + "version": "2.0.0" + }, + "openapi": "3.0.0", + "paths": { + "/api/orgs/{org_name}/products/{product_name}": { + "delete": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ProductController.delete", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Empty response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Delete an Organizations Product", + "tags": [ + "Products" + ] + }, + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ProductController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductShowResponse" + } + } + }, + "description": "Product response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Retrieves a Products details", + "tags": [ + "Products" + ] + }, + "patch": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ProductController.update (2)", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductUpdateRequest" + } + } + }, + "description": "Product update request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductShowResponse" + } + } + }, + "description": "Product response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Update a Product's settings", + "tags": [ + "Products" + ] + }, + "put": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ProductController.update", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductUpdateRequest" + } + } + }, + "description": "Product update request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductShowResponse" + } + } + }, + "description": "Product response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Update a Product's settings", + "tags": [ + "Products" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/scripts": { + "get": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.ScriptController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Pagination", + "in": "query", + "name": "pagination", + "required": false, + "schema": { + "properties": { + "page": { + "default": 1, + "example": "5", + "type": "integer" + }, + "page_size": { + "default": 10, + "example": "20", + "type": "integer" + } + }, + "type": "object" + }, + "style": "deepObject" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportScriptIndexResponse" + } + } + }, + "description": "Support Scripts List Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List Support Scripts", + "tags": [ + "Support Scripts" + ] + }, + "post": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ScriptController.create", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportScriptCreationRequest" + } + } + }, + "description": "Support Script creation request body", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportScriptShowResponse" + } + } + }, + "description": "Support Scripts" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Create a Support Script", + "tags": [ + "Support Scripts" + ] + } + }, + "/api/orgs/{org_name}/iroh_endpoints/{identifier}": { + "delete": { + "callbacks": {}, + "description": "An endpoint a device reported is removed too, but the device records it\nagain the next time it connects — deleting one is not a way to stop a device\nholding a key it holds.\n", + "operationId": "NervesHubWeb.API.IrohEndpointController.delete", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The endpoint id — 64 hex characters", + "example": "c8924b6c9b7a8528b1365ebec4b2e43b6edebef684f8521f12b8caaf6e1b2302", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Empty response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Delete an Iroh Endpoint for an Organization", + "tags": [ + "Iroh Endpoints" + ] + }, + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.IrohEndpointController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "The endpoint id — 64 hex characters", + "example": "c8924b6c9b7a8528b1365ebec4b2e43b6edebef684f8521f12b8caaf6e1b2302", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IrohEndpointShowResponse" + } + } + }, + "description": "Iroh Endpoint" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Show an Iroh Endpoint for an Organization", + "tags": [ + "Iroh Endpoints" + ] + } + }, + "/api/orgs/{org_name}/ca_certificates": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.CACertificateController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CACertificateListResponse" + } + } + }, + "description": "CA Certificate list response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List all CA Certificates for an Organization", + "tags": [ + "CA Certificates" + ] + }, + "post": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.CACertificateController.create", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CACertificateCreationRequest" + } + } + }, + "description": "CA Certificate creation request body", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CACertificateShowResponse" + } + } + }, + "description": "CA Certificate response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Create a CA Certificate for an Organization", + "tags": [ + "CA Certificates" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/logs": { + "get": { + "callbacks": {}, + "deprecated": false, + "description": "The log lines this device sent over the logging extension, newest first.\n\nLines stay readable after the extension is turned off for the product or\nthe device — the setting governs what arrives, not what can be read. They\nare not kept forever, though: log lines are dropped three days after they\nwere logged.\n\nTo page back through history, ask for a `limit` and then pass the\n`timestamp` of the oldest line you received as the next request's\n`before`.\n", + "operationId": "NervesHubWeb.API.DeviceLogController.index.long", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Only lines logged at these levels. Comma separated for more than one. Matched as given — a level no device has logged at matches nothing", + "example": "error,warning", + "in": "query", + "name": "level", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Only lines whose message contains this text, ignoring case. Matched literally, so `%` and `_` are searched for rather than treated as wildcards", + "example": "sensor bus", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Only lines logged at or after this ISO 8601 timestamp", + "example": "2026-08-16T09:00:00Z", + "in": "query", + "name": "since", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Only lines logged strictly before this ISO 8601 timestamp. To page back through history, pass the timestamp of the oldest line you received", + "example": "2026-08-16T09:14:00.123456Z", + "in": "query", + "name": "before", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "How many lines to return, from 1 to 1000", + "example": "250", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 100, + "type": "integer" + } + }, + { + "description": "`desc` for newest first, `asc` for oldest first", + "example": "asc", + "in": "query", + "name": "order", + "required": false, + "schema": { + "default": "desc", + "enum": [ + "desc", + "asc" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceLogListResponse" + } + } + }, + "description": "Device Log List Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unusable query parameter" + }, + "501": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "This platform has no analytics database, so no logs are stored" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List the Log Lines a Device has sent", + "tags": [ + "Device Logs" + ] + } + }, + "/api/orgs/{org_name}/products": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ProductController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductListResponse" + } + } + }, + "description": "Product list response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List all Products for an Organization", + "tags": [ + "Products" + ] + }, + "post": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ProductController.create", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductCreationRequest" + } + } + }, + "description": "Product creation request body", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProductShowResponse" + } + } + }, + "description": "Product response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Create a new Product in an Organization", + "tags": [ + "Products" + ] + } + }, + "/api/users/login": { + "post": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.UserController.login", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserAuthRequest" + } + } + }, + "description": "Authentication attributes", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + }, + "description": "User response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + } + }, + "security": [], + "summary": "Authenticate a user (deprecated)", + "tags": [ + "Auth" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/certificates/{serial}": { + "delete": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.DeviceCertificateController.delete", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "example_device", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Certificate Serial", + "example": "123456789101112", + "in": "path", + "name": "serial", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Empty response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Delete a Device's Certificate", + "tags": [ + "Device Certificates" + ] + }, + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.DeviceCertificateController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "example_device", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Certificate Serial", + "example": "123456789101112", + "in": "path", + "name": "serial", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceCertificateShowResponse" + } + } + }, + "description": "Device Certificate show response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Show a Certificate for a Device", + "tags": [ + "Device Certificates" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/upgrade": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.upgrade", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceUpgradeRequest" + } + } + }, + "description": "Firmware upgrade request body", + "required": true + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Request a Device upgrade to a different Firmware", + "tags": [ + "Devices" + ] + } + }, + "/api/devices/{identifier}/reconnect": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.reconnect", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Request a Device reconnect", + "tags": [ + "Devices (short URL)" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/reboot": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.reboot", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Request a Device reboot", + "tags": [ + "Devices" + ] + } + }, + "/api/devices/{identifier}/logs": { + "get": { + "callbacks": {}, + "deprecated": false, + "description": "The log lines this device sent over the logging extension, newest first.\n\nLines stay readable after the extension is turned off for the product or\nthe device — the setting governs what arrives, not what can be read. They\nare not kept forever, though: log lines are dropped three days after they\nwere logged.\n\nTo page back through history, ask for a `limit` and then pass the\n`timestamp` of the oldest line you received as the next request's\n`before`.\n", + "operationId": "NervesHubWeb.API.DeviceLogController.index.short", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Only lines logged at these levels. Comma separated for more than one. Matched as given — a level no device has logged at matches nothing", + "example": "error,warning", + "in": "query", + "name": "level", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Only lines whose message contains this text, ignoring case. Matched literally, so `%` and `_` are searched for rather than treated as wildcards", + "example": "sensor bus", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Only lines logged at or after this ISO 8601 timestamp", + "example": "2026-08-16T09:00:00Z", + "in": "query", + "name": "since", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "Only lines logged strictly before this ISO 8601 timestamp. To page back through history, pass the timestamp of the oldest line you received", + "example": "2026-08-16T09:14:00.123456Z", + "in": "query", + "name": "before", + "required": false, + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "description": "How many lines to return, from 1 to 1000", + "example": "250", + "in": "query", + "name": "limit", + "required": false, + "schema": { + "default": 100, + "type": "integer" + } + }, + { + "description": "`desc` for newest first, `asc` for oldest first", + "example": "asc", + "in": "query", + "name": "order", + "required": false, + "schema": { + "default": "desc", + "enum": [ + "desc", + "asc" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceLogListResponse" + } + } + }, + "description": "Device Log List Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unusable query parameter" + }, + "501": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "This platform has no analytics database, so no logs are stored" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List the Log Lines a Device has sent", + "tags": [ + "Devices (short URL)" + ] + } + }, + "/api/auth/cli_session/{token}": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.UserController.check_cli_session", + "parameters": [ + { + "description": "CLI Session Token", + "example": "abc123token", + "in": "path", + "name": "token", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserAuthCLISessionStatusResponse" + } + } + }, + "description": "Auth CLI session token response" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [], + "summary": "Check the CLI authentication progress", + "tags": [ + "Auth" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/penalty": { + "delete": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.penalty", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Clear the penalty box for a Device", + "tags": [ + "Devices" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/import": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.bulk_import", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceBulkImport" + } + } + }, + "description": "Certificate manifest", + "required": true + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Bulk create Devices from a manifest. Only a Microchip Trust and Go manifest is currently supported.", + "tags": [ + "Devices" + ] + } + }, + "/api/auth/cli_session": { + "post": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.UserController.cli_session", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserAuthCLISessionRequest" + } + } + }, + "description": "CLI Session attributes", + "required": false + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserAuthCLISessionResponse" + } + } + }, + "description": "Auth CLI session token response" + } + }, + "security": [], + "summary": "Start the CLI authentication process", + "tags": [ + "Auth" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/firmwares/{uuid}": { + "delete": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.FirmwareController.delete", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Firmware UUID", + "example": "d9f8c63a-1234-5678-abcd-ef0123456789", + "in": "path", + "name": "uuid", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Empty response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Delete a Firmware", + "tags": [ + "Firmwares" + ] + }, + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.FirmwareController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Firmware UUID", + "example": "d9f8c63a-1234-5678-abcd-ef0123456789", + "in": "path", + "name": "uuid", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FirmwareResponse" + } + } + }, + "description": "Firmware response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Show a Firmware", + "tags": [ + "Firmwares" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/code": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.code", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceCodeRequest" + } + } + }, + "description": "Code execution request body", + "required": true + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Request a Device run some Elixir code in it's console connection", + "tags": [ + "Devices" + ] + } + }, + "/api/devices/{identifier}/code": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.code", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceCodeRequest" + } + } + }, + "description": "Code execution request body", + "required": true + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Request a Device run some Elixir code in it's console connection", + "tags": [ + "Devices (short URL)" + ] + } + }, + "/api/devices/{identifier}/scripts/{name_or_id}": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.ScriptController.send", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Support Script Name or ID. Name takes priority.", + "example": "my-script", + "in": "path", + "name": "name_or_id", + "required": true, + "schema": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + } + }, + { + "description": "How long to wait for a device response in milliseconds", + "example": "10000", + "in": "query", + "name": "timeout", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + }, + "description": "Script output" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Send a Support Script to a Device", + "tags": [ + "Devices (short URL)" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/certificates": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.DeviceCertificateController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "example_device", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceCertificateListResponse" + } + } + }, + "description": "Device Certificate list response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List all Certificates for a Device", + "tags": [ + "Device Certificates" + ] + }, + "post": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.DeviceCertificateController.create", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "example_device", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceCertificateCreateRequest" + } + } + }, + "description": "Device Certificate create request", + "required": false + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceCertificateShowResponse" + } + } + }, + "description": "Device Certificate show response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Create a Certificate for a Device", + "tags": [ + "Device Certificates" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices": { + "get": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Sort By", + "example": "identifier", + "in": "query", + "name": "sort", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Sort Direction", + "example": "asc", + "in": "query", + "name": "sort_direction", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Pagination", + "in": "query", + "name": "pagination", + "required": false, + "schema": { + "properties": { + "page": { + "default": 1, + "example": "5", + "type": "integer" + }, + "page_size": { + "default": 10, + "example": "20", + "type": "integer" + } + }, + "type": "object" + }, + "style": "deepObject" + }, + { + "description": "Filtering", + "in": "query", + "name": "filters", + "required": false, + "schema": { + "properties": { + "advanced_query": { + "description": "A query in the device list's advanced query language, the same one the\nweb UI's search bar uses. Values are quoted; expressions combine with\n`and`/`or`/`not` and parentheses. Custom health metrics are queried as\n`metric:` with numeric comparisons against each device's latest\nreading. Input that doesn't look like a query expression is treated as\na free-text search across the device's textual fields; an invalid\nquery expression is rejected with a 422 and the parse error.\n\nExamples:\n* `metric:cpu_temp > 70`\n* `connection = \"connected\" and tags contains \"prod\"`\n* `health_status != \"healthy\" or alarm_status = \"with\"`\n* `metric:battery_soc < 20 and updates = \"enabled\"`\n", + "example": "metric:cpu_temp > 70 and connection = \"connected\"", + "required": false, + "type": "string" + }, + "alarm": { + "example": "SomeAlarm", + "required": false, + "type": "string" + }, + "alarm_status": { + "enum": [ + "with", + "without" + ], + "required": false, + "type": "string" + }, + "connection": { + "enum": [ + "connected", + "disconnected", + "not_seen" + ], + "required": false, + "type": "string" + }, + "deployment_id": { + "example": "12", + "required": false, + "type": "string" + }, + "display_deleted": { + "enum": [ + "include", + "exclude", + "only" + ], + "required": false, + "type": "string" + }, + "firmware_version": { + "example": "1.10.0", + "required": false, + "type": "string" + }, + "has_no_tags": { + "enum": [ + "true", + "false" + ], + "required": false, + "type": "string" + }, + "health_status": { + "enum": [ + "healthy", + "unhealthy", + "warning", + "unknown" + ], + "required": false, + "type": "string" + }, + "identifier": { + "example": "sn123", + "required": false, + "type": "string" + }, + "only_updating": { + "enum": [ + "true", + "false" + ], + "required": false, + "type": "string" + }, + "platform": { + "example": "rpi4", + "required": false, + "type": "string" + }, + "search": { + "example": "sn123", + "required": false, + "type": "string" + }, + "tags": { + "example": "prod,staging", + "required": false, + "type": "string" + }, + "updates": { + "enum": [ + "enabled", + "disabled", + "automatic", + "device-managed", + "penalty-box" + ], + "required": false, + "type": "string" + } + }, + "type": "object" + }, + "style": "deepObject" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceListResponse" + } + } + }, + "description": "Device List Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List Devices", + "tags": [ + "Devices" + ] + } + }, + "/api/orgs/{org_name}/keys/{name}": { + "delete": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.KeyController.delete", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Signing Key Name", + "example": "example_key", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Empty response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Delete a Signing Key for an Organization", + "tags": [ + "Signing Keys" + ] + }, + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.KeyController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Signing Key Name", + "example": "example_key", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SigningKeyShowResponse" + } + } + }, + "description": "Signing Key" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Show a Signing Key for an Organization", + "tags": [ + "Signing Keys" + ] + } + }, + "/api/orgs/{org_name}/users": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.OrgUserController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgUserListResponse" + } + } + }, + "description": "Organization users list response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List all members of an Organization", + "tags": [ + "Organization Members" + ] + }, + "post": { + "callbacks": {}, + "deprecated": true, + "operationId": "NervesHubWeb.API.OrgUserController.add", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgUserCreationRequest" + } + } + }, + "description": "Org User creation request body", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgUserShowResponse" + } + } + }, + "description": "Organization User - User is added to the organization" + }, + "204": { + "description": "Empty response - User is invited to the organization" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Add a user to an Organization", + "tags": [ + "Organization Members" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/reconnect": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.reconnect", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Request a Device reconnect", + "tags": [ + "Devices" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}": { + "delete": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.delete", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Delete a Device", + "tags": [ + "Devices" + ] + }, + "get": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + }, + "description": "Device Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Show a Device", + "tags": [ + "Devices" + ] + }, + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.create", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceCreationRequest" + } + } + }, + "description": "Device creation request body", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + }, + "description": "Device Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Create a Device", + "tags": [ + "Devices" + ] + }, + "put": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.update", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceUpdateRequest" + } + } + }, + "description": "Device update request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + }, + "description": "Device Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Update a Device", + "tags": [ + "Devices" + ] + } + }, + "/api/users/me": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.UserController.me", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + }, + "description": "User response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Show details of the currently logged in user", + "tags": [ + "Auth" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/firmwares": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.FirmwareController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FirmwareListResponse" + } + } + }, + "description": "Firmware list response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List all Firmwares for a Product", + "tags": [ + "Firmwares" + ] + }, + "post": { + "callbacks": {}, + "description": "Accepts an fwup archive (`.fw`) or an ESP-IDF application image (`.bin`).\nThe format is determined from the file itself, not its name or extension.\n\nA product accepts only the formats listed in its `allowed_update_tools`,\nwhich is `[\"fwup\"]` by default. ESP-IDF additionally requires the platform\nto have `ESP_IDF_FIRMWARE_ENABLED` set.\n\n**Firmware must be signed** against a key registered to the organization: an\nEd25519 key for fwup and AtomVM, or an RSA-3072 Secure Boot v2 key for\nESP-IDF.\n\nTwo per-product settings excuse a *missing* signature, and nothing else.\n`allow_unsigned_esp_idf_firmware` accepts an ESP-IDF image with no signature\nblock, and `allow_unsigned_atomvm_firmware` accepts a packbeam with no\nsignature entry. Firmware that does carry a signature is always verified,\nand fwup archives are always verified.\n\nThe firmware's own metadata declares which product it belongs to, and that\nmust match the product in the path.\n", + "operationId": "NervesHubWeb.API.FirmwareController.create", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "multipart/form-data": {} + }, + "description": "Firmware file upload", + "required": false + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FirmwareResponse" + } + } + }, + "description": "Firmware response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FirmwareUploadErrorResponse" + } + } + }, + "description": "Upload rejected" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Upload a Firmware for a Product", + "tags": [ + "Firmwares" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/auth": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.auth", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceCertificateAuthRequest" + } + } + }, + "description": "Device certificate auth request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + }, + "description": "Device Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Test a Devices Certificate authentication", + "tags": [ + "Devices" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/move": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.move", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "New Organization Name", + "example": "new_example_org", + "in": "query", + "name": "new_org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "New Product Name", + "example": "new_example_product", + "in": "query", + "name": "new_product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + }, + "description": "Device Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Move a Device to a different Product", + "tags": [ + "Devices" + ] + } + }, + "/api/devices/{identifier}/upgrade": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.upgrade", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceUpgradeRequest" + } + } + }, + "description": "Firmware upgrade request body", + "required": true + }, + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Request a Device upgrade to a different Firmware", + "tags": [ + "Devices (short URL)" + ] + } + }, + "/api/devices/{identifier}/penalty": { + "delete": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.penalty", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Clear the penalty box for a Device", + "tags": [ + "Devices (short URL)" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/firmwares/{uuid}/download": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.FirmwareController.download", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Firmware UUID", + "example": "d9f8c63a-1234-5678-abcd-ef0123456789", + "in": "path", + "name": "uuid", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "302": { + "description": "Redirect to firmware download URL" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Download a Firmware", + "tags": [ + "Firmwares" + ] + } + }, + "/api/users/auth": { + "post": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.UserController.auth", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserAuthRequest" + } + } + }, + "description": "Authentication attributes", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + }, + "description": "User response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + } + }, + "security": [], + "summary": "Authenticate a user", + "tags": [ + "Auth" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/scripts/{id}": { + "delete": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ScriptController.delete", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Script ID", + "example": "123", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Empty response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Delete a Support Script by ID", + "tags": [ + "Support Scripts" + ] + }, + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ScriptController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Script ID", + "example": "123", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportScriptShowResponse" + } + } + }, + "description": "Support Scripts" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Get a Support Script by ID", + "tags": [ + "Support Scripts" + ] + }, + "put": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.ScriptController.update", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Support Script ID", + "example": "123", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportScriptUpdateRequest" + } + } + }, + "description": "Support Script update request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportScriptShowResponse" + } + } + }, + "description": "Support Scripts" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Update a Support Script", + "tags": [ + "Support Scripts" + ] + } + }, + "/api/orgs/{org_name}/users/{user_email}": { + "delete": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.OrgUserController.remove", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Email", + "example": "jane@person.com", + "in": "path", + "name": "user_email", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Empty response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Remove a user from an Organization", + "tags": [ + "Organization Members" + ] + }, + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.OrgUserController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Email", + "example": "jane@person.com", + "in": "path", + "name": "user_email", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgUserShowResponse" + } + } + }, + "description": "Organization User" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Show membership details of a user in an Organization", + "tags": [ + "Organization Members" + ] + }, + "put": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.OrgUserController.update", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "User Email", + "example": "jane@person.com", + "in": "path", + "name": "user_email", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgUserUpdateRequest" + } + } + }, + "description": "Org User update request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgUserShowResponse" + } + } + }, + "description": "Organization User" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Update a user's role in an Organization", + "tags": [ + "Organization Members" + ] + } + }, + "/api/orgs/{org_name}/users/invite": { + "post": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.OrgUserController.invite", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgUserCreationRequest" + } + } + }, + "description": "Org User creation request body", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgUserShowResponse" + } + } + }, + "description": "Organization User - User is added to the organization" + }, + "204": { + "description": "Empty response - User is invited to the organization" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Invite a user to the Organization", + "tags": [ + "Organization Members" + ] + } + }, + "/api/orgs/{org_name}/ca_certificates/{serial}": { + "delete": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.CACertificateController.delete", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "CA Certificate Serial", + "example": "5111552077003819958", + "in": "path", + "name": "serial", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Empty response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Delete an Organization's CA Certificate", + "tags": [ + "CA Certificates" + ] + }, + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.CACertificateController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "CA Certificate Serial", + "example": "5111552077003819958", + "in": "path", + "name": "serial", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CACertificateShowResponse" + } + } + }, + "description": "CA Certificate response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "View an Organization's CA Certificate", + "tags": [ + "CA Certificates" + ] + } + }, + "/api/devices/{identifier}/scripts": { + "get": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.scripts", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Pagination", + "in": "query", + "name": "pagination", + "required": false, + "schema": { + "properties": { + "page": { + "default": 1, + "example": "5", + "type": "integer" + }, + "page_size": { + "default": 10, + "example": "20", + "type": "integer" + } + }, + "type": "object" + }, + "style": "deepObject" + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SupportScriptIndexResponse" + } + } + }, + "description": "Support Scripts List Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List Support Scripts", + "tags": [ + "Devices (short URL)" + ] + } + }, + "/api/devices/{identifier}/reboot": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.reboot", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Request a Device reboot", + "tags": [ + "Devices (short URL)" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/scripts/{name_or_id}": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.ScriptController.send", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Support Script Name or ID. Name takes priority.", + "example": "my-script", + "in": "path", + "name": "name_or_id", + "required": true, + "schema": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + } + }, + { + "description": "How long to wait for a device response in milliseconds", + "example": "10000", + "in": "query", + "name": "timeout", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + }, + "description": "Script output" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Send a Support Script to a Device", + "tags": [ + "Devices" + ] + } + }, + "/api/devices/{identifier}/move": { + "post": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.move", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "New Organization Name", + "example": "new_example_org", + "in": "query", + "name": "new_org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "New Product Name", + "example": "new_example_product", + "in": "query", + "name": "new_product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + }, + "description": "Device Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Move a Device to a different Product", + "tags": [ + "Devices (short URL)" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/devices/{identifier}/network_identities": { + "get": { + "callbacks": {}, + "description": "The keys this device has reported holding on other networks — an iroh\nendpoint id, a NetBird, Tailscale or WireGuard public key.\n\n`service` is the protocol. `instance` names which endpoint of it, for a\ndevice running more than one — an iroh console and an iroh application, say.\nAnything running a single endpoint of a service uses `default`.\n", + "operationId": "NervesHubWeb.API.NetworkIdentityController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Device Identifier", + "example": "example_device", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Only identities for this protocol", + "example": "iroh", + "in": "query", + "name": "service", + "required": false, + "schema": { + "type": "string" + } + }, + { + "description": "Only this endpoint of the protocol", + "example": "default", + "in": "query", + "name": "instance", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkIdentityListResponse" + } + } + }, + "description": "Network Identity list response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unknown service" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List the Network Identities a Device holds", + "tags": [ + "Network Identities" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/deployments/{name}": { + "delete": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.DeploymentGroupController.delete", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Deployment Group Name", + "example": "production", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Empty response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Delete a Product's Deployment Group", + "tags": [ + "Deployment Groups" + ] + }, + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.DeploymentGroupController.show", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Deployment Group Name", + "example": "production", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeploymentGroupResponse" + } + } + }, + "description": "Deployment Group response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Show a Deployment Group", + "tags": [ + "Deployment Groups" + ] + }, + "put": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.DeploymentGroupController.update", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Deployment Group Name", + "example": "production", + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeploymentGroupUpdateRequest" + } + } + }, + "description": "Deployment Group update request body", + "required": true + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeploymentGroupResponse" + } + } + }, + "description": "Deployment Group response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Update a Deployment Group", + "tags": [ + "Deployment Groups" + ] + } + }, + "/api/orgs/{org_name}/iroh_endpoints": { + "get": { + "callbacks": {}, + "description": "Newest first.\n\n`search` matches the **start** of an endpoint id, and anywhere within a\ndevice identifier or a member's name. Prefix rather than substring on the\nkey because that is the half a caller has: logs and tables show a key\ntruncated, so the beginning is what can be copied out of one.\n", + "operationId": "NervesHubWeb.API.IrohEndpointController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Only endpoints held by this kind of owner, matching the `owner.type` in the response", + "example": "user", + "in": "query", + "name": "owner", + "required": false, + "schema": { + "enum": [ + "device", + "user", + "none" + ], + "type": "string" + } + }, + { + "description": "Match the start of an endpoint id, or part of a device identifier or member name", + "example": "c8924b6c", + "in": "query", + "name": "search", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IrohEndpointListResponse" + } + } + }, + "description": "Iroh Endpoints" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unknown owner" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List all Iroh Endpoints for an Organization", + "tags": [ + "Iroh Endpoints" + ] + }, + "post": { + "callbacks": {}, + "description": "Records an endpoint id nobody has proven yet, which is the point and also\nthe risk. A key already registered anywhere is refused, without saying\nwhere — whether another organization holds one is that organization's\nbusiness.\n\nA key registered here for a device in this organization is claimed by that\ndevice the next time it connects and proves it, so registering ahead of\nprovisioning needs no cleanup.\n", + "operationId": "NervesHubWeb.API.IrohEndpointController.create", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IrohEndpointCreateRequest" + } + } + }, + "description": "Iroh Endpoint registration request body", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IrohEndpointShowResponse" + } + } + }, + "description": "Iroh Endpoint" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "409": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Already registered" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Register an Iroh Endpoint for an Organization", + "tags": [ + "Iroh Endpoints" + ] + } + }, + "/status/alive": { + "get": { + "callbacks": {}, + "deprecated": false, + "description": "Provides a simple health check to verify that the application is running, responsive, and can connect to the database.", + "operationId": "Status.alive", + "parameters": [], + "responses": { + "200": { + "content": { + "text/plain": { + "schema": { + "example": "Hello, Friend!", + "type": "string" + } + } + }, + "description": "The application is running and the database is reachable." + }, + "500": { + "content": { + "text/plain": { + "schema": { + "example": "Sorry, Friend :(", + "type": "string" + } + } + }, + "description": "The application is running but the database is unreachable." + } + }, + "security": [], + "summary": "Check platform status", + "tags": [ + "Platform Status" + ] + } + }, + "/api/orgs": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.OrgController.index", + "parameters": [ + { + "description": "Comma-separated list of associations to include (e.g. \"products\")", + "in": "query", + "name": "include", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OrgListResponse" + } + } + }, + "description": "Organization list response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List all Organizations the authenticated user belongs to", + "tags": [ + "Organizations" + ] + } + }, + "/api/orgs/{org_name}/keys": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.KeyController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SigningKeyIndexResponse" + } + } + }, + "description": "Signing Keys" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List all Firmware and Archive Signing Keys for an Organization", + "tags": [ + "Signing Keys" + ] + }, + "post": { + "callbacks": {}, + "description": "The `scheme` decides how `key` is validated and what it can verify:\n\n * `ed25519` (the default) — an fwup signing key, base64 encoded, as\n produced by `fwup -g`. Verifies `.fw` archives.\n * `secure_boot_v2_rsa` — a PEM-encoded RSA-3072 public key, the public\n half of an `espsecure.py` Secure Boot v2 signing key. Verifies ESP-IDF\n `.bin` images.\n\nA key is only ever a candidate for firmware of its own scheme.\n", + "operationId": "NervesHubWeb.API.KeyController.create", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SigningKeyCreationRequest" + } + } + }, + "description": "Signing Key creation request body", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SigningKeyShowResponse" + } + } + }, + "description": "Signing Key" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Create a new Signing Key for an Organization", + "tags": [ + "Signing Keys" + ] + } + }, + "/api/orgs/{org_name}/ca_certificates/verification_token": { + "get": { + "callbacks": {}, + "description": "Used to generate a short lived token for use during the creation of a CA Certificate.", + "operationId": "NervesHubWeb.API.CACertificateController.verification_token", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CACertificateVerificationTokenResponse" + } + } + }, + "description": "CA Certificate verification token response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Generate a token for verifying the ownership of a CA Certificate", + "tags": [ + "CA Certificates" + ] + } + }, + "/api/orgs/{org_name}/products/{product_name}/deployments": { + "get": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.DeploymentGroupController.index", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeploymentGroupListResponse" + } + } + }, + "description": "Deployment Group list response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "List all Deployment Groups for a Product", + "tags": [ + "Deployment Groups" + ] + }, + "post": { + "callbacks": {}, + "operationId": "NervesHubWeb.API.DeploymentGroupController.create", + "parameters": [ + { + "description": "Organization Name", + "example": "example_org", + "in": "path", + "name": "org_name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "description": "Product Name", + "example": "example_product", + "in": "path", + "name": "product_name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeploymentGroupCreationRequest" + } + } + }, + "description": "Deployment Group creation request body", + "required": true + }, + "responses": { + "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeploymentGroupResponse" + } + } + }, + "description": "Deployment Group response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChangesetErrorResponse" + } + } + }, + "description": "Unprocessable Entity" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Create a new Deployment Group for a Product", + "tags": [ + "Deployment Groups" + ] + } + }, + "/api/devices/{identifier}": { + "get": { + "callbacks": {}, + "deprecated": false, + "operationId": "NervesHubWeb.API.DevicesController.show", + "parameters": [ + { + "description": "Device Identifier", + "example": "abc123", + "in": "path", + "name": "identifier", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceResponse" + } + } + }, + "description": "Device Response" + }, + "401": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Unauthorized" + }, + "403": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Forbidden" + }, + "404": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + }, + "description": "Not Found" + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "summary": "Show a Device", + "tags": [ + "Devices (short URL)" + ] + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ], + "servers": [ + { + "url": "https://manage.nervescloud.com", + "variables": {} + } + ], + "tags": [ + { + "description": "User authentication and API token creation", + "name": "Auth" + }, + { + "description": "Organization Certificate Authority management", + "name": "CA Certificates" + }, + { + "description": "Device management, including action requests eg. upgrade, reboot, reconnect", + "name": "Devices" + }, + { + "description": "Log lines Devices have sent over the logging extension", + "name": "Device Logs" + }, + { + "description": "Device management, including action requests eg. upgrade, reboot, reconnect", + "name": "Devices (short URL)" + }, + { + "description": "Device Certificate management", + "name": "Device Certificates" + }, + { + "description": "Identities a Device holds on networks NervesHub does not run", + "name": "Network Identities" + }, + { + "description": "Deployment Group and release management", + "name": "Deployment Groups" + }, + { + "description": "Firmware uploading and management", + "name": "Firmwares" + }, + { + "description": "Organization iroh endpoint id registration", + "name": "Iroh Endpoints" + }, + { + "description": "Organization management", + "name": "Organizations" + }, + { + "description": "Organization User membership management", + "name": "Organization Members" + }, + { + "description": "Product management", + "name": "Products" + }, + { + "description": "Organization Signing Key management", + "name": "Signing Keys" + }, + { + "description": "Product Support Script management", + "name": "Support Scripts" + }, + { + "description": "Platform healthcheck", + "name": "Platform Status" + } + ] +} From 6b5b1996dc61fad4b69c424b1467937a4d981fd7 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sat, 5 Sep 2026 18:34:46 +1200 Subject: [PATCH 3/3] Move the openapi reference from the group to the tab The build reported "Failed to fetch OpenAPI file for anchor or tab" and "Fetched 0 OpenApi file(s)" with the spec on a group, both when it was a remote URL and after it was checked in, so the file was never the problem. Group-level openapi appears not to be picked up by this deployment. Co-Authored-By: Claude Opus 5 --- docs.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs.json b/docs.json index 8793501..ee0677e 100644 --- a/docs.json +++ b/docs.json @@ -91,6 +91,7 @@ }, { "tab": "API Reference", + "openapi": "openapi.json", "groups": [ { "group": "HTTP API", @@ -99,10 +100,6 @@ "api/authentication" ] }, - { - "group": "Endpoints", - "openapi": "openapi.json" - }, { "group": "Device WebSocket", "pages": [