# Screens

Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none.

## GET /v1/screens

List screens

List the screens in your workspace, including each screen's organization node, assigned content, online status, and player settings. Pass `limit` (and optionally `cursor`) to page through results; without `limit`, the full list is returned. Results are limited to the organization nodes you can see.

**Notes.**
- The `state` object is a lean subset here (connection, currentContent, cache, proof, sync, telemetry.identity/display); GET /v1/screens/{id} returns the full snapshot.
- `nextCursor` is a screen id (keyset by id), not the base64 cursor the shared resource routes use.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size (max 500). Omit to get every screen. |
| `cursor` | query | string | no | The `nextCursor` of the previous page (a screen id). |

```bash
curl "https://api.brixsignage.com/v1/screens" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of Screen |  |
| `data[].id` | string | Screen id. |
| `data[].spaceId` | string | Workspace id. |
| `data[].nodeId` | string \| null | Location (org node) the screen belongs to; null = workspace root. |
| `data[].name` | string |  |
| `data[].status` | "online" \| "offline" \| "pairing" | `pairing` = no device has checked in yet. |
| `data[].lastSeenAt` | string \| null | Last heartbeat or socket ping reply, whichever is later. |
| `data[].coreUpdateOfferedAt` | string \| null |  |
| `data[].contentKind` | "playlist" \| "schedule" \| "layout" \| "creative" \| "app" \| "media" \| "signage" \| null | What is assigned. Null = nothing assigned. |
| `data[].contentId` | string \| null |  |
| `data[].contentFit` | null | Retired; always null. Fit is set on the content. |
| `data[].deactivatedAt` | string \| null | Set while an operator has deactivated the screen. |
| `data[].tags` | array of string |  |
| `data[].rotation` | integer | 0, 90, 180 or 270 degrees. |
| `data[].rotationCommandedAt` | string \| null |  |
| `data[].timezone` | string \| null | IANA time zone; null = inherited. |
| `data[].timezoneSource` | "operator" \| "device" \| null |  |
| `data[].operatingHours` | string | `default`, or a JSON-encoded weekly window. |
| `data[].lastPanelCommand` | string \| null |  |
| `data[].scheduleScreensOff` | boolean |  |
| `data[].displayPowerMode` | "always-on" \| "follow-schedule" \| "os-default" |  |
| `data[].playbackMode` | "sync" \| "unsync" \| "device-time" |  |
| `data[].locationLabel` | string \| null |  |
| `data[].locationLat` | number \| null |  |
| `data[].locationLng` | number \| null |  |
| `data[].ipCity` | string \| null |  |
| `data[].ipRegion` | string \| null |  |
| `data[].ipCountry` | string \| null |  |
| `data[].ipLat` | number \| null |  |
| `data[].ipLng` | number \| null |  |
| `data[].ipTimezone` | string \| null |  |
| `data[].ipGeoAt` | string \| null |  |
| `data[].playerSettings` | string \| null | Player settings as a JSON-ENCODED string (not an object). |
| `data[].kioskEnabled` | boolean |  |
| `data[].kioskPinMode` | "workspace" \| "custom" |  |
| `data[].kioskGraceSeconds` | integer |  |
| `data[].activeEmergencyId` | string \| null | The live emergency overriding this screen, if any. |
| `data[].activeCastId` | string \| null | The live cast overriding this screen, if any. |
| `data[].playerVersionHold` | string \| null |  |
| `data[].sealed` | boolean |  |
| `data[].sealedAt` | string \| null |  |
| `data[].powerPolicyId` | string \| null |  |
| `data[].importSourceId` | string \| null |  |
| `data[].billingGroupId` | string \| null |  |
| `data[].customFields` | string \| null | Custom fields as a JSON-ENCODED string. |
| `data[].lanSecretAt` | string \| null | When the local-trigger key was last set or rotated. The key itself is never returned. |
| `data[].createdAt` | string | ISO-8601 timestamp (UTC). |
| `data[].updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data[].deletedAt` | string \| null |  |
| `data[].location` | object \| null |  |
| `data[].deviceClaimed` | boolean | Status is `pairing` but a device has claimed it and not yet checked in. |
| `data[].nodeName` | string \| null |  |
| `data[].groupIds` | array of string | Screen groups this screen is in. |
| `data[].contentName` | string \| null |  |
| `data[].contentSharedFrom` | string \| null | Name of the workspace that shared the assigned content in; null when it is this workspace's own. |
| `data[].thumbnailUrl` | string \| null | Preview of the assigned content. |
| `data[].contentAppKey` | string \| null |  |
| `data[].contentMediaKind` | string \| null |  |
| `data[].contentState` | object \| null | Assigned media that is not playable yet (processing, failed, needs_action); null otherwise. |
| `data[].contentOrientation` | "portrait" \| "landscape" \| "adaptive" \| null |  |
| `data[].liveThumbnailAt` | string \| null | Capture time of the latest live frame (GET /v1/screens/{id}/live-thumbnail). |
| `data[].liveThumbnailFreshAt` | string \| null |  |
| `data[].currentContentKind` | "image" \| "video" \| "stream" \| null |  |
| `data[].currentContentPosterUrl` | string \| null |  |
| `data[].emergencyHeadline` | string \| null |  |
| `data[].castHeadline` | string \| null |  |
| `data[].castStartedAt` | string \| null |  |
| `data[].castExpiresAt` | string \| null |  |
| `data[].castContentKind` | string \| null |  |
| `data[].castContentId` | string \| null |  |
| `data[].kioskHasCustomPin` | boolean |  |
| `data[].kioskHasRecovery` | boolean |  |
| `data[].obscuredSince` | string \| null | A system dialog is covering the screen since this time. |
| `data[].pixelHealth` | "ok" \| "frozen" \| "blank" \| null |  |
| `data[].displayOffSince` | string \| null |  |
| `data[].displayOffReason` | "standby" \| "wrong-input" \| "disconnected" \| null |  |
| `data[].expectedDark` | object |  |
| `data[].expectedDark.dark` | boolean | True when the rules say the panel should be off now. |
| `data[].expectedDark.reason` | "schedule-block" \| "power-policy" \| "operating-hours" \| null |  |
| `data[].expectedDark.nextOpen` | object \| null |  |
| `data[].expectedDark.minutesSinceOpen` | number \| null |  |
| `data[].expectedDark.minutesSinceClose` | number \| null |  |
| `data[].accountHold` | object \| null |  |
| `data[].outageSummary30d` | object \| null |  |
| `data[].linkFlaps10m` | integer | Socket link flips in the last ten minutes; present only when > 0 (list and get). |
| `data[].state` | object \| null | The device's last reported state (`updatedAt` plus the heartbeat snapshot). The list returns a lean subset; get and PATCH return it all. Null before the first heartbeat. |
| `nextCursor` | string \| null | Present only when `limit` was passed; null on the last page. |

```json
{
  "data": [
    {
      "id": "scr_1a2b3c4d5e6f7a8b",
      "spaceId": "space_1a2b3c4d5e6f7a8b",
      "nodeId": "node_4c5d6e7f8a9b0c1d",
      "name": "Lobby",
      "status": "online",
      "lastSeenAt": "2026-09-28T11:58:00.000Z",
      "coreUpdateOfferedAt": null,
      "contentKind": "playlist",
      "contentId": "pl_9a8b7c6d5e4f3a2b",
      "contentFit": null,
      "deactivatedAt": null,
      "tags": [
        "lobby"
      ],
      "rotation": 0,
      "rotationCommandedAt": null,
      "timezone": "Europe/London",
      "timezoneSource": "operator",
      "operatingHours": "default",
      "lastPanelCommand": null,
      "scheduleScreensOff": false,
      "displayPowerMode": "always-on",
      "playbackMode": "unsync",
      "locationLabel": null,
      "locationLat": null,
      "locationLng": null,
      "ipCity": "London",
      "ipRegion": "England",
      "ipCountry": "GB",
      "ipLat": 51.5,
      "ipLng": -0.12,
      "ipTimezone": "Europe/London",
      "ipGeoAt": "2026-09-01T09:00:00.000Z",
      "playerSettings": null,
      "kioskEnabled": false,
      "kioskPinMode": "workspace",
      "kioskGraceSeconds": 30,
      "activeEmergencyId": null,
      "activeCastId": null,
      "playerVersionHold": null,
      "sealed": false,
      "sealedAt": null,
      "powerPolicyId": null,
      "importSourceId": null,
      "billingGroupId": null,
      "customFields": null,
      "lanSecretAt": null,
      "createdAt": "2026-09-01T09:00:00.000Z",
      "updatedAt": "2026-09-28T11:58:00.000Z",
      "deletedAt": null,
      "location": null,
      "deviceClaimed": false,
      "nodeName": "Head office",
      "groupIds": [],
      "contentName": "Lobby loop",
      "contentSharedFrom": null,
      "thumbnailUrl": null,
      "contentAppKey": null,
      "contentMediaKind": null,
      "contentState": null,
      "contentOrientation": "landscape",
      "liveThumbnailAt": "2026-09-28T11:45:00.000Z",
      "liveThumbnailFreshAt": "2026-09-28T11:57:00.000Z",
      "currentContentKind": "image",
      "currentContentPosterUrl": null,
      "emergencyHeadline": null,
      "castHeadline": null,
      "castStartedAt": null,
      "castExpiresAt": null,
      "castContentKind": null,
      "castContentId": null,
      "kioskHasCustomPin": false,
      "kioskHasRecovery": false,
      "obscuredSince": null,
      "pixelHealth": "ok",
      "displayOffSince": null,
      "displayOffReason": null,
      "expectedDark": {
        "dark": false,
        "reason": null,
        "nextOpen": null,
        "minutesSinceOpen": null,
        "minutesSinceClose": null
      },
      "accountHold": null,
      "outageSummary30d": null,
      "state": {
        "updatedAt": "2026-09-28T11:58:00.000Z",
        "connection": "online",
        "currentContent": {
          "kind": "playlist",
          "id": "pl_9a8b7c6d5e4f3a2b"
        }
      }
    }
  ]
}
```

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens

Create a screen. `name` is required; `nodeId`, `location`, and `tags` are optional. This operation is safe to retry with an idempotency key. Each screen created increases your billed screen count, so this request is refused if your account is suspended or cancelled.

**Notes.**
- Returns the stored row, not the composed Screen that GET /v1/screens/{id} returns: `tags` is the JSON-encoded string, and the composed fields (`contentName`, `state`, …) are absent.
- The screen waits in `pairing` status until a device claims it (POST /v1/screens/{id}/claim-replacement) — or use POST /v1/screens/claim or /v1/screens/enroll, which create and pair in one step.

Auth: Bearer token. Permission: `screen.create`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes |  |
| `nodeId` | string | no | Location to create it in; default: the API key's own location, else the workspace root. |
| `billingGroupId` | string \| null | no | Billing group that pays for the screen. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | ScreenRow | A screen as stored, before composition. |
| `data.id` | string | Screen id. |
| `data.spaceId` | string | Workspace id. |
| `data.nodeId` | string \| null | Location (org node) the screen belongs to; null = workspace root. |
| `data.name` | string |  |
| `data.status` | "online" \| "offline" \| "pairing" | `pairing` = no device has checked in yet. |
| `data.lastSeenAt` | string \| null | Last heartbeat or socket ping reply, whichever is later. |
| `data.coreUpdateOfferedAt` | string \| null |  |
| `data.contentKind` | "playlist" \| "schedule" \| "layout" \| "creative" \| "app" \| "media" \| "signage" \| null | What is assigned. Null = nothing assigned. |
| `data.contentId` | string \| null |  |
| `data.contentFit` | null | Retired; always null. Fit is set on the content. |
| `data.deactivatedAt` | string \| null | Set while an operator has deactivated the screen. |
| `data.rotation` | integer | 0, 90, 180 or 270 degrees. |
| `data.rotationCommandedAt` | string \| null |  |
| `data.timezone` | string \| null | IANA time zone; null = inherited. |
| `data.timezoneSource` | "operator" \| "device" \| null |  |
| `data.operatingHours` | string | `default`, or a JSON-encoded weekly window. |
| `data.lastPanelCommand` | string \| null |  |
| `data.scheduleScreensOff` | boolean |  |
| `data.displayPowerMode` | "always-on" \| "follow-schedule" \| "os-default" |  |
| `data.playbackMode` | "sync" \| "unsync" \| "device-time" |  |
| `data.locationLabel` | string \| null |  |
| `data.locationLat` | number \| null |  |
| `data.locationLng` | number \| null |  |
| `data.ipCity` | string \| null |  |
| `data.ipRegion` | string \| null |  |
| `data.ipCountry` | string \| null |  |
| `data.ipLat` | number \| null |  |
| `data.ipLng` | number \| null |  |
| `data.ipTimezone` | string \| null |  |
| `data.ipGeoAt` | string \| null |  |
| `data.playerSettings` | string \| null | Player settings as a JSON-ENCODED string (not an object). |
| `data.kioskEnabled` | boolean |  |
| `data.kioskPinMode` | "workspace" \| "custom" |  |
| `data.kioskGraceSeconds` | integer |  |
| `data.activeEmergencyId` | string \| null | The live emergency overriding this screen, if any. |
| `data.activeCastId` | string \| null | The live cast overriding this screen, if any. |
| `data.playerVersionHold` | string \| null |  |
| `data.sealed` | boolean |  |
| `data.sealedAt` | string \| null |  |
| `data.powerPolicyId` | string \| null |  |
| `data.importSourceId` | string \| null |  |
| `data.billingGroupId` | string \| null |  |
| `data.customFields` | string \| null | Custom fields as a JSON-ENCODED string. |
| `data.lanSecretAt` | string \| null | When the local-trigger key was last set or rotated. The key itself is never returned. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data.deletedAt` | string \| null |  |
| `data.tags` | string | Tags as a JSON-ENCODED array string (the stored column), not an array. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: The location does not exist.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: The account is suspended or cancelled.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: `name` is missing.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}

Get a screen

Get one screen, with the same detail included in the screen list. A screen outside your organization scope returns a not-found error rather than a permission error.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | Screen | A screen, composed with its content, live state and health facts. |
| `data.id` | string | Screen id. |
| `data.spaceId` | string | Workspace id. |
| `data.nodeId` | string \| null | Location (org node) the screen belongs to; null = workspace root. |
| `data.name` | string |  |
| `data.status` | "online" \| "offline" \| "pairing" | `pairing` = no device has checked in yet. |
| `data.lastSeenAt` | string \| null | Last heartbeat or socket ping reply, whichever is later. |
| `data.coreUpdateOfferedAt` | string \| null |  |
| `data.contentKind` | "playlist" \| "schedule" \| "layout" \| "creative" \| "app" \| "media" \| "signage" \| null | What is assigned. Null = nothing assigned. |
| `data.contentId` | string \| null |  |
| `data.contentFit` | null | Retired; always null. Fit is set on the content. |
| `data.deactivatedAt` | string \| null | Set while an operator has deactivated the screen. |
| `data.tags` | array of string |  |
| `data.rotation` | integer | 0, 90, 180 or 270 degrees. |
| `data.rotationCommandedAt` | string \| null |  |
| `data.timezone` | string \| null | IANA time zone; null = inherited. |
| `data.timezoneSource` | "operator" \| "device" \| null |  |
| `data.operatingHours` | string | `default`, or a JSON-encoded weekly window. |
| `data.lastPanelCommand` | string \| null |  |
| `data.scheduleScreensOff` | boolean |  |
| `data.displayPowerMode` | "always-on" \| "follow-schedule" \| "os-default" |  |
| `data.playbackMode` | "sync" \| "unsync" \| "device-time" |  |
| `data.locationLabel` | string \| null |  |
| `data.locationLat` | number \| null |  |
| `data.locationLng` | number \| null |  |
| `data.ipCity` | string \| null |  |
| `data.ipRegion` | string \| null |  |
| `data.ipCountry` | string \| null |  |
| `data.ipLat` | number \| null |  |
| `data.ipLng` | number \| null |  |
| `data.ipTimezone` | string \| null |  |
| `data.ipGeoAt` | string \| null |  |
| `data.playerSettings` | string \| null | Player settings as a JSON-ENCODED string (not an object). |
| `data.kioskEnabled` | boolean |  |
| `data.kioskPinMode` | "workspace" \| "custom" |  |
| `data.kioskGraceSeconds` | integer |  |
| `data.activeEmergencyId` | string \| null | The live emergency overriding this screen, if any. |
| `data.activeCastId` | string \| null | The live cast overriding this screen, if any. |
| `data.playerVersionHold` | string \| null |  |
| `data.sealed` | boolean |  |
| `data.sealedAt` | string \| null |  |
| `data.powerPolicyId` | string \| null |  |
| `data.importSourceId` | string \| null |  |
| `data.billingGroupId` | string \| null |  |
| `data.customFields` | string \| null | Custom fields as a JSON-ENCODED string. |
| `data.lanSecretAt` | string \| null | When the local-trigger key was last set or rotated. The key itself is never returned. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data.deletedAt` | string \| null |  |
| `data.location` | object \| null |  |
| `data.deviceClaimed` | boolean | Status is `pairing` but a device has claimed it and not yet checked in. |
| `data.nodeName` | string \| null |  |
| `data.groupIds` | array of string | Screen groups this screen is in. |
| `data.contentName` | string \| null |  |
| `data.contentSharedFrom` | string \| null | Name of the workspace that shared the assigned content in; null when it is this workspace's own. |
| `data.thumbnailUrl` | string \| null | Preview of the assigned content. |
| `data.contentAppKey` | string \| null |  |
| `data.contentMediaKind` | string \| null |  |
| `data.contentState` | object \| null | Assigned media that is not playable yet (processing, failed, needs_action); null otherwise. |
| `data.contentOrientation` | "portrait" \| "landscape" \| "adaptive" \| null |  |
| `data.liveThumbnailAt` | string \| null | Capture time of the latest live frame (GET /v1/screens/{id}/live-thumbnail). |
| `data.liveThumbnailFreshAt` | string \| null |  |
| `data.currentContentKind` | "image" \| "video" \| "stream" \| null |  |
| `data.currentContentPosterUrl` | string \| null |  |
| `data.emergencyHeadline` | string \| null |  |
| `data.castHeadline` | string \| null |  |
| `data.castStartedAt` | string \| null |  |
| `data.castExpiresAt` | string \| null |  |
| `data.castContentKind` | string \| null |  |
| `data.castContentId` | string \| null |  |
| `data.kioskHasCustomPin` | boolean |  |
| `data.kioskHasRecovery` | boolean |  |
| `data.obscuredSince` | string \| null | A system dialog is covering the screen since this time. |
| `data.pixelHealth` | "ok" \| "frozen" \| "blank" \| null |  |
| `data.displayOffSince` | string \| null |  |
| `data.displayOffReason` | "standby" \| "wrong-input" \| "disconnected" \| null |  |
| `data.expectedDark` | object |  |
| `data.expectedDark.dark` | boolean | True when the rules say the panel should be off now. |
| `data.expectedDark.reason` | "schedule-block" \| "power-policy" \| "operating-hours" \| null |  |
| `data.expectedDark.nextOpen` | object \| null |  |
| `data.expectedDark.minutesSinceOpen` | number \| null |  |
| `data.expectedDark.minutesSinceClose` | number \| null |  |
| `data.accountHold` | object \| null |  |
| `data.outageSummary30d` | object \| null |  |
| `data.linkFlaps10m` | integer | Socket link flips in the last ten minutes; present only when > 0 (list and get). |
| `data.state` | object \| null | The device's last reported state (`updatedAt` plus the heartbeat snapshot). The list returns a lean subset; get and PATCH return it all. Null before the first heartbeat. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such screen in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## PATCH /v1/screens/{id}

Update a screen's settings

Update a screen's own fields, including name, location, tags, orientation, organization node, and player settings. This endpoint does not assign content; use POST /v1/screens/:id/assign to change what a screen plays.

Partial update. To change what a screen plays use POST /v1/screens/{id}/assign — this route refuses `contentKind`/`contentId` with a 400.

**Notes.**
- The PATCH response does not merge the live socket facts (`linkFlaps10m`, a socket-fresh `lastSeenAt`) that GET adds.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no |  |
| `nodeId` | string \| null | no | Move to another location (you need screen.edit there too). |
| `billingGroupId` | string \| null | no | Needs billing.edit. |
| `status` | "online" \| "offline" \| "pairing" | no |  |
| `tags` | array of string | no |  |
| `rotation` | 0 \| 90 \| 180 \| 270 | no |  |
| `timezone` | string \| null | no | IANA time zone; null to inherit. |
| `operatingHours` | string | no |  |
| `displayPowerMode` | "always-on" \| "follow-schedule" \| "os-default" | no |  |
| `powerPolicyId` | string \| null | no |  |
| `playbackMode` | "sync" \| "unsync" \| "device-time" | no |  |
| `location` | object \| null | no |  |
| `deactivated` | boolean | no | True deactivates the screen (nothing plays); false reactivates it. |
| `playerVersionHold` | boolean \| string \| null | no |  |
| `playerSettings` | object \| null | no | Merged into the current settings; null resets them. |
| `customFields` | object \| null | no |  |
| `kioskPinMode` | "workspace" \| "custom" | no |  |
| `kioskGraceSeconds` | number | no |  |
| `kioskPin` | string \| null | no | 4-8 digits; null clears the custom PIN. |
| `kioskEnabled` | boolean | no |  |

```bash
curl -X PATCH "https://api.brixsignage.com/v1/screens/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | Screen | A screen, composed with its content, live state and health facts. |
| `data.id` | string | Screen id. |
| `data.spaceId` | string | Workspace id. |
| `data.nodeId` | string \| null | Location (org node) the screen belongs to; null = workspace root. |
| `data.name` | string |  |
| `data.status` | "online" \| "offline" \| "pairing" | `pairing` = no device has checked in yet. |
| `data.lastSeenAt` | string \| null | Last heartbeat or socket ping reply, whichever is later. |
| `data.coreUpdateOfferedAt` | string \| null |  |
| `data.contentKind` | "playlist" \| "schedule" \| "layout" \| "creative" \| "app" \| "media" \| "signage" \| null | What is assigned. Null = nothing assigned. |
| `data.contentId` | string \| null |  |
| `data.contentFit` | null | Retired; always null. Fit is set on the content. |
| `data.deactivatedAt` | string \| null | Set while an operator has deactivated the screen. |
| `data.tags` | array of string |  |
| `data.rotation` | integer | 0, 90, 180 or 270 degrees. |
| `data.rotationCommandedAt` | string \| null |  |
| `data.timezone` | string \| null | IANA time zone; null = inherited. |
| `data.timezoneSource` | "operator" \| "device" \| null |  |
| `data.operatingHours` | string | `default`, or a JSON-encoded weekly window. |
| `data.lastPanelCommand` | string \| null |  |
| `data.scheduleScreensOff` | boolean |  |
| `data.displayPowerMode` | "always-on" \| "follow-schedule" \| "os-default" |  |
| `data.playbackMode` | "sync" \| "unsync" \| "device-time" |  |
| `data.locationLabel` | string \| null |  |
| `data.locationLat` | number \| null |  |
| `data.locationLng` | number \| null |  |
| `data.ipCity` | string \| null |  |
| `data.ipRegion` | string \| null |  |
| `data.ipCountry` | string \| null |  |
| `data.ipLat` | number \| null |  |
| `data.ipLng` | number \| null |  |
| `data.ipTimezone` | string \| null |  |
| `data.ipGeoAt` | string \| null |  |
| `data.playerSettings` | string \| null | Player settings as a JSON-ENCODED string (not an object). |
| `data.kioskEnabled` | boolean |  |
| `data.kioskPinMode` | "workspace" \| "custom" |  |
| `data.kioskGraceSeconds` | integer |  |
| `data.activeEmergencyId` | string \| null | The live emergency overriding this screen, if any. |
| `data.activeCastId` | string \| null | The live cast overriding this screen, if any. |
| `data.playerVersionHold` | string \| null |  |
| `data.sealed` | boolean |  |
| `data.sealedAt` | string \| null |  |
| `data.powerPolicyId` | string \| null |  |
| `data.importSourceId` | string \| null |  |
| `data.billingGroupId` | string \| null |  |
| `data.customFields` | string \| null | Custom fields as a JSON-ENCODED string. |
| `data.lanSecretAt` | string \| null | When the local-trigger key was last set or rotated. The key itself is never returned. |
| `data.createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.updatedAt` | string | ISO-8601 timestamp (UTC). |
| `data.deletedAt` | string \| null |  |
| `data.location` | object \| null |  |
| `data.deviceClaimed` | boolean | Status is `pairing` but a device has claimed it and not yet checked in. |
| `data.nodeName` | string \| null |  |
| `data.groupIds` | array of string | Screen groups this screen is in. |
| `data.contentName` | string \| null |  |
| `data.contentSharedFrom` | string \| null | Name of the workspace that shared the assigned content in; null when it is this workspace's own. |
| `data.thumbnailUrl` | string \| null | Preview of the assigned content. |
| `data.contentAppKey` | string \| null |  |
| `data.contentMediaKind` | string \| null |  |
| `data.contentState` | object \| null | Assigned media that is not playable yet (processing, failed, needs_action); null otherwise. |
| `data.contentOrientation` | "portrait" \| "landscape" \| "adaptive" \| null |  |
| `data.liveThumbnailAt` | string \| null | Capture time of the latest live frame (GET /v1/screens/{id}/live-thumbnail). |
| `data.liveThumbnailFreshAt` | string \| null |  |
| `data.currentContentKind` | "image" \| "video" \| "stream" \| null |  |
| `data.currentContentPosterUrl` | string \| null |  |
| `data.emergencyHeadline` | string \| null |  |
| `data.castHeadline` | string \| null |  |
| `data.castStartedAt` | string \| null |  |
| `data.castExpiresAt` | string \| null |  |
| `data.castContentKind` | string \| null |  |
| `data.castContentId` | string \| null |  |
| `data.kioskHasCustomPin` | boolean |  |
| `data.kioskHasRecovery` | boolean |  |
| `data.obscuredSince` | string \| null | A system dialog is covering the screen since this time. |
| `data.pixelHealth` | "ok" \| "frozen" \| "blank" \| null |  |
| `data.displayOffSince` | string \| null |  |
| `data.displayOffReason` | "standby" \| "wrong-input" \| "disconnected" \| null |  |
| `data.expectedDark` | object |  |
| `data.expectedDark.dark` | boolean | True when the rules say the panel should be off now. |
| `data.expectedDark.reason` | "schedule-block" \| "power-policy" \| "operating-hours" \| null |  |
| `data.expectedDark.nextOpen` | object \| null |  |
| `data.expectedDark.minutesSinceOpen` | number \| null |  |
| `data.expectedDark.minutesSinceClose` | number \| null |  |
| `data.accountHold` | object \| null |  |
| `data.outageSummary30d` | object \| null |  |
| `data.linkFlaps10m` | integer | Socket link flips in the last ten minutes; present only when > 0 (list and get). |
| `data.state` | object \| null | The device's last reported state (`updatedAt` plus the heartbeat snapshot). The list returns a lean subset; get and PATCH return it all. Null before the first heartbeat. |

Response 400: The body tried to set content (use POST /v1/screens/{id}/assign).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such screen (or destination location/billing group) in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Invalid status, rotation, time zone or PIN.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## DELETE /v1/screens/{id}

Delete a screen. It moves to the 30-day recovery window, and its paired device is unpaired so the player shows its pairing screen. An optional `reason` field is recorded in the activity log.

Soft-deletes the screen to the recycle bin (restorable for 30 days) and releases its device, which returns to the pairing screen.

Auth: Bearer token. Permission: `screen.delete`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X DELETE "https://api.brixsignage.com/v1/screens/{id}" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.deleted` | true |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such screen in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/assign

Set what a screen plays

Assign content to a screen using `contentKind` and `contentId`. Pass `null` to clear the current assignment. The content is validated to confirm it exists, can be used at the screen's organization node, and has no unfilled layout zones, before the screen is updated to play it.

Assigns content to one screen (null clears it). The screen is told to reload immediately.

Auth: Bearer token. Permission: `screen.cast`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `contentKind` | "playlist" \| "schedule" \| "layout" \| "creative" \| "app" \| "media" \| "signage" \| null | yes | What to assign; null clears the assignment. |
| `contentId` | string | no | Required unless `contentKind` is null. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/assign" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"contentKind":"playlist","contentId":"pl_9a8b7c6d5e4f3a2b"}'
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.contentKind` | "playlist" \| "schedule" \| "layout" \| "creative" \| "app" \| "media" \| "signage" \| null |  |
| `data.contentId` | string \| null |  |

```json
{
  "data": {
    "id": "scr_1a2b3c4d5e6f7a8b",
    "contentKind": "playlist",
    "contentId": "pl_9a8b7c6d5e4f3a2b"
  }
}
```

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: `not_shared`: the content is not shared to this screen's location.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such screen or content in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Invalid kind, a layout with unbound zones, or content that cannot play (empty playlist, unprocessed media).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/claim-replacement

Replace a screen's device

Bind a new device, identified by the 6-digit pairing code it is displaying, to an existing screen. The screen keeps its identity, assigned content, and history. This endpoint is rate-limited because the pairing code is a small, guessable value.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | string | yes | The 6-digit code the new device shows. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/claim-replacement" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.paired` | true |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No screen with this id, or no device shows that code.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: `ambiguous_code` (two devices show it) or `already_claimed`.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 410: The code has expired.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: `code` is missing.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 429: Too many wrong codes; wait and try again.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/commands

Send a command to a screen

Queue a command for a screen's device, such as reboot, turning the display on or off, taking a screenshot, or clearing the cache. The device runs the command the next time it checks in.

Queues a device command (reboot, refresh, screenshot, volume, …). Poll GET /v1/screens/{id}/commands/{commandId} for the device's answer.

**Notes.**
- A `screenshot` that coalesces onto an older pending one answers 200 (not 201) with `coalesced: true`.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | string | yes |  |
| `payload` | object | no | Per-kind payload: `set-volume` {level 0-100}, `set-brightness` {level}, `set-mute` {muted}, `set-input` {input}, … Most kinds take none. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/commands" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string | Command id. |
| `data.kind` | string |  |
| `data.status` | "pending" |  |
| `data.coalesced` | boolean | True when a stale pending screenshot command was reused instead. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such screen in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Unknown kind or invalid payload.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/commands/{commandId}

Get a command's status

Get the outcome of one previously queued device command, including its status (acknowledged, failed, or pending) and any note reported by the device.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |
| `commandId` | path | string | yes | Command id from POST /v1/screens/{id}/commands. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/commands/{commandId}" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.kind` | string | The command kind (see POST /v1/screens/{id}/commands). |
| `data.status` | "pending" \| "delivered" \| "acked" \| "failed" \| "unconfirmed" \| "held" |  |
| `data.result` | string \| null |  |
| `data.issuedAt` | string | ISO-8601 timestamp (UTC). |
| `data.deliveredAt` | string \| null |  |
| `data.ackedAt` | string \| null |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/diagnose

Diagnose a screen issue

Get a likely-cause troubleshooting verdict for a screen, based on signals such as online or offline status, what is actually rendering, resource pressure, and early hardware warnings. The response also includes a guided checklist for checking the TV, input, and cable, which the screen itself cannot detect.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/diagnose" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | ScreenDiagnosis | Why a screen is (or is not) showing what it should. |
| `data.layer` | "offline" \| "deactivated" \| "unassigned" \| "stick" \| "resource" \| "display" \| "downstream" \| "healthy" | The first layer that explains the problem, checked from the device outward. |
| `data.severity` | "none" \| "info" \| "warning" \| "critical" |  |
| `data.likelyCause` | string | One sentence an operator can act on. |
| `data.detail` | string \| null |  |
| `data.fault` | "platform" \| "user" \| "environment" \| null | Whose problem it is; null when healthy. |
| `data.guided` | boolean | True when `checklist` is a guided fix. |
| `data.checklist` | array of object |  |
| `data.checklist[].code` | string |  |
| `data.checklist[].label` | string |  |
| `data.checklist[].detail` | string |  |
| `data.remedies` | array of object | One-click fixes, when one applies. |
| `data.remedies[].kind` | "reboot" \| "wake-screen" \| "restart-app" | The command to send with POST /v1/screens/{id}/commands. |
| `data.remedies[].label` | string |  |
| `data.remedies[].cost` | string | What the remedy interrupts, in words. |
| `data.remedies[].confirm` | boolean |  |
| `data.prominent` | boolean | True when the console shows this as a banner. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/diagnostic-bundles

List a screen's diagnostic bundles

List the detailed diagnostic bundles a screen has uploaded, such as system logs, thread dumps, and exit reasons, newest first. This returns only the index; download an individual bundle separately.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/diagnostic-bundles" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of object |  |
| `data[].id` | string |  |
| `data[].reason` | string |  |
| `data[].sections` | array of string |  |
| `data[].bytes` | integer |  |
| `data[].downloadable` | boolean | False when the bundle was recorded but not stored. |
| `data[].playerVersion` | string \| null |  |
| `data[].platform` | string \| null |  |
| `data[].clockSkewMs` | number \| null |  |
| `data[].createdAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/diagnostic-bundles/{bundleId}

Download a diagnostic bundle

Download one detailed diagnostic bundle as JSON, exactly as it was uploaded by the device.

**Notes.**
- The body is the bundle exactly as the device uploaded it, served as an attachment.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |
| `bundleId` | path | string | yes | Bundle id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/diagnostic-bundles/{bundleId}" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such bundle, or it was not stored.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/diagnostics

Get consolidated screen diagnostics

Get a consolidated diagnostic report for a screen: the likely-cause verdict from GET /v1/screens/:id/diagnose plus the underlying signals, including open alerts, recent health trends, crashes and self-heals, a recent log tail, and the latest reported state. Pass `format=text` for a plain-text report, or omit it for structured JSON.

**Notes.**
- `?format=text` returns the same report as plain text (text/plain) instead of JSON.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/diagnostics" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.screen` | object |  |
| `data.screen.id` | string |  |
| `data.screen.name` | string |  |
| `data.screen.status` | "online" \| "offline" \| "pairing" |  |
| `data.screen.lastSeenAt` | string \| null |  |
| `data.diagnosis` | ScreenDiagnosis | Why a screen is (or is not) showing what it should. |
| `data.diagnosis.layer` | "offline" \| "deactivated" \| "unassigned" \| "stick" \| "resource" \| "display" \| "downstream" \| "healthy" | The first layer that explains the problem, checked from the device outward. |
| `data.diagnosis.severity` | "none" \| "info" \| "warning" \| "critical" |  |
| `data.diagnosis.likelyCause` | string | One sentence an operator can act on. |
| `data.diagnosis.detail` | string \| null |  |
| `data.diagnosis.fault` | "platform" \| "user" \| "environment" \| null | Whose problem it is; null when healthy. |
| `data.diagnosis.guided` | boolean | True when `checklist` is a guided fix. |
| `data.diagnosis.checklist` | array of object |  |
| `data.diagnosis.checklist[].code` | string |  |
| `data.diagnosis.checklist[].label` | string |  |
| `data.diagnosis.checklist[].detail` | string |  |
| `data.diagnosis.remedies` | array of object | One-click fixes, when one applies. |
| `data.diagnosis.remedies[].kind` | "reboot" \| "wake-screen" \| "restart-app" | The command to send with POST /v1/screens/{id}/commands. |
| `data.diagnosis.remedies[].label` | string |  |
| `data.diagnosis.remedies[].cost` | string | What the remedy interrupts, in words. |
| `data.diagnosis.remedies[].confirm` | boolean |  |
| `data.diagnosis.prominent` | boolean | True when the console shows this as a banner. |
| `data.alerts` | array of object | Open alerts on this screen. |
| `data.alerts[].code` | string |  |
| `data.alerts[].severity` | string |  |
| `data.alerts[].message` | string |  |
| `data.alerts[].openedAt` | string | ISO-8601 timestamp (UTC). |
| `data.vitals` | array of object | Recent device vitals, newest first. |
| `data.vitals[].id` | string |  |
| `data.vitals[].spaceId` | string |  |
| `data.vitals[].screenId` | string |  |
| `data.vitals[].storageFreeMb` | number \| null |  |
| `data.vitals[].storageTotalMb` | number \| null |  |
| `data.vitals[].memoryFreeMb` | number \| null |  |
| `data.vitals[].memoryTotalMb` | number \| null |  |
| `data.vitals[].heapUsedMb` | number \| null |  |
| `data.vitals[].heapLimitMb` | number \| null |  |
| `data.vitals[].networkLossRatio` | number \| null |  |
| `data.vitals[].networkRttMs` | number \| null |  |
| `data.vitals[].thermalStatus` | string \| null |  |
| `data.vitals[].reloadCount` | integer \| null |  |
| `data.vitals[].rebootCount` | integer \| null |  |
| `data.vitals[].uptimeSec` | number \| null |  |
| `data.vitals[].fps` | number \| null |  |
| `data.vitals[].videoDropPct` | number \| null |  |
| `data.vitals[].webViewKillCount` | integer \| null |  |
| `data.vitals[].renderFreezeCount` | integer \| null |  |
| `data.vitals[].maxRenderFreezeMs` | number \| null |  |
| `data.vitals[].childFreezeCount` | integer \| null |  |
| `data.vitals[].occurredAt` | string | ISO-8601 timestamp (UTC). |
| `data.crashes` | array of object |  |
| `data.crashes[].reason` | string |  |
| `data.crashes[].reportedAt` | string | ISO-8601 timestamp (UTC). |
| `data.logs` | array of object |  |
| `data.logs[].entries` | any | The uploaded log lines (decoded JSON). |
| `data.logs[].reason` | string |  |
| `data.logs[].errorCount` | integer |  |
| `data.logs[].warnCount` | integer |  |
| `data.logs[].createdAt` | string | ISO-8601 timestamp (UTC). |
| `data.state` | object \| null | The last heartbeat snapshot; null before the first heartbeat. |
| `data.generatedAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/display-history

Get a screen's display history

Get the history of what the physical display itself did, such as being switched off, switched to another HDMI input, losing its HDMI connection, or coming back, separate from whether the player software was online. Entries are newest first, each showing how long the previous state lasted, along with the current state. This data is only available for displays that support CEC, and history is limited to the last 14 days.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |
| `days` | query | string | no | Window in days; default and maximum: the telemetry retention. |
| `limit` | query | string | no | Most changes to list (default 50, max 500). |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/display-history" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.screenId` | string |  |
| `data.windowDays` | integer |  |
| `data.retentionDays` | integer |  |
| `data.current` | object \| null | What the panel is doing now; null when the device cannot read the panel. |
| `data.changes` | array of object |  |
| `data.changes[].id` | string |  |
| `data.changes[].at` | string | ISO-8601 timestamp (UTC). |
| `data.changes[].from` | "standby" \| "wrong-input" \| "disconnected" \| "lit" |  |
| `data.changes[].to` | "standby" \| "wrong-input" \| "disconnected" \| "lit" |  |
| `data.changes[].forSec` | number \| null |  |
| `data.truncated` | boolean |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/frames

Get a screen's frame history

Get a log of visual fingerprints captured from a screen over a time range, using the `since` and `until` query parameters. This log is a visual audit trail and is kept even after the screen is deleted.

**Notes.**
- The newest 500 frames in the window.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |
| `since` | query | string | no | ISO timestamp; only frames at or after it. |
| `until` | query | string | no | ISO timestamp; only frames at or before it. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/frames" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of object |  |
| `data[].hash` | string | Perceptual hash of the frame on the glass. |
| `data[].contentKind` | string \| null |  |
| `data[].contentId` | string \| null |  |
| `data[].capturedAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/kiosk-recovery/reveal

Reveal a screen's recovery code

Reveal the offline recovery code for a screen with Screen Lock enabled. This action is recorded in the activity log.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/kiosk-recovery/reveal" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.code` | string | The Screen Lock recovery code. Each reveal is recorded in the audit log. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No screen with this id, or it has no recovery code.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/kiosk-recovery/rotate

Rotate a screen's recovery code

Generate a new offline recovery code for a screen with Screen Lock enabled. The previous code stops working immediately. This action is recorded in the activity log.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/kiosk-recovery/rotate" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.code` | string | The new Screen Lock recovery code. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/lan-secret/rotate

Rotate a screen's local network key

Replace the key used to authenticate local-network requests to this screen. The new key is not returned in the response; only the device receives it. Any third-party integration that signs its own requests to the screen must be updated with the new key.

**Notes.**
- The key itself is never returned; the device collects it.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/lan-secret/rotate" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.rotatedAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No screen with this id, or Local trigger is off for it.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/live-thumbnail

Get a screen's live thumbnail

Get the most recently captured still image for a screen, returned as image bytes. This accepts standard API authentication, including an API key, or a short-lived `asset` query parameter token so the image can be loaded directly in an image tag. A screen outside your access scope returns a not-found error rather than a permission error.

**Notes.**
- The response header `x-captured-at` carries the capture time.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |
| `at` | query | string | no | `capturedAt` of a specific capture; default the newest. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/live-thumbnail" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No screen with this id, or it has no capture yet (`no_screenshot`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/logs

Get a screen's activity log

Get a combined, newest-first feed of a screen's recent activity from the last 24 hours, including telemetry events, crash reports, command acknowledgements, and screenshot uploads.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/logs" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.lines` | array of object | Newest first, at most 200. |
| `data.lines[].at` | string | ISO-8601 timestamp (UTC). |
| `data.lines[].level` | "error" \| "info" \| "warn" |  |
| `data.lines[].text` | string |  |
| `data.windowHours` | integer |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/mirror

Start a screen mirror session

Start a live mirroring session for a screen. The response includes a signaling URL that a viewer connects to over WebRTC; the screen's device connects when it receives the corresponding command. End the session with POST /v1/mirror/:sessionId/end.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/mirror" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.sessionId` | string |  |
| `data.signalingUrl` | string |  |
| `data.signalingToken` | string | Viewer token for `signalingUrl`; valid for 5 minutes, for this session only. |
| `data.turnHint` | string |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/outages

Get a screen's outage history

Get the outage history for a screen over the last N days (default 30, capped at the retention window). Each outage includes when it happened, how long it lasted, a plain-language cause such as a Wi-Fi drop or a power cut, and supporting evidence, along with a summary sentence such as "went down 6 times in the last 30 days, all Wi-Fi drops". Planned downtime is listed but excluded from the outage count.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |
| `days` | query | string | no | Window in days (default 30), capped at the workspace's retention. |
| `limit` | query | string | no | Most outages to list (default 50). |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/outages" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.screenId` | string |  |
| `data.windowDays` | integer |  |
| `data.retentionDays` | integer |  |
| `data.summary` | object |  |
| `data.summary.windowDays` | integer |  |
| `data.summary.count` | integer | Unplanned outages in the window. |
| `data.summary.byCause` | object | Outage count per cause; a cause with none is absent. |
| `data.summary.byCause.power-loss` | integer |  |
| `data.summary.byCause.powered-off` | integer |  |
| `data.summary.byCause.os-reboot` | integer |  |
| `data.summary.byCause.app-crash` | integer |  |
| `data.summary.byCause.app-killed-low-memory` | integer |  |
| `data.summary.byCause.app-anr` | integer |  |
| `data.summary.byCause.app-restart` | integer |  |
| `data.summary.byCause.wifi-dropped` | integer |  |
| `data.summary.byCause.wifi-weak` | integer |  |
| `data.summary.byCause.wifi-no-internet` | integer |  |
| `data.summary.byCause.ethernet-dropped` | integer |  |
| `data.summary.byCause.brix-unreachable` | integer |  |
| `data.summary.byCause.display-off` | integer |  |
| `data.summary.byCause.sleep` | integer |  |
| `data.summary.byCause.scheduled-off` | integer |  |
| `data.summary.byCause.unknown` | integer |  |
| `data.summary.byCause.power-cycle` | integer |  |
| `data.summary.byCause.network-only` | integer |  |
| `data.summary.byBucket` | object |  |
| `data.summary.byBucket.app` | integer |  |
| `data.summary.byBucket.display` | integer |  |
| `data.summary.byBucket.unknown` | integer |  |
| `data.summary.byBucket.network` | integer |  |
| `data.summary.byBucket.power` | integer |  |
| `data.summary.byBucket.brix` | integer |  |
| `data.summary.byBucket.planned` | integer |  |
| `data.summary.topCause` | string \| null |  |
| `data.summary.topCauseShare` | number |  |
| `data.summary.hedgedShare` | number |  |
| `data.summary.lastCause` | object \| null |  |
| `data.summary.lastAt` | string \| null |  |
| `data.summary.lastKnownCause` | object \| null |  |
| `data.summary.avgRssiDbm` | number \| null |  |
| `data.summary.insight` | string \| null | The rollup sentence, e.g. `All 6 were Wi-Fi drops.` |
| `data.outages` | array of object |  |
| `data.outages[].id` | string |  |
| `data.outages[].from` | string | ISO-8601 timestamp (UTC). |
| `data.outages[].to` | string \| null | Null while the screen is still down. |
| `data.outages[].durationSec` | integer \| null |  |
| `data.outages[].open` | boolean |  |
| `data.outages[].detectionPath` | string |  |
| `data.outages[].cause` | object \| null |  |
| `data.outages[].label` | string \| null |  |
| `data.outages[].sentence` | string \| null |  |
| `data.outages[].evidence` | object \| null |  |
| `data.outages[].expectedDark` | boolean \| null | True for planned darkness (operating hours): listed, never counted. |
| `data.outages[].expectedReason` | string \| null |  |
| `data.outages[].selfReportedReason` | string \| null |  |
| `data.outages[].playerVersion` | string \| null |  |
| `data.truncated` | boolean |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/playback-quality

Get a screen's playback quality

Check whether this screen's device is limiting playback quality. The response reports, for each 4K-capable video, whether it plays at full quality or has been reduced because of device limitations or stuttering, along with any hardware upgrade recommendation.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/playback-quality" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.model` | string \| null |  |
| `data.tier` | string \| null |  |
| `data.serves4k` | boolean | True when the device is sent 4K renditions. |
| `data.videos4k` | array of object |  |
| `data.videos4k[].id` | string |  |
| `data.videos4k[].name` | string |  |
| `data.videos4k[].demoted` | boolean |  |
| `data.videos4k[].served` | boolean |  |
| `data.recommendation` | string \| null |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/preview

Preview what a screen would play at a given time, without affecting the actual device. Pass `at` as an ISO timestamp to preview a different time; it defaults to now.

**Notes.**
- `data` is the player manifest. Content blocks are open objects in the player's own format; `v` versions it.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |
| `at` | query | string | no | ISO timestamp to build the preview for; default now. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/preview" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | ManifestPreview | What the screen would play: the player manifest, built as the device would receive it. |
| `data.v` | integer | Manifest format version. |
| `data.screen` | object |  |
| `data.screen.id` | string |  |
| `data.screen.name` | string |  |
| `data.screen.rotation` | integer |  |
| `data.screen.tags` | array of string |  |
| `data.generatedAt` | string | ISO-8601 timestamp (UTC). |
| `data.minPlayerVersion` | string |  |
| `data.content` | ManifestContentBlock | Resolved content: `kind` plus the fields for that kind (`media`, `playlist` + `items`, `app`, `layout` + `zones`, `creative`, `signage`). The inner shape is the player's format and grows with it. |
| `data.content.kind` | "none" \| "creative" \| "signage" \| "playlist" \| "app" \| "media" \| "layout" |  |
| `data.standbyContent` | ManifestContentBlock | Resolved content: `kind` plus the fields for that kind (`media`, `playlist` + `items`, `app`, `layout` + `zones`, `creative`, `signage`). The inner shape is the player's format and grows with it. |
| `data.standbyContent.kind` | "none" \| "creative" \| "signage" \| "playlist" \| "app" \| "media" \| "layout" |  |
| `data.screensOff` | boolean | True when the rules say the panel should be off now. |
| `data.operatingWindow` | object \| null | Weekly on-hours by weekday key (`mon`…`sun`), or null for always on. |
| `data.proofOfPlay` | boolean |  |
| `data.playbackMode` | "sync" \| "unsync" \| "device-time" |  |
| `data.clock` | object |  |
| `data.clock.timeZone` | string |  |
| `data.language` | string |  |
| `data.location` | object \| null |  |
| `data.playerSettings` | object | Effective player settings (volume, watermark, download policy, …). |
| `data.watermarkText` | string \| null |  |
| `data.accountHold` | object | Present when the account is on hold: the screen shows a hold card, not content. |
| `data.accountHold.status` | string |  |
| `data.accountHold.reason` | string |  |
| `data.kiosk` | object | Screen Lock. The device's PIN material is never returned here — only whether it is set. |
| `data.kiosk.enabled` | boolean |  |
| `data.kiosk.graceSeconds` | integer |  |
| `data.kiosk.hasPin` | boolean |  |
| `data.kiosk.hasRecovery` | boolean |  |
| `data.apkUpdatePolicy` | object |  |
| `data.apkUpdatePolicy.window` | boolean |  |
| `data.apkUpdatePolicy.hourLocal` | integer |  |
| `data.apkUpdatePolicy.forceAfterDays` | integer |  |
| `data.apkUpdatePolicy.windowMinutes` | integer |  |
| `data.apkUpdatePolicy.force` | boolean |  |
| `data.apkUpdate` | object |  |
| `data.apkUpdate.version` | string |  |
| `data.apkUpdate.url` | string |  |
| `data.apkUpdate.sha256` | string |  |
| `data.apkUpdate.signature` | string \| null |  |
| `data.testMode` | true |  |
| `data.testEligible` | true |  |
| `data.fonts` | array of object |  |
| `data.fonts[].family` | string |  |
| `data.fonts[].url` | string |  |
| `data.fonts[].weights` | array of integer |  |
| `data.upcomingBlocks` | array of object | The next scheduled changes, so an offline device can pre-load them. |
| `data.upcomingBlocks[].activatesAt` | string | ISO-8601 timestamp (UTC). |
| `data.upcomingBlocks[].content` | ManifestContentBlock | Resolved content: `kind` plus the fields for that kind (`media`, `playlist` + `items`, `app`, `layout` + `zones`, `creative`, `signage`). The inner shape is the player's format and grows with it. |
| `data.upcomingBlocks[].content.kind` | "none" \| "creative" \| "signage" \| "playlist" \| "app" \| "media" \| "layout" |  |
| `data.upcomingBlocks[].screensOff` | boolean |  |
| `data.nextRefreshAt` | string | ISO-8601 timestamp (UTC). |
| `data.prestagedOverrides` | array of object | Emergency templates pre-loaded on the device. |
| `data.prestagedOverrides[].id` | string |  |
| `data.prestagedOverrides[].kind` | "emergency" \| "cast" |  |
| `data.prestagedOverrides[].severity` | "info" \| "warning" \| "critical" |  |
| `data.prestagedOverrides[].headline` | string |  |
| `data.prestagedOverrides[].body` | string \| null |  |
| `data.prestagedOverrides[].content` | ManifestContentBlock | Resolved content: `kind` plus the fields for that kind (`media`, `playlist` + `items`, `app`, `layout` + `zones`, `creative`, `signage`). The inner shape is the player's format and grows with it. |
| `data.prestagedOverrides[].content.kind` | "none" \| "creative" \| "signage" \| "playlist" \| "app" \| "media" \| "layout" |  |
| `data.prestagedOverrides[].triggeredAt` | string \| null |  |
| `data.prestagedOverrides[].expiresAt` | string \| null |  |
| `data.cast` | object |  |
| `data.cast.id` | string |  |
| `data.cast.kind` | "emergency" \| "cast" |  |
| `data.cast.severity` | "info" \| "warning" \| "critical" |  |
| `data.cast.headline` | string |  |
| `data.cast.body` | string \| null |  |
| `data.cast.content` | ManifestContentBlock | Resolved content: `kind` plus the fields for that kind (`media`, `playlist` + `items`, `app`, `layout` + `zones`, `creative`, `signage`). The inner shape is the player's format and grows with it. |
| `data.cast.content.kind` | "none" \| "creative" \| "signage" \| "playlist" \| "app" \| "media" \| "layout" |  |
| `data.cast.triggeredAt` | string \| null |  |
| `data.cast.expiresAt` | string \| null |  |
| `data.emergency` | object |  |
| `data.emergency.id` | string |  |
| `data.emergency.kind` | "emergency" \| "cast" |  |
| `data.emergency.severity` | "info" \| "warning" \| "critical" |  |
| `data.emergency.headline` | string |  |
| `data.emergency.body` | string \| null |  |
| `data.emergency.content` | ManifestContentBlock | Resolved content: `kind` plus the fields for that kind (`media`, `playlist` + `items`, `app`, `layout` + `zones`, `creative`, `signage`). The inner shape is the player's format and grows with it. |
| `data.emergency.content.kind` | "none" \| "creative" \| "signage" \| "playlist" \| "app" \| "media" \| "layout" |  |
| `data.emergency.triggeredAt` | string \| null |  |
| `data.emergency.expiresAt` | string \| null |  |
| `data.sealed` | true |  |
| `data.brand` | object |  |
| `data.brand.partnerId` | string \| null |  |
| `data.brand.productName` | string |  |
| `data.brand.logoUrl` | string \| null |  |
| `data.brand.brandColor` | string |  |
| `data.brand.cmsHost` | string |  |
| `data.brand.supportEmail` | string |  |
| `data.brand.termsUrl` | string \| null |  |
| `data.brand.privacyUrl` | string \| null |  |
| `data.brand.isWhiteLabel` | boolean |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/replace-device

Unpair a screen's device

Release the device currently paired with a screen, without deleting the screen itself. The screen's name, content, location, and history are kept. Follow up with POST /v1/screens/:id/claim-replacement to pair a new device.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/replace-device" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.awaitingReplacement` | true |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/resource-pressure

Get a screen's resource pressure

Check a screen's storage, memory, and offline cache pressure. The response ranks the content contributing most to that pressure, with suggestions to remove or replace it, and includes hardware upgrade recommendations if relevant.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/resource-pressure" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.severity` | "none" \| "warning" \| "critical" |  |
| `data.resources` | array of "storage" \| "memory" \| "cache" | The resources under pressure. |
| `data.storage` | object |  |
| `data.storage.severity` | "none" \| "warning" \| "critical" |  |
| `data.storage.freeMb` | number \| null |  |
| `data.storage.totalMb` | number \| null |  |
| `data.storage.freeFrac` | number \| null | Free as a fraction of total (0–1). |
| `data.storage.headline` | string \| null |  |
| `data.storage.daysLeft` | number \| null | Days until full at the recent rate; null when not shrinking. |
| `data.memory` | object |  |
| `data.memory.severity` | "none" \| "warning" \| "critical" |  |
| `data.memory.freeMb` | number \| null |  |
| `data.memory.totalMb` | number \| null |  |
| `data.memory.freeFrac` | number \| null | Free as a fraction of total (0–1). |
| `data.memory.headline` | string \| null |  |
| `data.memory.daysLeft` | number \| null | Days until full at the recent rate; null when not shrinking. |
| `data.cache` | object |  |
| `data.cache.severity` | "none" \| "warning" \| "critical" |  |
| `data.cache.cacheableBytes` | integer |  |
| `data.cache.budgetBytes` | integer \| null |  |
| `data.cache.ramTotalMb` | number \| null |  |
| `data.cache.videoCount` | integer |  |
| `data.cache.overBytes` | integer |  |
| `data.cache.headline` | string \| null |  |
| `data.heavyContent` | array of object | Assigned content that costs the most, with what removing it frees. |
| `data.heavyContent[].kind` | "layout" \| "app" \| "media" |  |
| `data.heavyContent[].id` | string |  |
| `data.heavyContent[].name` | string |  |
| `data.heavyContent[].detail` | string |  |
| `data.heavyContent[].freesMb` | number |  |
| `data.heavyContent[].estimated` | boolean |  |
| `data.heavyContent[].freesLabel` | string |  |
| `data.heavyContent[].reason` | string |  |
| `data.hardware` | object \| null | A hardware upgrade suggestion, when the device is the limit. |
| `data.summary` | string \| null |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/restore

Restore a deleted screen

Restore a screen deleted within the last 30 days, and restore its device pairing. A device that has kept its access token reconnects automatically, without needing to be re-paired on site.

Auth: Bearer token. Permission: `screen.delete`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/restore" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.restored` | true |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No deleted screen with this id.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: The screen is not deleted, or restoring it would exceed the plan.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/revoke-device

Revoke a screen's device access

Immediately invalidate the access token of the device currently paired with a screen, for example after a device is lost or stolen. The token can no longer be used, including to pair as a new screen. The screen itself is not deleted.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/revoke-device" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.revoked` | true |  |
| `data.at` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/rotate-token

Rotate a screen's device token

Generate a new access token for a screen's paired device, invalidating the previous one immediately. The new token is returned once in the response and cannot be retrieved afterward.

**Notes.**
- The response carries the NEW device credential. The device holding the old token is disconnected and must be given this token (or re-paired) to keep playing.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/rotate-token" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.deviceToken` | string | The new device credential. Shown once; the old token stops working now. |
| `data.rotatedAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/state

Get a screen's reported device state

Get the screen's most recently reported state, including what is currently displayed, the player software version, and its cache and resource status.

The device's last heartbeat snapshot (telemetry, cache, current content, pending updates). Free-form: fields vary by player shell and version.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/state" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object \| null | Null before the first heartbeat. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such screen in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/telemetry

Get a screen's raw telemetry

Get the 100 most recent raw telemetry events sent by a screen, newest first, with parsed payload data.

**Notes.**
- The newest 100 events.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/telemetry" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of object |  |
| `data[].id` | string |  |
| `data[].spaceId` | string |  |
| `data[].screenId` | string |  |
| `data[].type` | string |  |
| `data[].payload` | any | The event payload (decoded JSON); its shape depends on `type`. |
| `data[].occurredAt` | string | ISO-8601 timestamp (UTC). |
| `data[].receivedAt` | string | ISO-8601 timestamp (UTC). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/trigger

Trigger a screen interaction

Send a real-time trigger to a screen: `refresh`, `next`, `go-to-scene`, or `send-event`. The screen receives it immediately.

Auth: Bearer token. Permission: `screen.edit`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | "refresh" \| "next" \| "prev" \| "go-to-page" \| "go-to-scene" \| "send-event" | yes |  |
| `page` | integer | no | `go-to-page`: 1–100. |
| `sceneId` | string | no | `go-to-scene`: the scene to show. |
| `event` | string | no | `send-event`: the event name the content listens for. |
| `payload` | string | no | `send-event`: an optional string payload. |
| `overlay` | object | no | A popup shown over what is playing for `seconds`, then removed. The content underneath does not restart. |
| `overlay.contentKind` | "media" \| "app" \| "canvas" | yes |  |
| `overlay.contentId` | string | yes |  |
| `overlay.seconds` | number | no | 1–600 (clamped); default 15. |
| `overlay.position` | "center" \| "top" \| "bottom" \| "top-left" \| "top-right" \| "bottom-left" \| "bottom-right" | no | Default `center`. |
| `overlay.params` | object | no | Values passed to the content, e.g. `{ "table": "4" }`. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/trigger" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string | The command id. |
| `data.kind` | "trigger" |  |
| `data.trigger` | object | The trigger as stored (only the fields its kind uses). |
| `data.trigger.kind` | string |  |
| `data.trigger.page` | integer |  |
| `data.trigger.sceneId` | string |  |
| `data.trigger.event` | string |  |
| `data.trigger.payload` | string |  |
| `data.overlay` | object |  |
| `data.overlay.contentKind` | "media" \| "app" \| "canvas" |  |
| `data.overlay.contentId` | string |  |
| `data.overlay.seconds` | integer | 1–600; default 15. |
| `data.overlay.position` | "center" \| "top" \| "bottom" \| "top-left" \| "top-right" \| "bottom-left" \| "bottom-right" |  |
| `data.overlay.params` | object | Values passed to the content, e.g. `{ "table": "4" }`. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Not a valid trigger, or the overlay content is not in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/{id}/unpower-and-delete

Power off and delete a screen

Retire a screen in one action: turn off the physical display, unpair the device, and delete the screen. The screen can be recovered within 30 days.

Auth: Bearer token. Permission: `screen.delete`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/{id}/unpower-and-delete" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.id` | string |  |
| `data.deleted` | true |  |
| `data.commandId` | string | The `cec-off` command sent before the device was unpaired. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/up-next

Get a screen's up-next schedule

Get the current running order and the next scheduled change for a screen, based on exactly what the screen itself will play. This endpoint is relatively expensive to compute and should be called on demand rather than polled regularly.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/up-next" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.now` | object \| object \| null | What is on the glass now; null when nothing is. |
| `data.overriddenBy` | object \| null |  |
| `data.rotation` | array of object | The running order under any override, in play order. |
| `data.rotation[].name` | string |  |
| `data.rotation[].kind` | string |  |
| `data.rotation[].seconds` | number \| null | Fixed dwell; null when the item plays for its own length. |
| `data.loops` | boolean |  |
| `data.nextChange` | object \| null |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/{id}/why

Explain what a screen shows and why

Get a plain-language explanation of why a screen is currently showing what it is showing. The explanation follows the order of precedence: emergency content, then manually assigned content, then scheduled content, then the default, including reasons such as a missing asset or a revoked share.

The resolution chain (deactivated → emergency → cast → assigned content), what the device reports on the glass, and warnings.

Auth: Bearer token. Permission: `screen.view`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Screen id. |

```bash
curl "https://api.brixsignage.com/v1/screens/{id}/why" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.summary` | string |  |
| `data.currentContent` | object |  |
| `data.currentContent.kind` | string | The winning layer, or `none`. |
| `data.currentContent.id` | string \| null |  |
| `data.currentContent.name` | string \| null |  |
| `data.renderTruth` | object |  |
| `data.renderTruth.verdict` | string |  |
| `data.renderTruth.ok` | boolean |  |
| `data.renderTruth.problem` | boolean |  |
| `data.renderTruth.fault` | "user" \| "platform" \| null |  |
| `data.renderTruth.reason` | string \| null |  |
| `data.layers` | array of object |  |
| `data.layers[].layer` | string |  |
| `data.layers[].active` | boolean |  |
| `data.layers[].reason` | string |  |
| `data.layers[].contentName` | string |  |
| `data.layers[].startedAt` | string | ISO-8601 timestamp (UTC). |
| `data.layers[].expiresAt` | string | ISO-8601 timestamp (UTC). |
| `data.warnings` | array of string |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such screen in this workspace.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/bulk-assign

Set what many screens play

Assign one piece of content to many screens in a single request. The content is validated the same way as in the single-screen assign endpoint. Screen IDs outside your access scope or belonging to another workspace are dropped; screens at locations not shared with the content are skipped and counted in the response. This action is recorded as a single entry in the activity log.

Assigns one piece of content to many screens. Unknown or out-of-reach screen ids are silently dropped; screens whose location the content is not shared to are skipped and counted in `notShared`.

Auth: Bearer token. Permission: `screen.cast`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `screenIds` | array of string | yes |  |
| `contentKind` | "playlist" \| "schedule" \| "layout" \| "creative" \| "app" \| "media" \| "signage" | yes |  |
| `contentId` | string | yes |  |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/bulk-assign" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.updated` | integer |  |
| `data.notShared` | integer |  |
| `data.screenIds` | array of string | The screens actually assigned. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No such content.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Empty/oversized `screenIds`, invalid kind, or unplayable content.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/bulk-settings

Apply settings to many screens

Apply a Display Profile, a bundle of player settings, to many screens at once. You can also set placement fields such as `nodeId`, `location`, and `tags` for the screens in the same request.

**Notes.**
- A `rotation`, `displayPowerMode` or `playbackMode` value outside its set is ignored, not refused.

Auth: Bearer token. Permission: `screen.edit`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `screenIds` | array of string | yes | Screens to change. Ids you cannot edit are skipped. |
| `settings` | object | yes |  |
| `settings.rotation` | 0 \| 90 \| 180 \| 270 | no |  |
| `settings.timezone` | string \| null | no | IANA time zone; null clears it. |
| `settings.operatingHours` | string | no |  |
| `settings.displayPowerMode` | "always-on" \| "follow-schedule" \| "os-default" | no |  |
| `settings.playbackMode` | "sync" \| "unsync" \| "device-time" | no |  |
| `settings.powerPolicyId` | string \| null | no |  |
| `settings.nodeId` | string \| null | no | Move the screens to this location (needs screen.edit there). |
| `settings.location` | object \| null | no |  |
| `settings.tags` | array of string | no | Replaces each screen's tags. |
| `settings.playerSettings` | object | no | Merged into each screen's player settings. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/bulk-settings" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.updated` | integer |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: The destination location does not exist.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Invalid time zone or screen list.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/claim

Pair a device by its code

Pair a device using its 6-digit pairing code, creating a new screen. Provide `code`, `name`, and an optional `nodeId`. This endpoint is rate-limited because the pairing code space is small, and is refused if your account is suspended or cancelled.

Claims the device showing a 6-digit pairing code into this workspace as a new screen.

Auth: Bearer token. Permission: `screen.create`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `code` | string | yes | The 6-digit code the device shows. |
| `name` | string | no | Screen name. Default "New screen". |
| `nodeId` | string | no | Location to file the screen under. |
| `billingGroupId` | string | no |  |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/claim" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.screenId` | string |  |
| `data.name` | string |  |
| `data.previousScreen` | object \| null | The screen this same device was last paired to in this workspace, if any. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: `invalid_code`: no device is waiting with that code.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: `ambiguous_code` or `already_claimed`.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 410: `code_expired`.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: `code` missing, or a foreign location.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 429: Too many failed codes; honour `Retry-After`.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/commands

Send a command to many screens

Queue the same command across many screens in a single request, using `screenIds`, `kind`, and an optional `payload`. Screen IDs you do not have permission to edit are silently dropped from the request rather than causing an error. Use this instead of sending one request per screen.

Queues one command on many screens. Unknown or out-of-reach ids are silently dropped. Risky kinds (set-proxy, set-input, rs232, add-wifi) on a large cohort go to a small canary batch first and the rest are staged.

**Notes.**
- When no requested screen is reachable the answer is 200 (not 201) with `{ issued: 0, commandIds: [] }`.

Auth: Bearer token. Permission: `screen.edit`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | string | yes |  |
| `payload` | object | no | Per-kind payload: `set-volume` {level 0-100}, `set-brightness` {level}, `set-mute` {muted}, `set-input` {input}, … Most kinds take none. |
| `screenIds` | array of string | yes |  |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/commands" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.issued` | integer |  |
| `data.commandIds` | array of string |  |
| `data.staged` | integer | Canary rollouts: commands held behind the canary. |
| `data.rolloutId` | string |  |
| `data.canary` | true |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: Unknown kind, invalid payload, or empty/oversized `screenIds`.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/count-by-status

Count screens by status

Get a count of screens by status: online, offline, and unpaired. Counts are limited to the organization nodes you can see.

**Notes.**
- Returns the counts at the top level — NOT wrapped in `{ data }` like other routes.

Auth: Bearer token. Permission: `screen.view`.

```bash
curl "https://api.brixsignage.com/v1/screens/count-by-status" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `online` | integer |  |
| `offline` | integer |  |
| `degraded` | integer | Always 0 today (reserved). |
| `unpaired` | integer | Screens in `pairing` status. |
| `obstructed` | integer | Online screens with a system dialog covering them (also counted in `online`). |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/enroll

Enroll a screen programmatically

Create a screen and pair it in a single call using a workspace API key, without a 6-digit pairing code. This is intended for automated device provisioning. The returned device token is shown once and cannot be retrieved again. The screen is created in the workspace the API key belongs to; an API key scoped to one organization node creates the screen under that node, while an account-wide key can target any node in the account.

Auth: Bearer token. Permission: `screen.create`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | Default `New screen`. |
| `nodeId` | string | no |  |
| `billingGroupId` | string \| null | no |  |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/enroll" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 201: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.screenId` | string |  |
| `data.name` | string |  |
| `data.status` | string |  |
| `data.deviceToken` | string | The device credential. Shown ONCE: install it on the device; it cannot be read again. |
| `data.space` | object |  |
| `data.space.id` | string |  |
| `data.space.name` | string \| null |  |
| `data.node` | object \| null |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: The location does not exist.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: The account is suspended or cancelled.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 429: More than 60 enrollments a minute.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/pending

List pending discovered devices

List devices automatically discovered on the same network as your workspace's existing screens, newest first. Each entry includes its 6-digit pairing code.

**Notes.**
- Only devices on the caller's own network (same public IPv4, or the same IPv6 /64) are listed, so call it from the network the devices are on.

Auth: Bearer token. Permission: `screen.create`.

```bash
curl "https://api.brixsignage.com/v1/screens/pending" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of object |  |
| `data[].pairingId` | string |  |
| `data[].code` | string | The 6-digit code the device shows. |
| `data[].ordinal` | integer | 1 = newest; matches the number the device shows when identified. |
| `data[].createdAt` | string | ISO-8601 timestamp (UTC). |
| `data[].expiresAt` | string | ISO-8601 timestamp (UTC). |
| `data[].deviceLabel` | string \| null |  |
| `data[].zmScreenName` | string \| null | The screen name the device carried over from a previous signage system, when it has one. |
| `data[].zmMachineId` | string \| null |  |
| `data[].previousScreen` | object \| null | A screen in this workspace this device was paired to before; null for a new device. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/pending/{id}/dismiss

Dismiss a pending device

Dismiss a discovered device that you do not want to claim. Its current pairing code expires and the device generates a new one. The dismissed entry is kept in the activity history.

Auth: Bearer token. Permission: `screen.create`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Pending pairing id (`pairingId` from the pending list). |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/pending/{id}/dismiss" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.ok` | true |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No pending device with this id on the caller's network.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/pending/{id}/identify

Identify a pending device

Briefly flash a discovered device's display so you can tell which physical screen it is. This only works for devices discovered on your own network; an ID from another network returns a not-found error.

Auth: Bearer token. Permission: `screen.create`.

Parameters:

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | Pending pairing id (`pairingId` from the pending list). |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/pending/{id}/identify" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.ok` | true |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 404: No pending device with this id on the caller's network.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/pending/claim

Claim multiple discovered devices as screens in one request, using `items`, an array of objects with `pairingId`, `name`, and an optional `nodeId`. To claim a device from a different network using its 6-digit code, use POST /v1/screens/claim instead. This request is refused if your account is suspended or cancelled.

**Notes.**
- Items that are not pending on the caller's network are skipped, not refused; `claimed` lists the ones that became screens.

Auth: Bearer token. Permission: `screen.create`.

Request body (`application/json`):

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `items` | array of object | yes |  |
| `items[].pairingId` | string | yes |  |
| `items[].name` | string | no |  |
| `items[].nodeId` | string | no | Location for the new screen. |

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/pending/claim" \
  -H "Authorization: Bearer $BRIX_API_KEY" \
  -H "Content-Type: application/json"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.claimed` | array of object |  |
| `data.claimed[].pairingId` | string |  |
| `data.claimed[].screenId` | string |  |
| `data.claimed[].name` | string |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 409: The account is suspended or cancelled.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 422: `items` is empty.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## POST /v1/screens/pending/identify-all

Identify all pending devices

Briefly flash the displays of every discovered device on your network at once. This is useful when several new devices appear at the same time.

Auth: Bearer token. Permission: `screen.create`.

```bash
curl -X POST "https://api.brixsignage.com/v1/screens/pending/identify-all" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | object |  |
| `data.count` | integer | Devices asked to show their number. |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

## GET /v1/screens/recycle-bin

List deleted screens

List screens deleted within the last 30 days that can still be restored with POST /v1/screens/:id/restore. For deleted items across all types, use GET /v1/recycle-bin.

**Notes.**
- Screens deleted in the last 30 days; older ones are purged.

Auth: Bearer token. Permission: `screen.view`.

```bash
curl "https://api.brixsignage.com/v1/screens/recycle-bin" \
  -H "Authorization: Bearer $BRIX_API_KEY"
```

Response 200: Success.

| Field | Type | Description |
| --- | --- | --- |
| `data` | array of object |  |
| `data[].kind` | "screen" |  |
| `data[].id` | string |  |
| `data[].name` | string |  |
| `data[].deletedAt` | string | ISO-8601 timestamp (UTC). |
| `data[].nodeId` | string \| null |  |

Response 401: Missing, expired or revoked bearer token.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 403: The token lacks the permission this operation needs (see `x-brix-permission`).

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |

Response 5XX: Server error. The body carries a `requestId` to quote to support.

| Field | Type | Description |
| --- | --- | --- |
| `error` | string | Machine-readable code: `unauthorized`, `forbidden`, `not_found`, `validation_error`, `conflict`, `rate_limited`, `internal_error`, … |
| `message` | string | Human-readable explanation. Safe to show an operator. |
| `requestId` | string | Present on 5xx: quote it to support. |
