# Brix developer documentation (full text) > Brix is cloud digital signage software. This file is every page of the Brix developer docs as plain markdown, followed by the complete REST API reference. Use it to call the Brix API or connect an agent over MCP. Docs: https://brixsignage.com/developers/ · Index: https://brixsignage.com/llms.txt · OpenAPI: https://api.brixsignage.com/v1/openapi.json # Brix developer docs > Control Brix digital signage from code or an AI agent. REST API, MCP server, webhooks, API keys and a full reference generated from the OpenAPI spec. Source: https://brixsignage.com/developers/ Brix is cloud digital signage software. Everything the Brix CMS does goes through the same public API, so a script, an integration or an AI agent can do it too: list screens, upload media, build playlists, schedule content, take over screens for a set time, and read proof of play and uptime. ## Three ways in | Way in | Use it when | Start here | | --- | --- | --- | | REST API | You write code: a sync job, a POS or HR integration, a report. | [Quickstart](/developers/quickstart/) | | MCP server | You want Claude, ChatGPT, Cursor or another agent to operate your screens. | [Connect an AI agent (MCP)](/developers/mcp/) | | SDK and CLI | Typed clients and a command-line tool. | Coming. Use the REST API or MCP for now. | Both the REST API and the MCP server use the same API keys, the same permissions and the same workspace boundary. claude.ai and ChatGPT can also connect to the MCP server by signing in, with no key. ## The basics - Base URL: `https://api.brixsignage.com`. Every path starts with `/v1`. - Auth: `Authorization: Bearer `. Create keys in the CMS at **Settings > API & MCP**. See [Authentication and scopes](/developers/authentication/). - Success bodies are `{ "data": ... }`. Errors are `{ "error": "", "message": "" }`. See [Errors and rate limits](/developers/errors-and-rate-limits/). - Default rate limit: 600 requests per minute per workspace, across all keys. - Every workspace can create API keys. There is no separate API plan and no per-call charge. - MCP endpoint: `https://api.brixsignage.com/v1/mcp`. ```bash curl https://api.brixsignage.com/v1/me \ -H "Authorization: Bearer $BRIX_API_KEY" ``` ## Pages - [Quickstart](/developers/quickstart/): create a key, make a first call, list screens, cast content to a screen. - [Authentication and scopes](/developers/authentication/): key format, permissions, location-pinned keys, OAuth client credentials. - [Errors and rate limits](/developers/errors-and-rate-limits/): the error envelope, error codes, rate-limit headers, retries, idempotency. - [Pagination](/developers/pagination/): cursor pagination on list endpoints. - [Connect an AI agent (MCP)](/developers/mcp/): Claude Code, Claude Desktop, Cursor, ChatGPT and the Claude API, plus the full tool catalog. - [Webhooks](/developers/webhooks/): subscribe to events, verify signatures, retries. - [API reference](/developers/api/): every customer endpoint, grouped by resource. ## For agents and LLMs - [/llms.txt](/llms.txt): a short index of Brix, with links to these pages. - [/llms-full.txt](/llms-full.txt): every developer page and the full API reference as one markdown file. - Every page here has a markdown twin. Add `.md` to the path without the trailing slash, for example [/developers/quickstart.md](/developers/quickstart.md). - OpenAPI 3.1 spec: [https://api.brixsignage.com/v1/openapi.json](https://api.brixsignage.com/v1/openapi.json). A copy is also served at [https://brixsignage.com/openapi.json](/openapi.json). # Quickstart > Create a Brix API key, make your first request with curl, list your screens and cast a playlist to a screen for an hour. Copy-paste commands. Source: https://brixsignage.com/developers/quickstart/ This page takes you from nothing to content on a screen. You need a Brix workspace with at least one paired screen, and `curl`. Every command runs against the live API. ## 1. Create an API key 1. Sign in to the CMS at [cms.brixsignage.com](https://cms.brixsignage.com). 2. Open **Settings > API & MCP**. 3. Select **New key**. 4. Type a name into **Name**, for example `Quickstart`. 5. Under **Permissions**, select **Screens** and **Playlists**. Leave **Limit to a location** empty. 6. Select **Create key**. 7. Copy the key from the panel. Brix shows the full key once. A key looks like `ak_us_live_` followed by 32 characters. The second part is your workspace's data region: `us`, `eu`, `oc` or `apac`. You need the `api-key.create` permission to make a key. Workspace Owners and Admins have it. A key never gets a permission that the person who creates it does not hold. Store the key in an environment variable: ```bash export BRIX_API_KEY="ak_us_live_..." ``` ## 2. Check who you are ```bash curl https://api.brixsignage.com/v1/me \ -H "Authorization: Bearer $BRIX_API_KEY" ``` ```json { "data": { "actor": { "kind": "key", "id": "ak_...", "name": "Quickstart", "nodeId": null }, "workspace": { "id": "...", "name": "Acme Coffee" }, "permissions": ["screen.view", "screen.cast", "playlist.view", "..."] } } ``` `permissions` is what this key may do. If a later call returns 403 or an empty list, check this first. A missing header returns `401 {"error":"missing_credentials",...}`. A wrong, revoked or expired key returns `401 {"error":"invalid_credentials",...}`. ## 3. List your screens ```bash curl "https://api.brixsignage.com/v1/screens?limit=50" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` ```json { "data": [ { "id": "scr_...", "name": "Lobby", "status": "online", "contentKind": "playlist", "contentId": "pl_...", "lastSeenAt": "..." } ], "nextCursor": null } ``` The response is trimmed here. Copy the `id` of the screen you want to use. When `nextCursor` is not null, there are more screens: see [Pagination](/developers/pagination/). With [jq](https://jqlang.org), print id, name and status: ```bash curl -s "https://api.brixsignage.com/v1/screens?limit=500" \ -H "Authorization: Bearer $BRIX_API_KEY" | jq -r '.data[] | [.id, .name, .status] | @tsv' ``` ## 4. Find a playlist ```bash curl -s https://api.brixsignage.com/v1/playlists \ -H "Authorization: Bearer $BRIX_API_KEY" | jq -r '.data[] | [.id, .name] | @tsv' ``` Copy the `id` of a playlist. ## 5. Cast the playlist to the screen for one hour A cast is a timed takeover. The screens play your content until `expiresAt`, then go back to their normal schedule by themselves. ```bash SCREEN_ID="scr_..." PLAYLIST_ID="pl_..." EXPIRES_AT=$(( $(date +%s) * 1000 + 3600000 )) # one hour from now, epoch milliseconds curl -X POST https://api.brixsignage.com/v1/casts \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"contentKind\": \"playlist\", \"contentId\": \"$PLAYLIST_ID\", \"scopeKind\": \"screens\", \"screenIds\": [\"$SCREEN_ID\"], \"expiresAt\": $EXPIRES_AT }" ``` The API answers `201` with the cast: ```json { "data": { "id": "cast_...", "kind": "cast", "status": "active", "contentKind": "playlist", "contentId": "pl_...", "scopeKind": "screens", "screenCount": 1, "triggeredAt": "...", "expiresAt": "..." } } ``` Keep the cast id for the next step. With jq, run the same request with `curl -s ... | jq -r .data.id` and store it: ```bash CAST_ID="cast_..." # data.id from the response above ``` Always send `scopeKind`. If you leave it out, the cast goes to **every screen** the key can reach. | Field | Values | | --- | --- | | `contentKind` | `media`, `playlist`, `app`, `creative`, `schedule` | | `contentId` | The id of that content. | | `scopeKind` | `screens` (with `screenIds`), `node` (with `scopeNodeId`, a location and everything under it), or `all`. Default `all`. | | `expiresAt` | ISO 8601 date-time or epoch milliseconds. Omit it and the cast stays until you clear it. | | `contentName` | Optional label shown in the CMS. | The key needs `screen.cast`. A newer cast on the same screens replaces an older one. An emergency takeover pre-empts a cast, and the cast resumes after it. ## 6. End the cast early ```bash curl -X POST https://api.brixsignage.com/v1/casts/$CAST_ID/clear \ -H "Authorization: Bearer $BRIX_API_KEY" ``` The screens go back to their scheduled content at once. Clearing a cast that already ended returns `409 already_inactive`. To see what is on air now: ```bash curl "https://api.brixsignage.com/v1/casts?status=active" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` ## Change what a screen plays by default A cast is temporary. To change the content a screen plays every day, assign it: ```bash curl -X POST https://api.brixsignage.com/v1/screens/$SCREEN_ID/assign \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"contentKind\": \"playlist\", \"contentId\": \"$PLAYLIST_ID\"}" ``` `contentKind` is one of `playlist`, `schedule`, `layout`, `creative`, `signage`, `app`, `media`. Send `"contentKind": null` to clear the assignment. For many screens at once, use `POST /v1/screens/bulk-assign` with `screenIds`. ## Upload a file ```bash curl -X POST https://api.brixsignage.com/v1/media/upload \ -H "Authorization: Bearer $BRIX_API_KEY" \ -F "file=@menu.jpg" \ -F "name=Winter menu" ``` The key needs `media.create`. The response is `201` with the new media item in `data`. Use its `id` as `contentId` with `"contentKind": "media"`. For files over about 100 MB, use the multipart upload routes under `/v1/media/upload/multipart/`. To import from a public URL, use `POST /v1/media/import-url`. SVG files are refused. ## Next - [Authentication and scopes](/developers/authentication/) for least-privilege keys. - [Errors and rate limits](/developers/errors-and-rate-limits/) before you ship an integration. - [API reference](/developers/api/) for every endpoint. - [Connect an AI agent (MCP)](/developers/mcp/) to do all of this from Claude or ChatGPT. # Authentication and scopes > How Brix API keys work: key format, the Bearer header, resource.verb permissions, location-pinned keys, rotation, revocation and OAuth client credentials. Source: https://brixsignage.com/developers/authentication/ Every request carries one API key as a Bearer token: ```bash curl https://api.brixsignage.com/v1/me \ -H "Authorization: Bearer $BRIX_API_KEY" ``` The key decides everything: which workspace the request acts on, what it may do, and which locations it can reach. There is no workspace header and no workspace id in the path. ## Key format `ak__live_<32 characters>`, for example `ak_eu_live_...`. - `region` is the workspace's data region: `us`, `eu`, `oc` or `apac`. The API routes the request to that region. All regions use the same host, `https://api.brixsignage.com`. - Keys made before regions were tagged look like `ak_live_...` and belong to `us`. - The CMS shows only a preview (the first 15 characters). Brix stores a SHA-256 hash, never the key itself. Brix cannot show you a lost key. Rotate it instead. - Keys are for server-side use. Browser calls from other origins are refused by CORS. Do not put a key in front-end code. ## Create a key In the CMS: **Settings > API & MCP > New key**. | Field | What it does | | --- | --- | | **Name** | A label, for example `Roster sync - PowerSchool`. | | **Limit to a location (optional)** | Pins the key to one location and every location under it. Leave it empty for the whole workspace. | | **Full access** | Every permission. Shown only to people who hold every permission. | | **Permissions** | Areas such as Screens, Playlists, Files. Each area grants every action in that area that you hold. | Select **Create key**, then copy the key from the panel. It is shown once. Rules: - You need `api-key.create` to make a key, `api-key.view` to list keys and `api-key.delete` to revoke one. - A key never gets a permission its creator does not hold: `403 "A key can't be granted permissions you don't hold."` - A key for the whole workspace needs its permissions at the workspace level. Otherwise pin it to a location you control. - Every workspace can create keys. There is no plan gate. You can also manage keys over the API: ```bash # Create (returns the secret once, in data.secret) curl -X POST https://api.brixsignage.com/v1/api-keys \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Nightly report", "permissions": ["screen.view", "playback-log.view"], "expiresInDays": 90}' ``` | Field | Notes | | --- | --- | | `name` | Required. | | `permissions` | An array of `resource.verb` strings, or `"all"`. | | `nodeId` | Optional. Pins the key to that location and everything under it. | | `expiresInDays` | Optional, 1 to 3650. Omit it for a key that does not expire. The CMS does not set an expiry. | A key's record has `id`, `name`, `preview`, `permissions`, `nodeId`, `lastUsedAt`, `expiresAt`, `expired`, `revokedAt` and `createdAt`. The secret is never returned again after create or rotate. ## Rotate and revoke - **Rotate**: `POST /v1/api-keys/{id}/rotate`, or **Rotate** in the CMS. Returns a new secret once. The old secret stops working at once. Name, permissions, location and expiry stay the same. You cannot rotate a revoked key (`409 revoked`). - **Revoke**: `DELETE /v1/api-keys/{id}`, or **Revoke** in the CMS. Every integration that uses the key stops at once. This cannot be undone. An expired or revoked key gets `401 invalid_credentials`, the same as an unknown key. ## Permissions (scopes) A permission is `resource.verb`. Every endpoint requires exactly one, shown in the [API reference](/developers/api/) and in the spec as `x-brix-permission`. A missing permission returns: ```json { "error": "forbidden", "message": "Missing permission: screen.cast" } ``` | Resource | Verbs | | --- | --- | | `screen` | view, create, edit, delete, cast, manage-permissions | | `playlist`, `schedule` | view, create, edit, delete, approve, publish, share, manage-permissions | | `layout`, `creative` | view, create, edit, delete, approve, publish, share | | `media` (Files) | view, create, edit, delete, share, export | | `font` | view, create, delete | | `app-instance` | view, create, edit, delete, share | | `brand-kit` | view, edit | | `billing` | view, edit, export | | `recycle-bin` | view, edit, delete | | `audit-log`, `playback-log` (Proof of Play) | view, export | | `org-unit`, `user`, `permission-group` (Roles), `tag`, `alert-rule` | view, create, edit, delete | | `api-key` | view, create, delete | | `integration` | view, create, edit, delete | | `data-source` | delete | | `settings` | view, edit | | `quick-post` | view, create, approve, publish | | `emergency-override` | view, create, edit, approve, publish | | `import` | create | Verb meanings: `cast` puts content on a screen. `publish` means publish without approval. `share` means share to other locations. Common sets: | Integration | Permissions | | --- | --- | | Read-only dashboard or AI assistant | `screen.view`, `playlist.view`, `schedule.view`, `media.view`, `playback-log.view` | | Menu or price sync | `media.view`, `media.create`, `media.edit`, `playlist.view`, `playlist.edit` | | Timed takeovers | `screen.view`, `screen.cast`, plus `.view` on the content you cast | | Webhook management | `integration.view`, `integration.edit` | ## Keys pinned to a location A key with a location (`nodeId`) sees and changes only that location and the locations under it. Anything outside returns 404, not 403. `GET /v1/me` shows the pin in `actor.nodeId`. An empty list or an unexpected 404 from a pinned key usually means the resource is outside its branch. ## Tenancy Every request resolves to one workspace. A resource that belongs to another workspace returns 404, never 403, so a response never confirms that something exists elsewhere. ## OAuth client credentials For tools that expect OAuth, exchange a key for a short-lived access token. The client id is the key's id (`ak_...`, from the key list), and the client secret is the key itself. ```bash curl -X POST https://api.brixsignage.com/v1/oauth/token \ -u "$BRIX_KEY_ID:$BRIX_API_KEY" \ -d grant_type=client_credentials ``` ```json { "access_token": "bat_...", "token_type": "Bearer", "expires_in": 3600, "scope": "..." } ``` - Send `access_token` as `Authorization: Bearer bat_...`. It lasts 3600 seconds. - The token carries the key's permissions. Revoking or rotating the key ends the token. - Credentials can go in HTTP Basic (`client_secret_basic`) or in the form body (`client_secret_post`). - Only the `client_credentials` grant exists. There is no authorization-code flow and no consent screen. - The token endpoint allows 30 requests per 5 minutes per IP address. - Discovery: `GET https://api.brixsignage.com/.well-known/oauth-authorization-server`. # Errors and rate limits > The Brix API response envelope, error codes, the 600 requests per minute workspace limit, RateLimit headers, retries and idempotency keys. Source: https://brixsignage.com/developers/errors-and-rate-limits/ ## Response envelope Success: ```json { "data": { "id": "scr_...", "name": "Lobby" } } ``` List endpoints return an array in `data`. Paginated lists add `nextCursor` (see [Pagination](/developers/pagination/)). Endpoints that serve bytes, such as a media file or an invoice PDF, return the bytes. Error: ```json { "error": "validation_error", "message": "name is required" } ``` - `error` is a stable, machine-readable code. Branch on it. - `message` is for people. It can change. It is sometimes absent, for example `{"error":"not_found"}`. - 5xx errors add `requestId`. Quote it to support. ## Status codes and error codes | HTTP | `error` | Meaning | | --- | --- | --- | | 401 | `missing_credentials` | No `Authorization: Bearer` header. | | 401 | `invalid_credentials` | Key unknown, revoked or expired. | | 403 | `forbidden` | The key lacks the permission: `Missing permission: `. | | 403 | `not_shared` | The content is not shared to that screen's location. Share it there first. | | 404 | `not_found` | Does not exist, is outside the key's location, or belongs to another workspace. | | 409 | `revoked` | The key is revoked. Create a new key. | | 409 | `already_inactive` | The cast or takeover already ended. | | 409 | `idempotency_in_progress` | A request with the same `Idempotency-Key` is still running. | | 413 | `too_large` | The body or file is over the limit. | | 415 / 422 | `unsupported_type` | The file type is not accepted. | | 422 | `validation_error` | A field is missing or invalid. `message` names it. | | 422 | `invalid_node` | `nodeId is not a node in this account.` | | 422 | `invalid_folder` | The folder does not exist in this workspace. | | 422 | `content_unplayable` | The content cannot play, for example a file still processing. | | 422 | `no_screens` | The target matched no screens the key can reach. | | 429 | `rate_limited` | Over the rate limit. Wait `Retry-After` seconds. | | 500 | `internal_error` | `Something went wrong on our side.` Retry with backoff. Quote `requestId`. | | 503 | `database_busy` | Retry after `Retry-After` (2 seconds). | An unknown path returns `404 {"error":"not_found","message":"No route here."}`. ## Rate limits - **600 requests per minute per workspace**, shared by all its API keys, in fixed 60-second windows. - The limit applies to API-key traffic. People using the CMS do not count against it. - Brix support can raise the limit for a workspace. `GET /v1/account/limits` returns the workspace's limits. - Some routes also have their own limit. `POST /v1/media/upload` allows 300 per minute per key. The MCP endpoint allows 30 per minute per caller. The OAuth token endpoint allows 30 per 5 minutes per IP address. Every API-key response carries the IETF `RateLimit` headers: ``` RateLimit-Limit: 600 RateLimit-Remaining: 587 RateLimit-Reset: 41 RateLimit-Policy: 600;w=60 ``` `RateLimit-Reset` is seconds until the window resets. Over the limit, the API returns `429` with `Retry-After`: ```json { "error": "rate_limited", "message": "This workspace has used its 600 requests per minute. Wait 12 seconds and try again.", "limit": 600, "windowSeconds": 60, "retryAfterSeconds": 12 } ``` ## Retries - Retry `429`, `500`, `503` and network errors. Wait `Retry-After` when it is present, otherwise back off exponentially. - Do not retry other `4xx` responses. Fix the request. - Send an `Idempotency-Key` header on creates you might retry (screens, playlists, schedules, media upload). A repeat with the same key returns the first success instead of creating a second item. ```bash curl -X POST https://api.brixsignage.com/v1/playlists \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 7c1d0f5e-nightly-2026" \ -d '{"name": "Nightly specials"}' ``` ## Request ids Send `x-brix-request-id` (letters, digits, `.`, `_`, `-`, up to 120 characters) to tag a request with your own trace id. Quote it to support with the time of the call. ## Versioning and deprecation The version is in the path: `/v1`. Brix announces a retiring route at least 90 days ahead. Calls to it carry `Deprecation` and `Sunset` headers, and `GET /v1/status/deprecations` (no auth) lists every retiring route, its end date and its replacement. # Pagination > Brix list endpoints use opt-in cursor pagination: send limit, follow nextCursor until it is null. Which endpoints page, their limits, and a full loop. Source: https://brixsignage.com/developers/pagination/ Brix uses cursor pagination. There are no page numbers. ## How it works 1. Send `?limit=N` on a list endpoint. This turns pagination on. 2. The response has `data` and `nextCursor`. 3. Send `?cursor=` with the same `limit` to get the next page. 4. Stop when `nextCursor` is `null`. ```json { "data": [ { "id": "scr_..." } ], "nextCursor": "scr_..." } ``` - Treat the cursor as opaque. Pass it back as it came. - A page can hold fewer than `limit` items even when more follow, for example with a key pinned to one location. Always follow `nextCursor`, do not stop on a short page. - Without `limit`, these endpoints return the full list in one response. Use `limit` for large workspaces. ## Endpoints | Endpoint | `limit` | Order | Notes | | --- | --- | --- | --- | | `GET /v1/screens` | 1 to 500 | by id | | | `GET /v1/media` | default 100, max 500 | newest first | `?search=`, `?kind=`, `?state=`, `?folderId=` filters. `?count=1` adds `total`. | | `GET /v1/media-folders`, `/v1/app-instances`, `/v1/creatives`, `/v1/signage-templates`, `/v1/layouts`, `/v1/banners`, `/v1/alert-rules`, `/v1/celebration-entries` | default 100, max 500 | newest first | `?search=` and `?count=1` as above. | | `GET /v1/webhooks/deliveries` | default 50, max 200 | newest first | `?endpointId=`, `?status=` | | `GET /v1/casts` | default 200, max 500 | newest first | `?status=active`. No cursor. | | `GET /v1/playlists`, `GET /v1/schedules` | none | | Return the full list. | ## Loop over every screen ```bash cursor="" while :; do page=$(curl -s "https://api.brixsignage.com/v1/screens?limit=500${cursor:+&cursor=$cursor}" \ -H "Authorization: Bearer $BRIX_API_KEY") echo "$page" | jq -r '.data[] | [.id, .name, .status] | @tsv' cursor=$(echo "$page" | jq -r '.nextCursor // empty') [ -z "$cursor" ] && break done ``` The same loop in JavaScript: ```js async function* allScreens(key) { let cursor = null; do { const url = new URL('https://api.brixsignage.com/v1/screens'); url.searchParams.set('limit', '500'); if (cursor) url.searchParams.set('cursor', cursor); const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } }); if (!res.ok) throw new Error(`${res.status} ${(await res.json()).error}`); const page = await res.json(); yield* page.data; cursor = page.nextCursor; } while (cursor); } ``` # Connect an AI agent (MCP) > Connect Claude, ChatGPT, Claude Code, Cursor or the Claude API to Brix over MCP. Sign in from the app or use an API key. Endpoint, setup and tools. Source: https://brixsignage.com/developers/mcp/ Brix runs a Model Context Protocol (MCP) server. Connect an agent once and it can find your screens, explain why one is blank, cast content, build playlists and pull proof-of-play and uptime reports. ## Connection details | Setting | Value | | --- | --- | | Endpoint | `https://api.brixsignage.com/v1/mcp` | | Transport | Streamable HTTP. `POST` one JSON-RPC 2.0 message; the reply is a JSON response. No SSE stream. | | Auth | Sign in with OAuth 2.1 (claude.ai, ChatGPT and other connector apps), or `Authorization: Bearer ` with the same keys as the REST API. | | Protocol versions | `2024-11-05`, `2025-03-26`, `2025-06-18` | | Rate limit | 30 requests per minute per caller, inside the workspace's 600 per minute. Over it: HTTP 429 with `Retry-After`. | | Methods | `initialize`, `ping`, `tools/list`, `tools/call`, `resources/list`, `resources/templates/list`, `resources/read`, `prompts/list`, `prompts/get` | There are two ways to connect: - **Sign in (claude.ai, ChatGPT).** Add the endpoint as a custom connector. The app opens a Brix page where you sign in and choose what it may do. No key to copy. - **API key (Claude Code, Cursor, Claude Desktop config file, the Claude and OpenAI APIs).** Create a key and put it in the client's config. Either way, the permissions you pick decide what the agent can do. See [Authentication and scopes](/developers/authentication/). ## claude.ai and ChatGPT: sign in 1. Copy the endpoint: `https://api.brixsignage.com/v1/mcp`. 2. Add it as a custom connector. - **Claude:** Settings > Connectors > Add custom connector. Claude Desktop uses the same connectors as claude.ai. - **ChatGPT:** Settings > Apps and Connectors > Advanced settings, turn on Developer mode, then Create. 3. The app opens a Brix page. Sign in, then pick the workspace, a location if you want to limit the app to one, and the permission areas. Click **Allow**. The app acts as you, with only the areas you picked. Viewing screens is always included, so if you pick no areas, the app can only view screens. You cannot give the app a permission you do not hold. Whatever areas you pick, an app connected by signing in cannot use billing, payment, invoice, API key, single sign-on, user or role routes. A person does these in the CMS. API keys are not affected. Every connection is listed in the CMS under **Settings > API & MCP > Connected apps**, with who connected it and when it was last used. **Revoke** ends it at once: the app's next call is refused and it has to be connected again. For client developers: the server follows the MCP authorization spec. A call without a token gets `401` with a `WWW-Authenticate` header that points to the protected-resource metadata at `/.well-known/oauth-protected-resource/v1/mcp`. The authorization server is `https://api.brixsignage.com` (metadata at `/.well-known/oauth-authorization-server`). It supports authorization code with PKCE (S256), dynamic client registration and client ID metadata documents. Access tokens last an hour. Refresh tokens rotate on every use. An access token from this flow works only at `/v1/mcp`. ## API key clients ### 1. Create a key for the agent In the CMS: **Settings > API & MCP > New key**. Give it only the areas the agent needs. For a first try, a view-only key (Screens, Playlists, Schedules, Files, Proof of Play) is safe: the agent can answer questions but cannot change what plays. The same page has a **Model Context Protocol (MCP)** section with the endpoint and **Copy config** and **Copy command** buttons. ### Claude Code ```bash claude mcp add --transport http brix https://api.brixsignage.com/v1/mcp \ --header "Authorization: Bearer $BRIX_API_KEY" ``` Then run `claude` and ask: "Which of my Brix screens are offline, and why?" Check the connection with `/mcp`. ### Claude Desktop To connect by signing in, add Brix as a custom connector (see above). To use a key instead: Claude Desktop's config file starts local (stdio) servers, so connect through the `mcp-remote` bridge. It needs Node.js. Open **Settings > Developer > Edit Config** and add: ```json { "mcpServers": { "brix": { "command": "npx", "args": [ "-y", "mcp-remote", "https://api.brixsignage.com/v1/mcp", "--header", "Authorization:${BRIX_AUTH}" ], "env": { "BRIX_AUTH": "Bearer ak_us_live_..." } } } } ``` Restart Claude Desktop. The header value goes through `env` because some platforms split arguments that contain spaces. ### Cursor Add to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project): ```json { "mcpServers": { "brix": { "url": "https://api.brixsignage.com/v1/mcp", "headers": { "Authorization": "Bearer ${env:BRIX_API_KEY}" } } } } ``` `${env:BRIX_API_KEY}` reads the key from your environment, so it stays out of the file. You can also paste the key in place of it. Open **Cursor Settings > MCP** to check that `brix` is connected. Other clients that accept a remote URL with custom headers use the same shape. This is the config that **Copy config** in the CMS gives you: ```json { "mcpServers": { "brix": { "url": "https://api.brixsignage.com/v1/mcp", "headers": { "Authorization": "Bearer " } } } } ``` ### OpenAI API The OpenAI Responses API sends your key in a header to a remote MCP server: ```bash curl https://api.openai.com/v1/responses \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5", "tools": [{ "type": "mcp", "server_label": "brix", "server_url": "https://api.brixsignage.com/v1/mcp", "headers": { "Authorization": "Bearer '"$BRIX_API_KEY"'" }, "require_approval": "never" }], "input": "Which of my screens are offline?" }' ``` `"require_approval": "never"` lets the model call tools without a pause. Use it with a view-only key, or set it to `"always"` and approve each call. ### Claude API The Messages API connects to remote MCP servers with the MCP connector: ```bash curl https://api.anthropic.com/v1/messages \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "anthropic-beta: mcp-client-2025-11-20" \ -H "content-type: application/json" \ -d '{ "model": "claude-opus-5", "max_tokens": 4096, "mcp_servers": [{ "type": "url", "name": "brix", "url": "https://api.brixsignage.com/v1/mcp", "authorization_token": "'"$BRIX_API_KEY"'" }], "tools": [{ "type": "mcp_toolset", "mcp_server_name": "brix" }], "messages": [{ "role": "user", "content": "Which of my screens are offline?" }] }' ``` ## Test the endpoint with curl ```bash curl https://api.brixsignage.com/v1/mcp \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"whoami","arguments":{}}}' ``` A tool result comes back as a text block plus `structuredContent`. A failed tool call returns `isError: true` with the error text, so the agent can correct itself. ## How the tools behave - Start with `whoami` in an unfamiliar workspace. It returns the key, the workspace and its permissions. - `search_screens` and `search_media` resolve names to real ids. Never guess an id. - `cast_content` shows existing content for a limited time (default one hour). `assign_content` changes what a screen plays by default. `quick_post` writes an announcement and casts it in one step. - `list_api_routes` finds any documented REST endpoint the key allows, for anything without a dedicated tool. `api_get` reads it. Writes are separate tools: `api_create` (POST), `api_update` (PATCH or PUT) and `api_delete`. Key clients set up before this split can still call `call_api`; connections made by signing in cannot. - Every tool declares MCP annotations: read-only, destructive, idempotent, and whether it reaches outside Brix. Clients use them to decide when to ask you first. - Content created over MCP lands as a draft where the location requires approval. - Changes made over MCP are recorded in the workspace audit log. Reads are not. ## Things to ask - "Which of my screens are offline, and why?" - "Why is the lobby screen blank? Show me what it's displaying right now." - "Put the Summer Menu playlist on every screen at the Downtown location for the next two hours." - "How many times did the Burger Promo play last week, and on which screens?" - "Give me this month's uptime report, worst screens first." Your AI app asks before it runs a tool that changes what a screen shows. ## Privacy, disconnecting and support - The app sees only what the tools return for the workspace, location and permission areas you approved. Brix does not receive your conversation with the app, only the tool calls it makes. - Disconnect any time in Brix under Settings > API & MCP > Connected apps. Revoke ends the connection at once: the app's next call is refused. Removing the connector in the app stops the app using it. - A connection record is deleted 30 days after the connection ends (revoked, refresh token expired, or sign-in never completed). The audit log entries stay for the audit log's normal retention period. - Brix does not use data accessed through connected apps to train AI models. The app's provider handles what the app receives under its own terms. - How Brix handles data is in the [Privacy Policy](https://brixsignage.com/privacy#connected-ai-apps). For help, email [hello@brixsignage.com](mailto:hello@brixsignage.com). - Service status is at [status.brixsignage.com](https://status.brixsignage.com). - To report a security problem, see [Reporting a vulnerability](https://brixsignage.com/security/#report). ## Tool catalog 47 tools. Each needs the permission shown on the API key. Read-only tools never change anything. ### capture_device_frame Ask a screen to capture a screenshot now and return the newest stored frame — `latestScreenshotId`, `latestFrameUrl`, `latestFrameAt`, and `fresh` (true when the frame post-dates this request). To see the picture, pass `latestFrameUrl` to api_get: the image comes back as { contentType, totalBytes, bodyBase64 }. Use to see what's actually on the panel. Permission: `screen.edit`. Behavior: creates. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `screenId` | string | yes | | ### get_screen_why Explain why a screen is showing what it's showing, in plain language. Returns the resolution chain (deactivated / emergency / cast / scheduled / default). Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `screenId` | string | yes | The screen ID, e.g. scr_aurora_42 | ### diagnose_screen "What might be wrong" — a likely-cause troubleshooting verdict for a screen that looks blank/wrong, reasoned by ELIMINATION from verifiable signals (online/offline, render-truth, resource pressure, hardware early-warning). Returns one likely cause + a guided next-step checklist. Note: the player cannot read TV/HDMI/input state, so when the stick is healthy and outputting content the verdict is the `downstream` layer with `guided: true` and a TV/input/cable CHECKLIST — a guided guess, NOT a detection. A definitive CEC `display` read (tv-off/disconnected) is the one real display signal. Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `screenId` | string | yes | The screen ID, e.g. scr_aurora_42 | ### list_open_alerts List currently-open alert events, newest first. Optionally filter by severity, screen or org node. Paginate with offset; the response reports total + hasMore. Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `severity` | info \| warning \| critical | no | | | `screenId` | string | no | | | `nodeId` | string | no | Limit to alerts raised at (or under) this org node. | | `offset` | integer | no | Skip this many matching alerts (default 0). | ### assign_content Push a playlist, schedule, app, media or layout to a screen — or to MANY screens at once via screenIds. Requires `screen.cast`. Permission: `screen.cast`. Behavior: changes existing data, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `screenId` | string | no | Single target screen. | | `screenIds` | array | no | Bulk alternative: assign the same content to every listed screen (max 200). | | `contentKind` | playlist \| schedule \| app \| media | yes | | | `contentId` | string | no | | ### send_command Send a runtime command (reboot, refresh, screenshot, clear-cache, cec-on, cec-off) to one screen or many (screenIds). Reboot interrupts what's on screen — confirm with the operator when unsure. Permission: `screen.edit`. Behavior: changes existing data. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `screenId` | string | no | | | `screenIds` | array | no | Bulk alternative: command every listed screen (max 200) in one call. | | `kind` | reboot \| refresh \| screenshot \| clear-cache \| cec-on \| cec-off | yes | | ### trigger_emergency_template Fire a pre-armed emergency template (Lockdown, Severe weather, Fire drill, …) across its in-scope screens — an immediate takeover. List ids first with the brix://emergency-templates resource; confirm scope with the operator. Permission: `screen.edit`. Behavior: changes existing data. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `templateId` | string | yes | | ### upload_media_from_url Download a file from a public URL into Brix media. Returns the new asset id + URL. Use for migration — pull from a Drive share, S3, the customer's old-CMS export, etc. Max 500 MB; use mint_upload_url for larger. Permission: `media.create`. Behavior: creates, reaches outside Brix. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | Publicly fetchable http(s) URL. | | `name` | string | yes | Display name for the asset. | | `folderId` | string | no | | | `tags` | array | no | | ### mint_upload_url Get a one-shot upload URL for a large file. The model's host PUTs bytes to it then calls confirm_media_upload with the same `uploadId`. Permission: `media.create`. Behavior: creates. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `contentType` | string | yes | | | `name` | string | yes | | ### confirm_media_upload Finalize an upload after PUTting bytes to a mint_upload_url. Creates the media_assets row. Permission: `media.create`. Behavior: creates. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `uploadId` | string | yes | | | `folderId` | string | no | | | `tags` | array | no | | ### bulk_create_playlists Create many playlists at once. Max 500 per call. Items reference media or app-instance ids that must already exist in this workspace. Permission: `playlist.create`. Behavior: creates. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `playlists` | array | yes | | ### bulk_create_org_nodes Create many org-tree nodes in one call. Max 1000. Each node hangs off `parentId` (an existing node), `parentExternalId` (the Location ID of an existing node or of an EARLIER node in this same call — build regions then stores in one call), or the Space root when both are omitted. `externalId` sets the node's Location ID (store number / region code; unique per workspace) — SSO claim mapping matches IdP location claims against it. A node whose externalId already exists is skipped, so re-running an import is safe. Permission: `org-unit.create`. Behavior: creates. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `nodes` | array | yes | | ### search_screens Filter the screen fleet. All criteria optional and AND-ed. Returns up to 200 per page; the response carries total + hasMore so you can page with `offset` instead of re-querying. Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `q` | string | no | Substring match on screen name. | | `status` | online \| offline \| pairing | no | | | `nodeId` | string | no | Limit to screens at (or under) this org node. | | `tag` | string | no | | | `offset` | integer | no | Skip this many matches (default 0). | ### bulk_create_schedules Create many schedules in one call. Max 200; up to 50 blocks each. Times are HH:MM 24-hour in the SCREEN's timezone. Permission: `schedule.create`. Behavior: creates. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `schedules` | array | yes | | ### assign_screen_to_node Move a screen to a different org node — useful for post-migration cleanup. Requires `screen.edit`. Permission: `screen.edit`. Behavior: changes existing data, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `screenId` | string | yes | | | `nodeId` | string | no | null to clear; otherwise the destination node id. | ### create_creatives Author new creatives (data-bound canvases) from scratch. This is how you BUILD templates programmatically. Each: { name, stage?:'landscape'|'portrait', backgroundUrl?, dataSourceId?, nodeId?, boxes:[…] }. A box is { kind, x, y, w, h, … } where x/y/w/h are 0–1 fractions of the stage and kind is one of text|field|list|image|shape|celebrations|event-board|signage|group|slot. Shapes use { shape:'rect'|'circle'|'line'|'triangle', fill, stroke, strokeWidth, cornerRadius }; text uses { text, color, fontWeight, align, fontSize? }; field/list bind via { field:'dot.path' }; image uses { url }; slot is a transparent hole the screen fills with real content, like a layout zone — { slot: { kind:'media'|'playlist'|'app', id, name } } naming a file / playlist / app in THIS workspace (or slot:null for an empty hole the customer fills later; borderRadius clips what plays there); signage is a full Signage Template Engine block — { signage: { archetype, style, mode, industry, orientation, content, anim } } — rendered 1:1 (layout + animations), usually one full-bleed box (x:0,y:0,w:1,h:1). Boxes are validated + clamped server-side; an invalid box rejects the whole call. Max 100 creatives per call. Designs also pass a quality gate (the Brix template standard): REJECTED with named findings when text pins under 24px (1920w scale), an authored contrast pair computes <3:1, or one layer fully hides another; WARNED as structured diagnostics for busy layouts (>36 content layers), >5 font sizes, >2 font families, hand-frozen list rows that should be ONE list box, static clock/date copy instead of {{now.*}} tokens, and competing loop animations. Treat warnings as defects: fix and re-emit. Permission: `creative.create`. Behavior: creates. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `creatives` | array | yes | | ### whoami Who this connection is and what it may do. Useful at the start of work in an unfamiliar workspace. Returns the actor (an API key with the org node it is pinned to, or the signed-in user), the workspace id + name, and the flat permission set. A pinned key simply cannot see or write outside its branch, so this is usually the answer to an unexplained 403 or an empty list. The permission set is a planning hint: node-precise checks still run on every write. Permission: `screen.view`. Behavior: read-only. ### list_api_routes Lists the Brix REST API (https://brixsignage.com/developers/api/): every documented endpoint as { method, path, description }. Everything the CMS UI can do lives behind one of these routes. A GET route is read with api_get; POST, PUT/PATCH and DELETE routes go through api_create, api_update and api_delete. Narrow with `prefix` (e.g. '/v1/screens') or free-text `q` (matched against path + description). One page is 200 routes and the surface is larger than that: while the reply carries `nextOffset`, call again with that `offset` to see the rest. Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `prefix` | string | no | Path prefix filter, e.g. '/v1/playlists'. | | `q` | string | no | Case-insensitive substring match on path or description. | | `offset` | number | no | Skip this many matching routes — pass the `nextOffset` from the previous reply to page through the whole surface. | ### api_get Sends a GET request to the Brix REST API (reference: https://brixsignage.com/developers/api/) with this connection's own permissions and returns { status, body }. Find routes with list_api_routes. Query strings may be included in `path`. A route that answers with bytes (a media file, a poster, an invoice PDF, a screenshot) comes back as { contentType, totalBytes, bodyBase64 }. A 4xx/5xx returns the API's error body. Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `path` | string | yes | Absolute API path starting with /v1/ with real ids in place of :params, optionally with ?query. | ### api_create Sends a POST request to the Brix REST API (reference: https://brixsignage.com/developers/api/) with this connection's own permissions: creates a record or runs an action (some POST routes act on screens, e.g. commands). Find POST routes with list_api_routes. Pass a JSON `body`, or `bodyBase64` + `contentType` for the few routes that take raw bytes; whole media files upload better with mint_upload_url. Pass `idempotencyKey` on a create you might retry and the API dedupes on it. Returns { status, body }; a 4xx/5xx returns the API's error body. Permission: `screen.view`. Behavior: changes existing data. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `path` | string | yes | Absolute API path starting with /v1/ with real ids in place of :params, optionally with ?query. | | `body` | any | no | JSON request body. Sent as application/json. Ignored when `bodyBase64` is present. | | `bodyBase64` | string | no | Raw request body, base64-encoded, for the routes that take bytes (POST /v1/media/:id/poster, /captions/upload, /captions/transcribe, PUT /v1/media/upload/:uploadId). Requires `contentType`. Capped at ~6 MB encoded. | | `contentType` | string | no | Content-Type for `bodyBase64`, e.g. image/jpeg, text/vtt, audio/wav. Required with bodyBase64. | | `idempotencyKey` | string | no | Forwarded as the `idempotency-key` header so a retried create cannot duplicate. Use any stable unique string per logical create. | ### api_update Sends a PATCH (default) or PUT request to the Brix REST API (reference: https://brixsignage.com/developers/api/) with this connection's own permissions, changing an existing record. Find PATCH/PUT routes with list_api_routes. Pass a JSON `body`, or `bodyBase64` + `contentType` for PUT /v1/media/upload/:uploadId. Returns { status, body }; a 4xx/5xx returns the API's error body. Permission: `screen.view`. Behavior: changes existing data. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `method` | PATCH \| PUT | no | PATCH (the default) or PUT, as the route lists it. | | `path` | string | yes | Absolute API path starting with /v1/ with real ids in place of :params, optionally with ?query. | | `body` | any | no | JSON request body. Sent as application/json. Ignored when `bodyBase64` is present. | | `bodyBase64` | string | no | Raw request body, base64-encoded, for the routes that take bytes (POST /v1/media/:id/poster, /captions/upload, /captions/transcribe, PUT /v1/media/upload/:uploadId). Requires `contentType`. Capped at ~6 MB encoded. | | `contentType` | string | no | Content-Type for `bodyBase64`, e.g. image/jpeg, text/vtt, audio/wav. Required with bodyBase64. | ### api_delete Sends a DELETE request to the Brix REST API (reference: https://brixsignage.com/developers/api/) with this connection's own permissions. Most content deletes move the item to the recycle bin for 30 days (see list_recycle_bin); a purge route deletes permanently. Find DELETE routes with list_api_routes. Returns { status, body }; a 4xx/5xx returns the API's error body. Permission: `screen.view`. Behavior: changes existing data, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `path` | string | yes | Absolute API path starting with /v1/ with real ids in place of :params, optionally with ?query. | ### search_media Search the media library by name, kind, folder or tag. Resolves media names to asset ids before building playlists, casting or assigning. Returns id/name/kind/tags/state/duration per asset; results are node-scoped to this key. Permission: `media.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `q` | string | no | Case-insensitive substring match on asset name. | | `kind` | string | no | Asset kind filter, e.g. image \| video \| audio \| pdf \| web \| rss. | | `folderId` | string | no | Only assets filed in this folder. | | `tag` | string | no | Exact tag match (case-insensitive). | | `limit` | integer | no | Page size (default 50). | | `offset` | integer | no | Skip this many matches (default 0). | ### list_emergency_templates List the pre-armed emergency takeover templates (Lockdown, Severe weather, Fire drill, …) with their ids, scope and severity. Pass an id to trigger_emergency_template — never guess one. Permission: `emergency-override.view`. Behavior: read-only. ### list_pending_approvals List approval requests (content awaiting an approve/reject decision before it airs). Defaults to pending; pass state to see decided history. Content created over MCP lands as 'draft' when the location requires approval — check here to see what's waiting. Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `state` | pending \| approved \| rejected \| withdrawn | no | Filter by decision state (default pending). | | `limit` | integer | no | Page size (default 50). | ### list_recycle_bin List soft-deleted items across every content kind within the 30-day restore window, with what used them before deletion. Pair with restore_recycle_bin_item for 'undo that delete' requests. Deleted SCREENS appear here too — DELETE /v1/screens/:id soft-deletes into the same 30-day window. Permission: `screen.view`. Behavior: read-only. ### update_playlist Edit an EXISTING playlist: rename/describe it, set its play window, toggle shuffle, append items, remove items, patch item duration/fit, or reorder. Compose any subset in one call; each change runs through the same validation + approval invalidation as the CMS editor. Requires `playlist.edit` at the playlist's node. Permission: `playlist.edit`. Behavior: changes existing data. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `playlistId` | string | yes | | | `name` | string | no | New display name. | | `description` | string | no | | | `shuffle` | boolean | no | | | `fullscreen` | boolean | no | When this playlist plays inside a layout, EVERY item fills the whole screen and the other zones hide. Per-item: patchItems[].fullscreen. | | `startsAt` | string | no | ISO datetime the playlist starts airing; null clears. | | `expiresAt` | string | no | ISO datetime the playlist stops airing; null clears. | | `appendItems` | array | no | | | `patchItems` | array | no | | | `removeItemIds` | array | no | | | `reorderItemIds` | array | no | The COMPLETE item order after the move — unknown ids are dropped, the rest keep this sequence. | ### update_media Update a media asset's metadata: rename it, retag it, move it to another folder, set alt text, set how it fills the screen, or give it an expiry (+ optional auto-archive into the recycle bin). Bytes are never touched. Requires `media.edit`. Permission: `media.edit`. Behavior: changes existing data, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `mediaId` | string | yes | | | `name` | string | no | | | `tags` | array | no | Replaces the whole tag list. | | `folderId` | string | no | Destination folder in THIS workspace; null moves to the library root. | | `altText` | string | no | | | `fit` | contain \| cover \| fill \| blur-fill | no | How this file fills any screen or zone it plays in: contain (whole picture, black bars), blur-fill (whole picture, blurred edges), cover (fills, crops the edges), fill (fills, distorts). Applies everywhere the file plays; a playlist slot can override it. | | `expiresAt` | string | no | ISO datetime after which the asset stops airing; null clears. | | `autoArchiveOnExpiry` | boolean | no | Soft-delete the asset into the recycle bin when it expires. | ### create_web_link Add a public web page or dashboard to the media library so it can be cast, scheduled or added to a playlist. Returns the new mediaId — pass it to configure_web_link to crop the page, set a refresh, or dismiss cookie banners. For a page only the screen's own network can reach, set onScreenNetwork: the address is stored but Brix's cloud never fetches it and the screen opens it directly. Requires `media.create`. Permission: `media.create`. Behavior: creates, reaches outside Brix. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | What operators will see in the library. | | `url` | string | yes | http(s) address. A private/loopback address is refused unless onScreenNetwork is true. | | `folderId` | string | no | Library folder; omit for the root. | | `tags` | array | no | | | `onScreenNetwork` | boolean | no | The page lives on the screen's own network (an intranet dashboard, a device web UI). | ### configure_web_link Change how a web link is rendered for screens: show only one part of the page, auto-dismiss cookie banners, auto-scroll a long page, set the refresh cadence, or set the browser size it is rendered at. Only the fields you pass are changed; a recorded sign-in is preserved. `showOnly` takes a CSS selector — everything else on the page is hidden, so a timetable or one dashboard panel fills the screen. Prefer an id (`#schedules`): class names change whenever the site is republished. Cropping needs a server-rendered snapshot, so setting showOnly switches the link to snapshot rendering. Requires `media.edit`. Permission: `media.edit`. Behavior: creates, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `mediaId` | string | yes | | | `showOnly` | string | no | CSS selector of the one element to show. Empty string shows the whole page again. | | `acceptCookies` | boolean | no | Click Accept on common cookie / consent / age-gate banners on every load. | | `autoScrollPxPerSec` | number | no | Scroll a long page top-to-bottom at this speed. 0 stops auto-scrolling. | | `refreshSeconds` | number | no | How often the page is re-loaded or re-rendered. 0 means never. | | `renderWidth` | number | no | Browser width the page is rendered at. Match the screen; 1920 by default. | | `renderHeight` | number | no | Browser height the page is rendered at. 1080 by default. | ### render_web_link Force a fresh server-side snapshot of a web link instead of waiting for its next scheduled refresh. Use it right after configure_web_link to see whether a crop selector actually matched. Only meaningful for snapshot-rendered links — a live page is loaded by the screen itself. Requires `media.edit`. Permission: `media.edit`. Behavior: creates, idempotent, reaches outside Brix. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `mediaId` | string | yes | | ### restore_recycle_bin_item Restore ONE soft-deleted item from the recycle bin (kind from list_recycle_bin). Playlists/screens referencing it resume automatically where they still do. Each kind's own permission applies (e.g. playlist.delete restores playlists). Purging (permanent deletion) is not part of this tool. Permission: `screen.view`. Behavior: creates, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `kind` | screen \| media \| playlist \| schedule \| layout \| creative \| app \| data-source \| signage \| banner \| media-folder | yes | | | `id` | string | yes | | ### decide_approval Approve or reject a pending approval request (from list_pending_approvals). Rejecting requires a reason. Only works when this key's identity is actually an approver for the request's chain — the API enforces that, not this tool. Permission: `screen.view`. Behavior: changes existing data. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `approvalId` | string | yes | | | `decision` | approve \| reject | yes | | | `reason` | string | no | Required for reject. | ### cast_content Show EXISTING content on screens as a TIMED takeover (a cast) — screens return to their normal schedule when it expires. Scope it to everything (all), an org node subtree, or explicit screenIds. This is the tool for 'put the lunch menu on the lobby screens until 2pm'. Requires `screen.cast`. Permission: `screen.cast`. Behavior: changes existing data, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `contentKind` | media \| playlist \| app \| creative \| schedule | yes | | | `contentId` | string | yes | | | `scopeKind` | all \| node \| screens | no | Default all — every screen this key can reach. | | `scopeNodeId` | string | no | With scopeKind=node: the org-node subtree to cast across. | | `screenIds` | array | no | With scopeKind=screens: explicit targets (max 500). | | `expiresAt` | string | no | ISO datetime the cast ends. Default: one hour from now. | | `headline` | string | no | Display name recorded for the cast. | ### end_cast End an active cast early — its screens return to scheduled content immediately. Requires `screen.cast`. Permission: `screen.cast`. Behavior: changes existing data, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `castId` | string | yes | | ### quick_post Author a headline (+ optional body) announcement and put it on screens in ONE step — the agent twin of Quick Post. Creates a branded takeover creative and casts it across the chosen scope. Needs creative.create AND screen.cast. Permission: `screen.cast`. Behavior: changes existing data. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `headline` | string | yes | | | `body` | string | no | | | `tone` | announcement \| alert \| celebrate \| info | no | Picks the background treatment. Default announcement. | | `scopeKind` | all \| node \| screens | no | Default all. | | `scopeNodeId` | string | no | | | `screenIds` | array | no | | | `expiresInSec` | integer | no | How long the message stays up (default 3600). | ### create_screen_group Create a named, saved cohort of screens (optionally seeding members). Groups are the reusable target for bulk commands, casts and assignments. Requires `screen.edit`. Permission: `screen.edit`. Behavior: creates. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `description` | string | no | | | `screenIds` | array | no | Initial membership; foreign/out-of-scope ids are skipped silently. | ### add_screens_to_group Add screens to a saved screen group. Idempotent; out-of-scope ids are skipped, never fatal. Requires `screen.edit`. Permission: `screen.edit`. Behavior: creates, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `groupId` | string | yes | | | `screenIds` | array | yes | | ### remove_screens_from_group Take screens out of a saved screen group — only the label drops, the screens themselves are untouched. Requires `screen.edit`. Permission: `screen.edit`. Behavior: changes existing data, idempotent. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `groupId` | string | yes | | | `screenIds` | array | yes | | ### sync_data_source Pull fresh data through one connected feed (CSV, Google Sheets, POS…) now, so data-bound apps and creatives show current values. Requires `integration.edit`. Permission: `integration.edit`. Behavior: creates, reaches outside Brix. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `dataSourceId` | string | yes | | ### refresh_all_data_sources Sync EVERY connected data feed in this workspace once. Use before big announcements so menus/prices are current. Requires `integration.edit`. Permission: `integration.edit`. Behavior: creates, reaches outside Brix. ### get_proof_of_play Proof-of-play rollup for a time window: plays, watch time, skips and failures per piece of content, plus which screens reported. Answers 'how did the promo actually do?'. Requires `playback-log.view`. Permission: `playback-log.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `from` | string | no | ISO datetime; default 7 days ago. | | `to` | string | no | ISO datetime; default now. | | `screenId` | string | no | Restrict to one screen's report. | ### get_screen_outages One screen's outage history over the last N days (default 30, capped at the offline retention window): every outage with when / how long / a plain-language CAUSE (Wi-Fi dropped, power cut, Brix unreachable, …) and its evidence, plus a rollup sentence ('went down 6 times in the last 30 days, all Wi-Fi drops'). Planned darkness (operating hours) is listed but not counted. Requires `screen.view`. Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `screenId` | string | yes | | | `days` | number | no | Window in days; default 30. | | `limit` | number | no | Max rows; default 50, max 200. | ### get_screen_display_history What the TV itself has been doing, which the outage history cannot show because the player is online and playing through all of it: every time the panel was switched off, switched to another HDMI input, lost its HDMI connection, or came back — newest first, each with how long the previous state lasted — plus its current state. Only a device that can read its panel (a Signage Stick over CEC) produces rows; a web or Windows player returns none and no current state. Default window is the telemetry retention (14 days). Requires `screen.view`. Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `screenId` | string | yes | | | `days` | number | no | Window in days; default and cap = the telemetry retention (14). | | `limit` | number | no | Max rows; default 50, max 500. | ### get_uptime_report Uptime/SLA summary for a window: online % per screen, downtime windows (each with its cause), worst offenders first. Every screen, location and the fleet carry an `outageSummary` — cause counts, the top cause and a plain-language insight such as 'went down 6 times in the last 30 days, all Wi-Fi drops'. Requires `screen.view`. Permission: `screen.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `from` | string | no | ISO datetime; default 7 days ago. | | `to` | string | no | ISO datetime; default now. | | `screenId` | string | no | | ### search_audit_log Read the workspace audit log, newest first — who changed what, including changes made by MCP tools themselves. Keyset-paged via cursor. Requires `audit-log.view`. Permission: `audit-log.view`. Behavior: read-only. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | Page size (default 200). | | `cursor` | string | no | nextCursor from the previous page. | ### bulk_create_celebration_entries Import birthdays/anniversaries/etc. for the Celebrations app in bulk — the classic migration job. Max 100 per call; each needs at least a name plus month/day. Requires `integration.create`. Permission: `integration.create`. Behavior: creates. | Argument | Type | Required | Description | | --- | --- | --- | --- | | `entries` | array | yes | | ## Resources - `brix://screens`: Every screen in the workspace, with status + location + assigned content. - `brix://playlists`: Playlists with their items (refKind/refId/duration), approval state and org node. - `brix://schedules`: Time-of-day schedules and which screens they target. - `brix://alerts/open`: Currently open alert events — anything offline / storage-critical / playback-failing. - `brix://org-tree`: The hierarchical org-node structure (Spaces / districts / schools / rooms). - `brix://media`: Images, videos, PDFs, web links and fonts — id, name, kind, folder, tags, state. - `brix://creatives`: Canvas creatives (data-bound designs) with stage + box count. - `brix://apps`: Installed app instances (weather, RSS, menus…) with their app key + node. - `brix://layouts`: Multi-zone layouts a screen can be pointed at, each with its `theme` (the Brand Kit look — { source: "brand", mode?, radius? } — or null for a bare canvas). Zone geometry, bindings and per-zone `frame` / `radius` / `role` come from GET /v1/layouts/:id. - `brix://data-sources`: Connected data feeds (CSV, Sheets, POS…) with connection + last-sync status. - `brix://screen-groups`: Named saved cohorts of screens — the unit for bulk commands and casts. - `brix://emergency-templates`: Pre-armed emergency takeover recipes; pass one's id to trigger_emergency_template. - `brix://approvals/pending`: Content awaiting an approve/reject decision before it can air. - `brix://screens/{screenId}/why`: The resolution chain (deactivated / emergency / cast / scheduled / default) for one screen. - `brix://playlists/{playlistId}`: One playlist WITH its ordered items. - `brix://media/{mediaId}/usage`: Where a media asset is used (playlists/creatives/layouts) and when it last played. ## Prompts - `fleet_health_report`: A daily ops digest: fleet status, open alerts, worst uptime offenders and what to do about each. - `diagnose_offline_screen`: Walk the likely-cause diagnosis for one screen and hand back a guided next-step checklist. Arguments: `screen_id` (optional). - `onboard_new_location`: Set up a new org location end-to-end: create the node, home its screens there and assign starter content. Arguments: `location_name`. - `weekly_content_review`: Proof-of-play rollup for the last 7 days: top content, skipped/failed plays, unused media worth pruning. # Webhooks > Get a signed HTTPS POST when Brix content is recalled or restored. Create endpoints, verify the brix-signature HMAC, handle retries, and replay deliveries. Source: https://brixsignage.com/developers/webhooks/ A webhook endpoint receives a signed `POST` from Brix when an event happens in your workspace. Manage endpoints in the CMS at **Settings > Integrations > Webhooks**, or over the API. Managing webhooks needs `integration.view` and `integration.edit` at the workspace level. ## Events | Event | Sent when | `data` | | --- | --- | --- | | `content.recalled` | Someone pulls a file, design, playlist or schedule off every screen. | `kind` (`media`, `creative`, `playlist`, `schedule`), `id`, `name` (the kind's label, such as `Playlist`), `reason` (or null), `recalledBy` | | `content.restored` | Recalled content is put back. | `kind`, `id`, `name` | `GET /v1/webhooks/events` returns the full event catalog with a label and description for each. The catalog also names screen, approval and emergency events (`screen.offline`, `screen.online`, `approval.requested`, `approval.decided`, `emergency.started`, `emergency.cleared`). You can subscribe to them, but Brix does not send them. Only the two events above are delivered. ## Create an endpoint ```bash curl -X POST https://api.brixsignage.com/v1/webhooks \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/brix-hook", "events": ["content.recalled", "content.restored"], "description": "Ops channel"}' ``` The response is `201` with the endpoint (`id` starts with `whe_`) and its signing `secret`. The secret is shown once. Store it. An unknown event name returns `400`. | Call | What it does | | --- | --- | | `GET /v1/webhooks` | List endpoints. The secret is never included. | | `PATCH /v1/webhooks/{id}` | Change `url`, `events`, `description` or `enabled`. Re-enabling clears an auto-disable. | | `DELETE /v1/webhooks/{id}` | Delete an endpoint. Its pending deliveries stop. | | `POST /v1/webhooks/{id}/test` | Send a real signed delivery now. | | `GET /v1/webhooks/deliveries` | The delivery log, with payload, response and attempts. Paginated. Kept 30 days. | | `POST /v1/webhooks/deliveries/{id}/replay` | Send a delivery again as a new delivery. | The test delivery is a `screen.offline` event with `data.test: true` and a note that nothing is wrong. It returns `{ deliveryId, ok, status, error }`. If it returns `409 not_subscribed`, add `screen.offline` to the endpoint's events. ## The request Brix sends ``` POST /brix-hook HTTP/1.1 content-type: application/json user-agent: Brix-Webhooks/1 x-brix-event: content.recalled brix-signature: t=1790000000,v1=5f2b...c9 ``` ```json { "deliveryId": "whd_...", "event": "content.recalled", "occurredAt": "2026-01-15T14:03:22.000Z", "workspaceId": "...", "attempt": 1, "data": { "kind": "playlist", "id": "pl_...", "name": "Playlist", "reason": "Wrong prices", "recalledBy": "..." } } ``` - `deliveryId` stays the same across retries. Use it to ignore duplicates. - `occurredAt` is when the event happened, not when it was sent. - `attempt` counts delivery attempts, starting at 1. - A replay has a new `deliveryId` and a `replayOf` field with the original id. ## Verify the signature `brix-signature` is `t=,v1=`. The hex is HMAC-SHA256 of `.` with your endpoint secret. Check it against the raw body bytes, before you parse the JSON, and reject old timestamps. ```js import crypto from 'node:crypto'; export function verifyBrix(rawBody, header, secret, toleranceSec = 300) { const parts = Object.fromEntries((header ?? '').split(',').map((p) => p.trim().split('=', 2))); const t = Number(parts.t); if (!Number.isFinite(t) || !/^[0-9a-f]{64}$/.test(parts.v1 ?? '')) return false; if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(parts.v1, 'hex')); } ``` ```python import hmac, hashlib, time def verify_brix(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool: parts = dict(p.strip().split("=", 1) for p in (header or "").split(",") if "=" in p) try: t = int(parts["t"]) except (KeyError, ValueError): return False if abs(time.time() - t) > tolerance: return False expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, parts.get("v1", "")) ``` ## Delivery and retries - Delivery is at least once. Deduplicate on `deliveryId`. - Any `2xx` response is success. Redirects are not followed. Brix waits 10 seconds for a response. - A failed delivery is retried with backoff: 30 seconds, 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour, 2 hours, then 4 hours between attempts. After the ninth attempt the delivery is abandoned. - After 9 failures in a row, Brix disables the endpoint and emails the workspace Owners. Fix the receiver, then set `enabled: true` again. - Reply fast and do the work after. A slow receiver risks the 10-second timeout and a duplicate. # API reference 391 operations. Base URL `https://api.brixsignage.com`. ## Account Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/account Get account overview Returns the workspace plan, subscription status, screen pool size, trial or paid status, and onboarding progress. **Notes.** - `email`, `emailVerifiedAt` and `verificationEmailLastSentAt` describe the CALLING user; they are null for an API key. - On a workspace created before the onboarding step existed, a null `prefs.onboardingCompletedAt` is filled with the workspace's creation time (and saved). Auth: Bearer token. Permission: `billing.view`. ```bash curl "https://api.brixsignage.com/v1/account" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Account | | | `data.scheduledScreenPool` | object \| null | A screen plan reduction queued for the renewal. | | `data.contract` | BillingTerm \| null | | | `data.id` | string | Workspace id (the account id support asks for). | | `data.whiteLabel` | boolean | A partner bills this workspace; Brix billing does not apply. | | `data.status` | "active" \| "cancel_scheduled" \| "paused" \| "past_due" \| "suspended" \| "cancelled" | | | `data.pauseUntil` | string \| null | | | `data.cancelEffectiveAt` | string \| null | | | `data.cancelReason` | "provider" \| "trial_expired" \| "voluntary" \| "nonpayment" \| null | | | `data.everPaid` | boolean | | | `data.deletionRequestedAt` | string \| null | | | `data.billingBanner` | BillingBanner \| null | | | `data.survey` | any | The last cancellation survey (`{ reason, competitor, returnLikelihood, comment }`), or null. | | `data.prefs` | WorkspacePrefs | Workspace preferences. Every key is present (defaults filled in). | | `data.prefs.timeFormat` | "12h" \| "24h" | | | `data.prefs.weekStart` | "mon" \| "sun" | | | `data.prefs.dateFormat` | "mdy" \| "dmy" \| "ymd" | | | `data.prefs.tempUnits` | "f" \| "c" | | | `data.prefs.timeZone` | string | IANA time zone. Screens without their own zone use it, and so do schedules timed by the workspace. | | `data.prefs.language` | string | BCP 47 language tag. | | `data.prefs.multinational` | boolean | Shows the per-location and per-screen language settings. | | `data.prefs.navExtras` | array of string | Console pages switched on that the workspace size hides by default. | | `data.prefs.appBranding` | object | The look of the on-screen apps. | | `data.prefs.appBranding.accent` | string | | | `data.prefs.appBranding.accent2` | string | | | `data.prefs.appBranding.theme` | "dark" \| "light" | | | `data.prefs.appBranding.backdrop` | "brand" \| "wash" \| "plain" \| "aurora" \| "dots" \| "grid" \| "solid" | | | `data.prefs.appBranding.intensity` | number | | | `data.prefs.audio` | object | Default screen volume and a master mute. | | `data.prefs.audio.volume` | number | 0–100. | | `data.prefs.audio.muted` | boolean | | | `data.prefs.brand` | object | | | `data.prefs.brand.companyName` | string | | | `data.prefs.brand.logoUrl` | string | | | `data.prefs.requireAltText` | boolean | | | `data.prefs.showScreenLogo` | boolean | | | `data.prefs.standbyContentKind` | string \| null | What a screen with nothing to play shows; null = the default card. | | `data.prefs.standbyContentId` | string \| null | | | `data.prefs.tier` | "simple" \| "team" \| "enterprise" | Workspace size. It sets console defaults, not access. | | `data.prefs.industry` | string \| null | | | `data.prefs.onboardingCompletedAt` | string \| null | | | `data.prefs.setupStep` | string \| null | Walkthrough position (`s1`…`s5`, `l1`…`l6`, `done`). | | `data.prefs.orgNameSet` | boolean | | | `data.prefs.tiers` | array of integer | Location tree levels in use. | | `data.prefs.tierLabels` | object | Custom names for the location tree levels. | | `data.prefs.requireTwoFactor` | boolean | | | `data.prefs.requireSso` | boolean | | | `data.prefs.requireSsoPending` | boolean | Single sign-on was required, and turns on after the first sign-in through the connection. | | `data.prefs.requireSsoPendingSince` | string \| null | | | `data.prefs.ai` | object | | | `data.prefs.ai.enabled` | boolean | | | `data.prefs.ai.features` | object | Per feature (`tags`, `alt-text`, `moderation`, `transcription`, `translation`, `focal-region`, `embedding`); an absent feature follows `enabled`. | | `data.prefs.ai.dailyCallLimit` | integer \| null | | | `data.prefs.ai.redactPersonalData` | boolean | | | `data.prefs.retention` | object | Days kept per stream, only for the streams the workspace has set. An absent stream uses the platform window. | | `data.prefs.retention.offline` | number | | | `data.prefs.retention.screenshots` | number | | | `data.prefs.retention.playback` | number | | | `data.prefs.retention.deviceLogs` | number | | | `data.prefs.retention.vitals` | number | | | `data.prefs.retention.replays` | number | | | `data.prefs.retention.aiEvents` | number | | | `data.prefs.warehouse` | object | Data warehouse feed. Set by Brix; not writable here. | | `data.prefs.warehouse.enabled` | boolean | | | `data.prefs.warehouse.bucketBinding` | string | Set by Brix when the data warehouse feed is provisioned. | | `data.prefs.warehouse.tables` | array of string | | | `data.prefs.warehouse.lookbackDays` | integer | | | `data.prefs.supportAccess` | SupportAccessPolicy | | | `data.prefs.supportAccess.mode` | "open" \| "notify" \| "approve" | `open`: Brix support may open the workspace. `notify`: it may, and the owners are emailed. `approve`: a person here must approve each request. | | `data.prefs.supportAccess.grantMinutes` | integer | How long an approval lasts. | | `data.prefs.supportAccess.requestTtlMinutes` | integer | How long a request waits for an answer before it expires. | | `data.prefs.kioskHasGlobalPin` | boolean | A workspace Screen Lock PIN is set. The PIN and its hash are never returned. | | `data.region` | "us" \| "eu" \| "oc" \| "apac" | Where the workspace's data is stored. | | `data.billingProvider` | "chargebee" \| "brix" | `brix`: Brix invoices the account (see `payment`). `chargebee`: a card subscription. | | `data.payment` | AccountPayment \| null | Only when `billingProvider` is `brix`. | | `data.subscriptionStatus` | string \| null | | | `data.cadence` | "monthly" \| "annual" \| null | | | `data.billableScreens` | integer | Screens that count toward the bill now. | | `data.licensedScreens` | integer \| null | Screens bought (the plan quantity or the term's screens). | | `data.trialEndsAt` | string \| null | | | `data.nextBillingAt` | string \| null | | | `data.currentTermEndsAt` | string \| null | | | `data.emailVerifiedAt` | string \| null | The calling user's; null for an API key. | | `data.verificationEmailLastSentAt` | string \| null | | | `data.email` | string \| null | The calling user's email; null for an 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/billing-address Get billing address Returns the billing address on file with the billing provider: the country used to determine invoice tax, and, for the United States and Canada, the state and postal code. The response includes `configured: false` when no billing address has been set yet. Auth: Bearer token. Permission: `billing.view`. ```bash curl "https://api.brixsignage.com/v1/account/billing-address" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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. | ### PUT /v1/account/billing-address Set billing address Sets the workspace billing address. `country` is required and validated against the ISO 3166-1 country list. For the United States and Canada, `stateCode` and `zip` are also required. This replaces the entire address on file; you cannot update a single field. **Notes.** - Answers 400 (not 422) for an invalid address, unlike most validation errors. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `country` | string | yes | Country code or name. | | `stateCode` | string | no | Required for US and Canada. Stored in upper case. | | `zip` | string | no | Required for US and Canada. | | `city` | string | no | | | `line1` | string | no | | ```bash curl -X PUT "https://api.brixsignage.com/v1/account/billing-address" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.configured` | true | | | `data.address` | object | | | `data.address.country` | string \| null | ISO 3166-1 alpha-2. | | `data.address.stateCode` | string \| null | State or province, without the country prefix (`CA`, `ON`). | | `data.address.zip` | string \| null | | | `data.address.city` | string \| null | | | `data.address.line1` | string \| null | | | `data.needsState` | boolean | | Response 400: `invalid_country` or `state_required` (400, not 422). | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `no_customer`: billing is not set up yet. | 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 502: The billing provider did not answer; nothing changed. | 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/account/billing-mode Switch billing mode Switches the workspace between automatic card charging (`auto`) and invoice billing (`invoice`). Switching to invoice billing requires a signed-in user with a verified work email address, so an API key can only switch to automatic charging. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `mode` | "auto" \| "invoice" | yes | | ```bash curl -X POST "https://api.brixsignage.com/v1/account/billing-mode" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.mode` | "auto" \| "invoice" | | 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: `user_required`: switching to `invoice` needs a signed-in user with a verified work email, so an API key can switch only to `auto`. Also: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `email_unverified`, `work_email_required`, `billing_not_configured`, or `under_contract`. | 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: `mode` is not `auto` or `invoice`. | 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 502: The billing provider did not answer; nothing changed. | 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/account/cancel Cancel subscription Submits a cancellation request with a reason. Choosing the reason "Seasonal Business" together with a `restartDate` pauses the account instead of cancelling it. Any other reason schedules cancellation for a fixed number of days after the request. Cancellation can be reversed at any time before it takes effect by calling the reactivate operation. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `reason` | string | yes | Survey answer. `Seasonal Business` pauses the account instead of cancelling it. | | `restartDate` | string | no | Required for `Seasonal Business`: when the account comes back (future, at most 12 months). | | `competitor` | string | no | | | `returnLikelihood` | number | no | | | `comment` | string | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/account/cancel" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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 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 409: `payment_required` (unpaid invoice), `already_cancelled`, or `under_contract` (an agreed term; contact Brix). | 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: `reason` missing, or a bad `restartDate`. | 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 502: The billing provider did not answer; nothing changed. | 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/account/cancel-deletion Cancel pending account deletion Stops a scheduled permanent deletion of the workspace before it happens. Call this any time before the deletion date to keep the account and its data. **Notes.** - The account stays `cancelled`; reactivate it separately. Auth: Bearer token. Permission: `billing.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/account/cancel-deletion" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.status` | "active" \| "cancel_scheduled" \| "paused" \| "past_due" \| "suspended" \| "cancelled" | | | `data.deletionRequestedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `not_pending_deletion`. | 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/account/contract/renewal Set contract auto-renewal Sets whether the workspace's current contract term renews automatically or ends at term expiry. Choosing not to renew voids any unpaid renewal invoice; choosing to renew allows Brix to invoice the next term automatically. Returns 409 if the workspace has no fixed term, and 404 for white-label workspaces, where this setting does not apply. **Notes.** - Turning auto-renew off voids an unpaid renewal invoice. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `renewal` | "renew" \| "non_renewing" | yes | | ```bash curl -X PATCH "https://api.brixsignage.com/v1/account/contract/renewal" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.ok` | true | | | `data.contract` | BillingTerm | An agreed billing term (a fixed number of screens at a fixed rate for a fixed period). | | `data.contract.id` | string | | | `data.contract.status` | "active" \| "cancelled" \| "awaiting_payment" \| "ended" | | | `data.contract.rail` | "chargebee" \| "brix" | Who invoices the term: `brix` (Brix invoices) or `chargebee` (the card subscription). | | `data.contract.activateOn` | "now" \| "paid" | | | `data.contract.poNumber` | string \| null | | | `data.contract.invoiceNumber` | string \| null | | | `data.contract.invoicePaid` | boolean \| null | | | `data.contract.activatedAt` | string \| null | | | `data.contract.screens` | integer | | | `data.contract.rateCentsPerScreenMonth` | integer | | | `data.contract.termMonths` | integer | | | `data.contract.schedule` | "upfront" \| "monthly" \| "annual" | | | `data.contract.collect` | "card" \| "invoice" | | | `data.contract.currency` | string | | | `data.contract.startsAt` | string | Date (YYYY-MM-DD). | | `data.contract.endsAt` | string | Date (YYYY-MM-DD). | | `data.contract.totalCents` | integer | | | `data.contract.monthlyCents` | integer | | | `data.contract.termLabel` | string | | | `data.contract.scheduleLabel` | string | | | `data.contract.invoiceId` | string \| null | | | `data.contract.notes` | string \| null | | | `data.contract.createdBy` | string \| null | | | `data.contract.prior` | object \| null | The plan before the term started. | | `data.contract.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.contract.endedAt` | string \| null | | | `data.contract.renewal` | "renew" \| "non_renewing" | | | `data.contract.renewalInvoiceId` | string \| null | | | `data.changed` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: A workspace billed by a partner. | 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: `no_term` (no agreed term), or a term that cannot change here. | 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: `renewal` is not `renew` or `non_renewing`. | 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/account/data-inventory Get data inventory Returns, for every category of data Brix holds about the workspace, what it is, whether it identifies a person, how long it is kept, whether it is included in a data export, and whether it is deleted when the account is closed. Pass `?counts=1` to include a row count for each category. Auth: Bearer token. Permission: `settings.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `counts` | query | "1" | no | `1` adds a row count per table (slower). | ```bash curl "https://api.brixsignage.com/v1/account/data-inventory" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.generatedAt` | string | ISO-8601 timestamp (UTC). | | `data.counted` | boolean | | | `data.categories` | array of object | | | `data.categories[].id` | string | | | `data.categories[].label` | string | | | `data.categories[].what` | string | | | `data.categories[].personal` | string | | | `data.categories[].retention` | string | | | `data.categories[].rows` | integer \| null | | | `data.categories[].tables` | array of object | | | `data.categories[].tables[].table` | string | | | `data.categories[].tables[].rows` | integer \| null | | | `data.categories[].tables[].inExport` | boolean | | | `data.categories[].tables[].exportNote` | string \| null | | | `data.categories[].tables[].erasedOnClose` | boolean | | | `data.categories[].tables[].retainedNote` | string \| null | | | `data.categories[].tables[].strippedFields` | array of string | | | `data.residency` | object \| null | Where the workspace's data is stored. `database`, `media` and `backups` are internal store names. | | `data.notes` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/downgrade-monthly Switch to monthly billing Switches the workspace from annual to monthly billing at the end of the current annual term. Nothing is charged or refunded now. Auth: Bearer token. Permission: `billing.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/account/downgrade-monthly" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.cadence` | "annual" | Unchanged until the annual term ends. | | `data.scheduledCadence` | "monthly" | | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `no_subscription`, `already_monthly`, or `under_contract`. | 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 502: The switch could not be scheduled. | 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/account/encryption-key Get encryption key status Returns whether the workspace can use its own encryption key in Azure Key Vault, what that key covers, its identifier, its status, and the progress of re-encrypting existing data under it. Auth: Bearer token. Permission: `settings.view`. ```bash curl "https://api.brixsignage.com/v1/account/encryption-key" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.available` | boolean | Customer-managed keys can be used on this platform. | | `data.covers` | string | | | `data.doesNotCover` | string | | | `data.key` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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. | ### PUT /v1/account/encryption-key Enable customer-managed key Enables encryption with the workspace's own key in Azure Key Vault. Brix verifies the key, generates and wraps a data key, and starts re-encrypting existing secrets under it. Every new secret created after this call is protected with the customer's key. Auth: Bearer token. Permission: `settings.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `tenantId` | string | yes | Your Microsoft Entra tenant id (a UUID). | | `keyId` | string | yes | `https://.vault.azure.net/keys//`. | ```bash curl -X PUT "https://api.brixsignage.com/v1/account/encryption-key" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.available` | boolean | Customer-managed keys can be used on this platform. | | `data.covers` | string | | | `data.doesNotCover` | string | | | `data.key` | object \| null | | | `data.firstPass` | object | The first re-encryption pass, run inside the request; the rest runs hourly. | | `data.firstPass.status` | string | | | `data.firstPass.remaining` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `conflict` (a key is already set; turn it off first) or `key_check_failed`. | 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: Bad `tenantId` or `keyId`. | 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 503: `not_available` on this platform. | 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/account/encryption-key Disable customer-managed key Disables the workspace's customer-managed encryption key. Every secret currently protected by that key is first moved back under the Brix-managed key, so nothing becomes unreadable, and only then is the key record removed. Returns 409 if the customer-managed key can no longer be reached. **Notes.** - `key` is null when the first pass moved every secret back; otherwise it shows `disabling` until the hourly pass finishes. Auth: Bearer token. Permission: `settings.edit`. ```bash curl -X DELETE "https://api.brixsignage.com/v1/account/encryption-key" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.available` | boolean | Customer-managed keys can be used on this platform. | | `data.covers` | string | | | `data.doesNotCover` | string | | | `data.key` | object \| null | | | `data.firstPass` | object | The first re-encryption pass, run inside the request; the rest runs hourly. | | `data.firstPass.status` | string | | | `data.firstPass.remaining` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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 key is set. | 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: `key_unavailable`: the vault did not answer, so secrets cannot be moved back. | 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/account/encryption-key/verify Verify encryption key Performs a dry run against the workspace's own encryption key: it wraps and unwraps a random test value to confirm the key still works. Nothing is stored or changed. Auth: Bearer token. Permission: `settings.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `tenantId` | string | yes | Your Microsoft Entra tenant id (a UUID). | | `keyId` | string | yes | `https://.vault.azure.net/keys//`. | ```bash curl -X POST "https://api.brixsignage.com/v1/account/encryption-key/verify" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `key_check_failed`: the vault refused (`kind` says why). | 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: Bad `tenantId` or `keyId`. | 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 503: `not_available` on this platform. | 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/account/export Download full data export Returns a data export of the workspace as one downloadable file: the workspace record, its members, nine content tables and the last 365 days of activity-log events. Credentials are never included. Because the file holds every member's personal data, it requires permission to edit billing for the whole workspace. Brix does not email or store the export. For every table and the complete activity log, use the export manifest and its parts. **Notes.** - No `{ data }` envelope: the body is the export file (`Content-Disposition: attachment`). - Credentials are never exported: their columns are kept with `null` or a `[secret; not exported]` / `[encrypted; not exported]` marker. - `generatedBy.kind` is `user` even when an API key made the export (and then `userId` is absent). - The complete export (every table, the whole audit log) is `GET /v1/account/export/manifest` and its parts. Auth: Bearer token. Permission: `billing.edit`. ```bash curl "https://api.brixsignage.com/v1/account/export" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `schemaVersion` | 1 | | | `generatedAt` | string | ISO-8601 timestamp (UTC). | | `generatedBy` | object | Always `user`; `userId` is absent for an API key. | | `generatedBy.kind` | "user" | | | `generatedBy.userId` | string | | | `workspace` | object | The workspace record (every column; the Screen Lock PIN hash replaced by `kioskHasGlobalPin` inside `prefs`). | | `people` | array of object | | | `people[].id` | string | | | `people[].name` | string | | | `people[].email` | string | | | `people[].status` | string | | | `people[].lastLoginAt` | string \| null | | | `people[].createdAt` | string | ISO-8601 timestamp (UTC). | | `content` | object | Nine content tables, one object per row (all columns). | | `content.screens` | array of object | | | `content.mediaAssets` | array of object | | | `content.playlists` | array of object | | | `content.schedules` | array of object | | | `content.layouts` | array of object | | | `content.creatives` | array of object | | | `content.appInstances` | array of object | | | `content.dataSources` | array of object | | | `content.banners` | array of object | | | `auditEvents` | array of object | The last 365 days only. | | `limits` | object | | | `limits.auditEvents` | string | | | `limits.completeness` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/export/manifest Get export manifest Describes the contents of a full data export without downloading it: every table of workspace data with its row count, which tables are excluded from the export and why, and which fields are redacted and why. Use this to confirm what an export will and will not contain before downloading it. Auth: Bearer token. Permission: `billing.edit`. ```bash curl "https://api.brixsignage.com/v1/account/export/manifest" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.schemaVersion` | integer | | | `data.generatedAt` | string | ISO-8601 timestamp (UTC). | | `data.parts` | array of object | | | `data.parts[].part` | string | Pass to `/export/part/:part`. | | `data.parts[].table` | string | | | `data.parts[].rows` | integer | | | `data.excluded` | array of object | | | `data.excluded[].table` | string | | | `data.excluded[].reason` | string | | | `data.redactedColumns` | array of string | `table.column` values withheld from every part. | | `data.notes` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/export/part/{part} Download one export part Downloads one part of a full data export. Results are paged with a cursor rather than an offset, so rows are not skipped or duplicated if data changes while the export is running. Secret values are replaced with a marker rather than removed, so a withheld value can be told apart from one that was never set. Pass `?format=csv` to receive the same data as CSV, encoded with a UTF-8 byte-order mark for compatibility with Excel and guarded against spreadsheet formula injection. Use the `x-brix-next-cursor` response header to fetch the next page. **Notes.** - Credentials are never exported: their columns are kept with `null` or a `[secret; not exported]` / `[encrypted; not exported]` marker (the manifest lists them in `redactedColumns`). - `?format=csv` answers `text/csv` instead, with the cursor in the `X-Brix-Next-Cursor` header. Auth: Bearer token. Permission: `billing.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `part` | path | string | yes | A `part` from the manifest (a table name). | | `cursor` | query | string | no | `nextCursor` of the previous page. | ```bash curl "https://api.brixsignage.com/v1/account/export/part/{part}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of object | Rows of the table, all columns (withheld ones marked). | | `nextCursor` | string \| null | Null on the last page. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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 part. | 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/account/invoices List invoices Returns the workspace's invoice history, newest first: invoices Brix issued for an agreed term and invoices of the card subscription. The response includes `configured: false` when billing has not been set up yet. Auth: Bearer token. Permission: `billing.view`. ```bash curl "https://api.brixsignage.com/v1/account/invoices" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.configured` | boolean | | | `data.invoices` | array of object \| object | Newest first. Amounts are in currency units, not cents. | | `data.reason` | string | Present when the card subscription's invoices could not be read. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/invoices/{id}/file Download invoice file Downloads the PDF file for one invoice belonging to the calling workspace. **Notes.** - For an invoice Brix issued, when the PDF cannot be drawn the route answers 302 to the invoice's web page instead. Auth: Bearer token. Permission: `billing.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Invoice id from the list. | | `inline` | query | string | no | Any value: `Content-Disposition: inline`. | ```bash curl "https://api.brixsignage.com/v1/account/invoices/{id}/file" \ -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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: Not an invoice of 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 502: The billing provider did not answer; nothing changed. | 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/account/invoices/{id}/pay-link Create invoice pay link Creates a shareable payment link for one invoice, which can be paid by card or ACH bank transfer. The link can optionally be emailed directly, for example to forward to a finance team. **Notes.** - For an invoice Brix issued, `email` is ignored and `emailed` is always false. Auth: Bearer token. Permission: `billing.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Invoice id from the list. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `ttlDays` | number | no | Link lifetime in days, 1–180 (clamped). Default 30. | | `email` | string | no | Also email the link to this address (card subscription invoices only). | ```bash curl -X POST "https://api.brixsignage.com/v1/account/invoices/{id}/pay-link" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.url` | string | A web page where anyone with the link can see and pay the invoice. | | `data.emailed` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: Not an invoice of 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/account/invoices/{id}/pdf Get invoice PDF link Returns a short-lived download URL for one invoice's PDF. Brix confirms the invoice belongs to the calling workspace before returning the link. Auth: Bearer token. Permission: `billing.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Invoice id (a card subscription invoice). | | `inline` | query | string | no | Any value: the link opens in the browser instead of downloading. | ```bash curl "https://api.brixsignage.com/v1/account/invoices/{id}/pdf" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.url` | string | A short-lived download link. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: Not an invoice of this workspace (invoices Brix issued have no link here; use `/file`). | 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 502: The billing provider did not answer; nothing changed. | 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/account/invoices/pay-all-link Create pay-all link Creates one shareable link that lets whoever receives it pay all of the workspace's outstanding invoices at once. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `ttlDays` | number | no | Link lifetime in days, 1–180 (clamped). Default 30. | ```bash curl -X POST "https://api.brixsignage.com/v1/account/invoices/pay-all-link" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.url` | string | One web page that lists and pays every open invoice. | | `data.ttlDays` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `billing_not_configured`. | 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/account/invoices/pay-links-outstanding Create links for outstanding invoices Creates a separate hosted payment link for every invoice currently outstanding on the workspace. **Notes.** - Covers the card subscription's invoices only, not invoices Brix issued. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `ttlDays` | number | no | Link lifetime in days, 1–180 (clamped). Default 30. | ```bash curl -X POST "https://api.brixsignage.com/v1/account/invoices/pay-links-outstanding" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.links` | array of object | | | `data.links[].invoiceId` | string | | | `data.links[].number` | string | | | `data.links[].amountDue` | number | | | `data.links[].currencyCode` | string | | | `data.links[].url` | string | | | `data.ttlDays` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `billing_not_configured`. | 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 502: The billing provider did not answer; nothing changed. | 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/account/limits Get API limits Returns the workspace's API usage limits: the number of requests allowed per minute, the time window, and confirmation that the limit is counted per workspace across all API keys rather than per key. Also returns the `RateLimit-*` response headers to expect, and the delivery, retry, and retention behavior for webhooks. Use this before building an integration to size your request rate correctly. Auth: Bearer token. Permission: `settings.view`. ```bash curl "https://api.brixsignage.com/v1/account/limits" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.api` | object | | | `data.api.perMinute` | integer | | | `data.api.windowSeconds` | integer | | | `data.api.scope` | string | `workspace`: all keys of the workspace share one budget. | | `data.api.appliesTo` | string | | | `data.api.headers` | array of string | | | `data.api.onBreach` | string | | | `data.webhooks` | object | | | `data.webhooks.maxConsecutiveFailures` | integer | | | `data.webhooks.deliveryLogRetentionDays` | integer | | | `data.webhooks.delivery` | string | | | `data.webhooks.retry` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/me-prefs Get my preferences Returns preferences in three layers: `workspace` (the shared defaults), `user` (only the values the calling user has changed; empty for an API key), and `effective` (the merged result to use for display). **Notes.** - `user` is returned as saved: the per-user write does not check its keys or values. Auth: Bearer token. Permission: `billing.view`. ```bash curl "https://api.brixsignage.com/v1/account/me-prefs" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.workspace` | WorkspacePrefs | Workspace preferences. Every key is present (defaults filled in). | | `data.workspace.timeFormat` | "12h" \| "24h" | | | `data.workspace.weekStart` | "mon" \| "sun" | | | `data.workspace.dateFormat` | "mdy" \| "dmy" \| "ymd" | | | `data.workspace.tempUnits` | "f" \| "c" | | | `data.workspace.timeZone` | string | IANA time zone. Screens without their own zone use it, and so do schedules timed by the workspace. | | `data.workspace.language` | string | BCP 47 language tag. | | `data.workspace.multinational` | boolean | Shows the per-location and per-screen language settings. | | `data.workspace.navExtras` | array of string | Console pages switched on that the workspace size hides by default. | | `data.workspace.appBranding` | object | The look of the on-screen apps. | | `data.workspace.appBranding.accent` | string | | | `data.workspace.appBranding.accent2` | string | | | `data.workspace.appBranding.theme` | "dark" \| "light" | | | `data.workspace.appBranding.backdrop` | "brand" \| "wash" \| "plain" \| "aurora" \| "dots" \| "grid" \| "solid" | | | `data.workspace.appBranding.intensity` | number | | | `data.workspace.audio` | object | Default screen volume and a master mute. | | `data.workspace.audio.volume` | number | 0–100. | | `data.workspace.audio.muted` | boolean | | | `data.workspace.brand` | object | | | `data.workspace.brand.companyName` | string | | | `data.workspace.brand.logoUrl` | string | | | `data.workspace.requireAltText` | boolean | | | `data.workspace.showScreenLogo` | boolean | | | `data.workspace.standbyContentKind` | string \| null | What a screen with nothing to play shows; null = the default card. | | `data.workspace.standbyContentId` | string \| null | | | `data.workspace.tier` | "simple" \| "team" \| "enterprise" | Workspace size. It sets console defaults, not access. | | `data.workspace.industry` | string \| null | | | `data.workspace.onboardingCompletedAt` | string \| null | | | `data.workspace.setupStep` | string \| null | Walkthrough position (`s1`…`s5`, `l1`…`l6`, `done`). | | `data.workspace.orgNameSet` | boolean | | | `data.workspace.tiers` | array of integer | Location tree levels in use. | | `data.workspace.tierLabels` | object | Custom names for the location tree levels. | | `data.workspace.requireTwoFactor` | boolean | | | `data.workspace.requireSso` | boolean | | | `data.workspace.requireSsoPending` | boolean | Single sign-on was required, and turns on after the first sign-in through the connection. | | `data.workspace.requireSsoPendingSince` | string \| null | | | `data.workspace.ai` | object | | | `data.workspace.ai.enabled` | boolean | | | `data.workspace.ai.features` | object | Per feature (`tags`, `alt-text`, `moderation`, `transcription`, `translation`, `focal-region`, `embedding`); an absent feature follows `enabled`. | | `data.workspace.ai.dailyCallLimit` | integer \| null | | | `data.workspace.ai.redactPersonalData` | boolean | | | `data.workspace.retention` | object | Days kept per stream, only for the streams the workspace has set. An absent stream uses the platform window. | | `data.workspace.retention.offline` | number | | | `data.workspace.retention.screenshots` | number | | | `data.workspace.retention.playback` | number | | | `data.workspace.retention.deviceLogs` | number | | | `data.workspace.retention.vitals` | number | | | `data.workspace.retention.replays` | number | | | `data.workspace.retention.aiEvents` | number | | | `data.workspace.warehouse` | object | Data warehouse feed. Set by Brix; not writable here. | | `data.workspace.warehouse.enabled` | boolean | | | `data.workspace.warehouse.bucketBinding` | string | Set by Brix when the data warehouse feed is provisioned. | | `data.workspace.warehouse.tables` | array of string | | | `data.workspace.warehouse.lookbackDays` | integer | | | `data.workspace.supportAccess` | SupportAccessPolicy | | | `data.workspace.supportAccess.mode` | "open" \| "notify" \| "approve" | `open`: Brix support may open the workspace. `notify`: it may, and the owners are emailed. `approve`: a person here must approve each request. | | `data.workspace.supportAccess.grantMinutes` | integer | How long an approval lasts. | | `data.workspace.supportAccess.requestTtlMinutes` | integer | How long a request waits for an answer before it expires. | | `data.workspace.kioskHasGlobalPin` | boolean | A workspace Screen Lock PIN is set. The PIN and its hash are never returned. | | `data.user` | object | The calling user's own overrides, as saved. Empty for an API key. | | `data.effective` | WorkspacePrefs | Workspace preferences. Every key is present (defaults filled in). | | `data.effective.timeFormat` | "12h" \| "24h" | | | `data.effective.weekStart` | "mon" \| "sun" | | | `data.effective.dateFormat` | "mdy" \| "dmy" \| "ymd" | | | `data.effective.tempUnits` | "f" \| "c" | | | `data.effective.timeZone` | string | IANA time zone. Screens without their own zone use it, and so do schedules timed by the workspace. | | `data.effective.language` | string | BCP 47 language tag. | | `data.effective.multinational` | boolean | Shows the per-location and per-screen language settings. | | `data.effective.navExtras` | array of string | Console pages switched on that the workspace size hides by default. | | `data.effective.appBranding` | object | The look of the on-screen apps. | | `data.effective.appBranding.accent` | string | | | `data.effective.appBranding.accent2` | string | | | `data.effective.appBranding.theme` | "dark" \| "light" | | | `data.effective.appBranding.backdrop` | "brand" \| "wash" \| "plain" \| "aurora" \| "dots" \| "grid" \| "solid" | | | `data.effective.appBranding.intensity` | number | | | `data.effective.audio` | object | Default screen volume and a master mute. | | `data.effective.audio.volume` | number | 0–100. | | `data.effective.audio.muted` | boolean | | | `data.effective.brand` | object | | | `data.effective.brand.companyName` | string | | | `data.effective.brand.logoUrl` | string | | | `data.effective.requireAltText` | boolean | | | `data.effective.showScreenLogo` | boolean | | | `data.effective.standbyContentKind` | string \| null | What a screen with nothing to play shows; null = the default card. | | `data.effective.standbyContentId` | string \| null | | | `data.effective.tier` | "simple" \| "team" \| "enterprise" | Workspace size. It sets console defaults, not access. | | `data.effective.industry` | string \| null | | | `data.effective.onboardingCompletedAt` | string \| null | | | `data.effective.setupStep` | string \| null | Walkthrough position (`s1`…`s5`, `l1`…`l6`, `done`). | | `data.effective.orgNameSet` | boolean | | | `data.effective.tiers` | array of integer | Location tree levels in use. | | `data.effective.tierLabels` | object | Custom names for the location tree levels. | | `data.effective.requireTwoFactor` | boolean | | | `data.effective.requireSso` | boolean | | | `data.effective.requireSsoPending` | boolean | Single sign-on was required, and turns on after the first sign-in through the connection. | | `data.effective.requireSsoPendingSince` | string \| null | | | `data.effective.ai` | object | | | `data.effective.ai.enabled` | boolean | | | `data.effective.ai.features` | object | Per feature (`tags`, `alt-text`, `moderation`, `transcription`, `translation`, `focal-region`, `embedding`); an absent feature follows `enabled`. | | `data.effective.ai.dailyCallLimit` | integer \| null | | | `data.effective.ai.redactPersonalData` | boolean | | | `data.effective.retention` | object | Days kept per stream, only for the streams the workspace has set. An absent stream uses the platform window. | | `data.effective.retention.offline` | number | | | `data.effective.retention.screenshots` | number | | | `data.effective.retention.playback` | number | | | `data.effective.retention.deviceLogs` | number | | | `data.effective.retention.vitals` | number | | | `data.effective.retention.replays` | number | | | `data.effective.retention.aiEvents` | number | | | `data.effective.warehouse` | object | Data warehouse feed. Set by Brix; not writable here. | | `data.effective.warehouse.enabled` | boolean | | | `data.effective.warehouse.bucketBinding` | string | Set by Brix when the data warehouse feed is provisioned. | | `data.effective.warehouse.tables` | array of string | | | `data.effective.warehouse.lookbackDays` | integer | | | `data.effective.supportAccess` | SupportAccessPolicy | | | `data.effective.supportAccess.mode` | "open" \| "notify" \| "approve" | `open`: Brix support may open the workspace. `notify`: it may, and the owners are emailed. `approve`: a person here must approve each request. | | `data.effective.supportAccess.grantMinutes` | integer | How long an approval lasts. | | `data.effective.supportAccess.requestTtlMinutes` | integer | How long a request waits for an answer before it expires. | | `data.effective.kioskHasGlobalPin` | boolean | A workspace Screen Lock PIN is set. The PIN and its hash are never returned. | 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/account/onboarding Complete onboarding Finishes workspace onboarding in a single call: renames the workspace to the given name, records the industry and complexity tier, marks onboarding as complete, and seeds a starter set of ready-to-play content tailored to the chosen industry. Combining these into one call ensures the completion flag and the seeded content are never out of sync. **Notes.** - Every call adds the starter content again; it is not idempotent. - `tier` is stored as sent without a check. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `workspaceName` | string | no | Renames the workspace and sets the brand company name (cut at 120). | | `industry` | string \| null | no | Industry id (`cafe`, `qsr`, `gym`, …); picks the starter designs. | | `tier` | "simple" \| "team" \| "enterprise" | no | | | `setupStep` | string | no | Walkthrough position to save with the answers. | ```bash curl -X POST "https://api.brixsignage.com/v1/account/onboarding" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.prefs` | WorkspacePrefs | Workspace preferences. Every key is present (defaults filled in). | | `data.prefs.timeFormat` | "12h" \| "24h" | | | `data.prefs.weekStart` | "mon" \| "sun" | | | `data.prefs.dateFormat` | "mdy" \| "dmy" \| "ymd" | | | `data.prefs.tempUnits` | "f" \| "c" | | | `data.prefs.timeZone` | string | IANA time zone. Screens without their own zone use it, and so do schedules timed by the workspace. | | `data.prefs.language` | string | BCP 47 language tag. | | `data.prefs.multinational` | boolean | Shows the per-location and per-screen language settings. | | `data.prefs.navExtras` | array of string | Console pages switched on that the workspace size hides by default. | | `data.prefs.appBranding` | object | The look of the on-screen apps. | | `data.prefs.appBranding.accent` | string | | | `data.prefs.appBranding.accent2` | string | | | `data.prefs.appBranding.theme` | "dark" \| "light" | | | `data.prefs.appBranding.backdrop` | "brand" \| "wash" \| "plain" \| "aurora" \| "dots" \| "grid" \| "solid" | | | `data.prefs.appBranding.intensity` | number | | | `data.prefs.audio` | object | Default screen volume and a master mute. | | `data.prefs.audio.volume` | number | 0–100. | | `data.prefs.audio.muted` | boolean | | | `data.prefs.brand` | object | | | `data.prefs.brand.companyName` | string | | | `data.prefs.brand.logoUrl` | string | | | `data.prefs.requireAltText` | boolean | | | `data.prefs.showScreenLogo` | boolean | | | `data.prefs.standbyContentKind` | string \| null | What a screen with nothing to play shows; null = the default card. | | `data.prefs.standbyContentId` | string \| null | | | `data.prefs.tier` | "simple" \| "team" \| "enterprise" | Workspace size. It sets console defaults, not access. | | `data.prefs.industry` | string \| null | | | `data.prefs.onboardingCompletedAt` | string \| null | | | `data.prefs.setupStep` | string \| null | Walkthrough position (`s1`…`s5`, `l1`…`l6`, `done`). | | `data.prefs.orgNameSet` | boolean | | | `data.prefs.tiers` | array of integer | Location tree levels in use. | | `data.prefs.tierLabels` | object | Custom names for the location tree levels. | | `data.prefs.requireTwoFactor` | boolean | | | `data.prefs.requireSso` | boolean | | | `data.prefs.requireSsoPending` | boolean | Single sign-on was required, and turns on after the first sign-in through the connection. | | `data.prefs.requireSsoPendingSince` | string \| null | | | `data.prefs.ai` | object | | | `data.prefs.ai.enabled` | boolean | | | `data.prefs.ai.features` | object | Per feature (`tags`, `alt-text`, `moderation`, `transcription`, `translation`, `focal-region`, `embedding`); an absent feature follows `enabled`. | | `data.prefs.ai.dailyCallLimit` | integer \| null | | | `data.prefs.ai.redactPersonalData` | boolean | | | `data.prefs.retention` | object | Days kept per stream, only for the streams the workspace has set. An absent stream uses the platform window. | | `data.prefs.retention.offline` | number | | | `data.prefs.retention.screenshots` | number | | | `data.prefs.retention.playback` | number | | | `data.prefs.retention.deviceLogs` | number | | | `data.prefs.retention.vitals` | number | | | `data.prefs.retention.replays` | number | | | `data.prefs.retention.aiEvents` | number | | | `data.prefs.warehouse` | object | Data warehouse feed. Set by Brix; not writable here. | | `data.prefs.warehouse.enabled` | boolean | | | `data.prefs.warehouse.bucketBinding` | string | Set by Brix when the data warehouse feed is provisioned. | | `data.prefs.warehouse.tables` | array of string | | | `data.prefs.warehouse.lookbackDays` | integer | | | `data.prefs.supportAccess` | SupportAccessPolicy | | | `data.prefs.supportAccess.mode` | "open" \| "notify" \| "approve" | `open`: Brix support may open the workspace. `notify`: it may, and the owners are emailed. `approve`: a person here must approve each request. | | `data.prefs.supportAccess.grantMinutes` | integer | How long an approval lasts. | | `data.prefs.supportAccess.requestTtlMinutes` | integer | How long a request waits for an answer before it expires. | | `data.prefs.kioskHasGlobalPin` | boolean | A workspace Screen Lock PIN is set. The PIN and its hash are never returned. | | `data.seeded` | array of object | Starter designs added. | | `data.seeded[].id` | string | | | `data.seeded[].name` | string | | | `data.library` | integer | Starter library items added (photos, a playlist, a schedule, …). 0 when that step failed. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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 `industry`. | 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/account/payment-card Remove saved card Removes the workspace's saved card. If the payment option was set to card, it reverts to invoice billing. **Notes.** - When the payment option was `card`, it goes back to `invoice`. Auth: Bearer token. Permission: `billing.edit`. ```bash curl -X DELETE "https://api.brixsignage.com/v1/account/payment-card" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.removed` | boolean | | | `data.payment` | AccountPayment | Payment settings of an account that Brix invoices directly. | | `data.payment.option` | "card" \| "invoice" | How issued invoices are paid: charged to the saved card, or paid from the invoice email. | | `data.payment.card` | object \| null | | | `data.payment.lastPaid` | object \| null | The card the last invoice was paid with. | | `data.payment.cardAvailable` | boolean | Card payment can be set up for this workspace. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: A workspace billed by a partner. | 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: `not_brix_billed`. | 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/account/payment-card/complete Complete card setup Saves the card from a finished hosted card-setup session, given its `sessionId`. Brix re-reads the session from the payment provider and confirms it belongs to the calling workspace, returning 404 otherwise. The same result also happens automatically when the payment provider notifies Brix that the session completed. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `sessionId` | string | yes | The `card_session` value from the return URL (`cs_…`). | ```bash curl -X POST "https://api.brixsignage.com/v1/account/payment-card/complete" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.outcome` | "saved" \| "already_saved" \| "incomplete" | | | `data.payment` | AccountPayment | Payment settings of an account that Brix invoices directly. | | `data.payment.option` | "card" \| "invoice" | How issued invoices are paid: charged to the saved card, or paid from the invoice email. | | `data.payment.card` | object \| null | | | `data.payment.lastPaid` | object \| null | The card the last invoice was paid with. | | `data.payment.cardAvailable` | boolean | Card payment can be set up for this workspace. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: Not a card session of 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 409: `not_brix_billed`. | 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: `sessionId` missing or malformed. | 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 502: The session could not be read. | 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/account/payment-card/setup Start card setup Starts adding or replacing the saved card for a workspace billed directly by Brix. Returns a hosted checkout URL where the card is entered, unless `useLastPaid: true` is passed and the payment provider already holds a card that was last used to pay this workspace, in which case that card is saved directly. Returns 409 if the workspace is not billed directly by Brix, and 404 for white-label workspaces. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `useLastPaid` | boolean | no | Save the card the last invoice was paid with, when that is possible, instead of opening a page. | ```bash curl -X POST "https://api.brixsignage.com/v1/account/payment-card/setup" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: A workspace billed by a partner. | 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: `not_brix_billed`, or `card_unavailable`. | 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 502: The card page could not be opened. | 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/account/payment-methods List payment methods Returns the workspace's saved payment methods, including cards and ACH bank accounts. When there are two or more methods and none is the backup, one method that is not the primary is made the backup. **Notes.** - A read that can write: with two or more methods and no backup, the handler makes one non-primary method the backup and records it in the audit log. Auth: Bearer token. Permission: `billing.view`. ```bash curl "https://api.brixsignage.com/v1/account/payment-methods" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.configured` | boolean | False when billing is not set up (or could not be read, see `reason`). | | `data.sources` | array of object | | | `data.sources[].id` | string | | | `data.sources[].type` | string | `card`, `bank_account`, or a wallet type (`apple_pay`, `paypal_express_checkout`, …). | | `data.sources[].brand` | string \| null | | | `data.sources[].last4` | string \| null | | | `data.sources[].walletType` | string \| null | | | `data.sources[].gateway` | string \| null | | | `data.sources[].expiryMonth` | integer \| null | | | `data.sources[].expiryYear` | integer \| null | | | `data.sources[].status` | string | | | `data.sources[].primary` | boolean | | | `data.sources[].backup` | boolean | Charged when the primary fails. | | `data.reason` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/payment-methods/{id}/role Set payment method role Sets a saved payment method as the primary or backup method for the workspace. Auth: Bearer token. Permission: `billing.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Payment method id from the list. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `role` | "primary" \| "backup" | yes | | ```bash curl -X POST "https://api.brixsignage.com/v1/account/payment-methods/{id}/role" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.ok` | true | | | `data.id` | string | | | `data.role` | "primary" \| "backup" | | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: Not one of this workspace's payment methods. | 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: `billing_not_configured`. | 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: Bad `role`. | 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 502: The billing provider did not answer; nothing changed. | 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/account/payment-methods/manage-url Get payment methods management link Returns a hosted page URL where the workspace can add, replace, or remove a saved card or bank account. Auth: Bearer token. Permission: `billing.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/account/payment-methods/manage-url" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.url` | string | A hosted page to add, replace or remove a card or bank account. Open it in a browser. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `billing_not_configured`. | 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 502: The billing provider did not answer; nothing changed. | 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/account/payment-option Set payment option Sets how a workspace billed directly by Brix pays: `card` or `invoice`. Choosing `card` requires a saved card and returns 409 if none exists; every invoice Brix issues is then charged to that card automatically. Returns 409 if the workspace is billed through the billing provider instead, and 404 for white-label workspaces. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `option` | "card" \| "invoice" | yes | | ```bash curl -X PATCH "https://api.brixsignage.com/v1/account/payment-option" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.changed` | boolean | | | `data.payment` | AccountPayment | Payment settings of an account that Brix invoices directly. | | `data.payment.option` | "card" \| "invoice" | How issued invoices are paid: charged to the saved card, or paid from the invoice email. | | `data.payment.card` | object \| null | | | `data.payment.lastPaid` | object \| null | The card the last invoice was paid with. | | `data.payment.cardAvailable` | boolean | Card payment can be set up for this workspace. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: A workspace billed by a partner. | 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: `not_brix_billed`, or `card_required` (save a card first). | 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: Bad `option`. | 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/account/prefs Update workspace preferences Updates workspace-level preferences such as locale, time zone, date and number formats, brand kit, and plan tier. Any field you include is overwritten; fields you omit keep their current value. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `kioskPin` | string \| null | no | Set (4–8 digits) or clear (null) the workspace Screen Lock PIN. Stored as a hash. | | `requireSso` | boolean | no | Needs a working single sign-on connection with verified domains; until someone signs in through it, the request is held (`requireSsoPending`). | | `requireTwoFactor` | boolean | no | Turning it on needs the calling user to have two-factor sign-in set up, so an API key cannot turn it on. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/account/prefs" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"timeFormat":"24h","timeZone":"America/Chicago"}' ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | WorkspacePrefs | Workspace preferences. Every key is present (defaults filled in). | | `data.timeFormat` | "12h" \| "24h" | | | `data.weekStart` | "mon" \| "sun" | | | `data.dateFormat` | "mdy" \| "dmy" \| "ymd" | | | `data.tempUnits` | "f" \| "c" | | | `data.timeZone` | string | IANA time zone. Screens without their own zone use it, and so do schedules timed by the workspace. | | `data.language` | string | BCP 47 language tag. | | `data.multinational` | boolean | Shows the per-location and per-screen language settings. | | `data.navExtras` | array of string | Console pages switched on that the workspace size hides by default. | | `data.appBranding` | object | The look of the on-screen apps. | | `data.appBranding.accent` | string | | | `data.appBranding.accent2` | string | | | `data.appBranding.theme` | "dark" \| "light" | | | `data.appBranding.backdrop` | "brand" \| "wash" \| "plain" \| "aurora" \| "dots" \| "grid" \| "solid" | | | `data.appBranding.intensity` | number | | | `data.audio` | object | Default screen volume and a master mute. | | `data.audio.volume` | number | 0–100. | | `data.audio.muted` | boolean | | | `data.brand` | object | | | `data.brand.companyName` | string | | | `data.brand.logoUrl` | string | | | `data.requireAltText` | boolean | | | `data.showScreenLogo` | boolean | | | `data.standbyContentKind` | string \| null | What a screen with nothing to play shows; null = the default card. | | `data.standbyContentId` | string \| null | | | `data.tier` | "simple" \| "team" \| "enterprise" | Workspace size. It sets console defaults, not access. | | `data.industry` | string \| null | | | `data.onboardingCompletedAt` | string \| null | | | `data.setupStep` | string \| null | Walkthrough position (`s1`…`s5`, `l1`…`l6`, `done`). | | `data.orgNameSet` | boolean | | | `data.tiers` | array of integer | Location tree levels in use. | | `data.tierLabels` | object | Custom names for the location tree levels. | | `data.requireTwoFactor` | boolean | | | `data.requireSso` | boolean | | | `data.requireSsoPending` | boolean | Single sign-on was required, and turns on after the first sign-in through the connection. | | `data.requireSsoPendingSince` | string \| null | | | `data.ai` | object | | | `data.ai.enabled` | boolean | | | `data.ai.features` | object | Per feature (`tags`, `alt-text`, `moderation`, `transcription`, `translation`, `focal-region`, `embedding`); an absent feature follows `enabled`. | | `data.ai.dailyCallLimit` | integer \| null | | | `data.ai.redactPersonalData` | boolean | | | `data.retention` | object | Days kept per stream, only for the streams the workspace has set. An absent stream uses the platform window. | | `data.retention.offline` | number | | | `data.retention.screenshots` | number | | | `data.retention.playback` | number | | | `data.retention.deviceLogs` | number | | | `data.retention.vitals` | number | | | `data.retention.replays` | number | | | `data.retention.aiEvents` | number | | | `data.warehouse` | object | Data warehouse feed. Set by Brix; not writable here. | | `data.warehouse.enabled` | boolean | | | `data.warehouse.bucketBinding` | string | Set by Brix when the data warehouse feed is provisioned. | | `data.warehouse.tables` | array of string | | | `data.warehouse.lookbackDays` | integer | | | `data.supportAccess` | SupportAccessPolicy | | | `data.supportAccess.mode` | "open" \| "notify" \| "approve" | `open`: Brix support may open the workspace. `notify`: it may, and the owners are emailed. `approve`: a person here must approve each request. | | `data.supportAccess.grantMinutes` | integer | How long an approval lasts. | | `data.supportAccess.requestTtlMinutes` | integer | How long a request waits for an answer before it expires. | | `data.kioskHasGlobalPin` | boolean | A workspace Screen Lock PIN is set. The PIN and its hash are never returned. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `sso_not_configured`, `two_factor_not_enrolled`, or a PIN that is not 4–8 digits. | 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/account/reactivate Reactivate account Reverses a scheduled cancellation, or resumes an account that was paused for seasonal closure. Auth: Bearer token. Permission: `billing.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/account/reactivate" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.status` | "active" | | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `already_active`, `pending_deletion`, `payment_required`, `payment_method_required`, `not_reactivatable`, or `under_contract`. | 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 502: The billing provider did not answer; nothing changed. | 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/account/retention Get retention settings Returns the workspace's data retention schedule: every data stream that can be governed, the retention window currently in effect for it, and the allowed range for that window. Auth: Bearer token. Permission: `settings.view`. ```bash curl "https://api.brixsignage.com/v1/account/retention" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | RetentionSchedule | | | `data.policy` | object | Days kept per stream, only for the streams the workspace has set. An absent stream uses the platform window. | | `data.policy.offline` | number | | | `data.policy.screenshots` | number | | | `data.policy.playback` | number | | | `data.policy.deviceLogs` | number | | | `data.policy.vitals` | number | | | `data.policy.replays` | number | | | `data.policy.aiEvents` | number | | | `data.effective` | object | Days kept per stream now. | | `data.effective.offline` | integer | | | `data.effective.screenshots` | integer | | | `data.effective.playback` | integer | | | `data.effective.deviceLogs` | integer | | | `data.effective.vitals` | integer | | | `data.effective.replays` | integer | | | `data.effective.aiEvents` | integer | | | `data.bounds` | object | | | `data.bounds.offline` | object | | | `data.bounds.offline.min` | integer | | | `data.bounds.offline.max` | integer | | | `data.bounds.offline.platformDefault` | integer | | | `data.bounds.offline.label` | string | | | `data.bounds.offline.what` | string | | | `data.bounds.screenshots` | object | | | `data.bounds.screenshots.min` | integer | | | `data.bounds.screenshots.max` | integer | | | `data.bounds.screenshots.platformDefault` | integer | | | `data.bounds.screenshots.label` | string | | | `data.bounds.screenshots.what` | string | | | `data.bounds.playback` | object | | | `data.bounds.playback.min` | integer | | | `data.bounds.playback.max` | integer | | | `data.bounds.playback.platformDefault` | integer | | | `data.bounds.playback.label` | string | | | `data.bounds.playback.what` | string | | | `data.bounds.deviceLogs` | object | | | `data.bounds.deviceLogs.min` | integer | | | `data.bounds.deviceLogs.max` | integer | | | `data.bounds.deviceLogs.platformDefault` | integer | | | `data.bounds.deviceLogs.label` | string | | | `data.bounds.deviceLogs.what` | string | | | `data.bounds.vitals` | object | | | `data.bounds.vitals.min` | integer | | | `data.bounds.vitals.max` | integer | | | `data.bounds.vitals.platformDefault` | integer | | | `data.bounds.vitals.label` | string | | | `data.bounds.vitals.what` | string | | | `data.bounds.replays` | object | | | `data.bounds.replays.min` | integer | | | `data.bounds.replays.max` | integer | | | `data.bounds.replays.platformDefault` | integer | | | `data.bounds.replays.label` | string | | | `data.bounds.replays.what` | string | | | `data.bounds.aiEvents` | object | | | `data.bounds.aiEvents.min` | integer | | | `data.bounds.aiEvents.max` | integer | | | `data.bounds.aiEvents.platformDefault` | integer | | | `data.bounds.aiEvents.label` | string | | | `data.bounds.aiEvents.what` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/retention Set retention window Sets the retention window, in days, for one or more data streams. Sending `null` for a stream resets it to the platform default. Values are clamped to the allowed range for each stream. Auth: Bearer token. Permission: `settings.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `offline` | number \| null | no | | | `screenshots` | number \| null | no | | | `playback` | number \| null | no | | | `deviceLogs` | number \| null | no | | | `vitals` | number \| null | no | | | `replays` | number \| null | no | | | `aiEvents` | number \| null | no | | ```bash curl -X PATCH "https://api.brixsignage.com/v1/account/retention" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | RetentionSchedule | | | `data.policy` | object | Days kept per stream, only for the streams the workspace has set. An absent stream uses the platform window. | | `data.policy.offline` | number | | | `data.policy.screenshots` | number | | | `data.policy.playback` | number | | | `data.policy.deviceLogs` | number | | | `data.policy.vitals` | number | | | `data.policy.replays` | number | | | `data.policy.aiEvents` | number | | | `data.effective` | object | Days kept per stream now. | | `data.effective.offline` | integer | | | `data.effective.screenshots` | integer | | | `data.effective.playback` | integer | | | `data.effective.deviceLogs` | integer | | | `data.effective.vitals` | integer | | | `data.effective.replays` | integer | | | `data.effective.aiEvents` | integer | | | `data.bounds` | object | | | `data.bounds.offline` | object | | | `data.bounds.offline.min` | integer | | | `data.bounds.offline.max` | integer | | | `data.bounds.offline.platformDefault` | integer | | | `data.bounds.offline.label` | string | | | `data.bounds.offline.what` | string | | | `data.bounds.screenshots` | object | | | `data.bounds.screenshots.min` | integer | | | `data.bounds.screenshots.max` | integer | | | `data.bounds.screenshots.platformDefault` | integer | | | `data.bounds.screenshots.label` | string | | | `data.bounds.screenshots.what` | string | | | `data.bounds.playback` | object | | | `data.bounds.playback.min` | integer | | | `data.bounds.playback.max` | integer | | | `data.bounds.playback.platformDefault` | integer | | | `data.bounds.playback.label` | string | | | `data.bounds.playback.what` | string | | | `data.bounds.deviceLogs` | object | | | `data.bounds.deviceLogs.min` | integer | | | `data.bounds.deviceLogs.max` | integer | | | `data.bounds.deviceLogs.platformDefault` | integer | | | `data.bounds.deviceLogs.label` | string | | | `data.bounds.deviceLogs.what` | string | | | `data.bounds.vitals` | object | | | `data.bounds.vitals.min` | integer | | | `data.bounds.vitals.max` | integer | | | `data.bounds.vitals.platformDefault` | integer | | | `data.bounds.vitals.label` | string | | | `data.bounds.vitals.what` | string | | | `data.bounds.replays` | object | | | `data.bounds.replays.min` | integer | | | `data.bounds.replays.max` | integer | | | `data.bounds.replays.platformDefault` | integer | | | `data.bounds.replays.label` | string | | | `data.bounds.replays.what` | string | | | `data.bounds.aiEvents` | object | | | `data.bounds.aiEvents.min` | integer | | | `data.bounds.aiEvents.max` | integer | | | `data.bounds.aiEvents.platformDefault` | integer | | | `data.bounds.aiEvents.label` | string | | | `data.bounds.aiEvents.what` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/screen-pool Buy screen licences Sets how many screens the workspace pays for. An increase is charged now, prorated, to the payment method on file (a very small amount is added to the renewal instead). A reduction on a paid plan takes effect at the renewal and is not refunded; during the free trial a change takes effect at once. You cannot set the plan below the number of screens that are running. **Notes.** - An increase is charged now (prorated). A reduction on a paid plan is queued for the renewal and answers `scheduled: true`; in the trial it changes at once. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `quantity` | integer | yes | | ```bash curl -X POST "https://api.brixsignage.com/v1/account/screen-pool" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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 402: `card_declined` or `payment_incomplete`: the plan did not change (or needs our team). | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `screens_in_use` (below the screens running), `no_subscription`, `account_cancelled`, or `under_contract`. | 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: `quantity` is not a whole number from 1 to 10000. | 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 502: The change could not be made. | 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/account/screen-pool/estimate Estimate screen-pool change Returns the prorated cost of changing the screen pool before committing to it, including the exact amount that would be charged today and which card would be used. This is read-only and does not change anything; it uses POST rather than GET because the requested quantity must never be cached. **Notes.** - A POST because it takes an argument; it changes nothing. Auth: Bearer token. Permission: `billing.view`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `quantity` | integer | yes | | ```bash curl -X POST "https://api.brixsignage.com/v1/account/screen-pool/estimate" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.lines` | array of object | Line items of the invoice raised today. | | `data.lines[].description` | string | | | `data.lines[].amount` | number | | | `data.creditsApplied` | number | Credit for the unused part of the current term. | | `data.subTotal` | number | | | `data.amountDueNow` | number | Charged now, after credits (currency units, not cents). | | `data.currencyCode` | string | | | `data.nextBillingAt` | string \| null | | | `data.payWith` | string \| null | The payment method that is charged (`Visa •••• 4242`). | | `data.currentQuantity` | integer | | | `data.newQuantity` | integer | | | `data.renewalAmount` | number \| null | What each term costs after the change. | | `data.chargedToday` | boolean | False when the amount is too small to charge today (it is added to the renewal). | | `data.invoiced` | boolean | The account pays by invoice; nothing is charged to a card. | | `data.decreaseRequiresSupport` | false | Always false. Kept for older clients. | | `data.scheduledAtRenewal` | boolean | A reduction on a paid plan: it takes effect at `renewsAt`. | | `data.renewsAt` | string \| null | | | `data.inTrial` | boolean | | | `data.trialEndsAt` | string \| null | | | `data.minQuantity` | integer | The lowest allowed quantity: the screens running now (at least 1). | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `no_subscription`, `account_cancelled`, or `under_contract`. | 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: `quantity` is not a whole number from 1 to 10000. | 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 502: `estimate_unavailable`. | 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/account/screen-pool/scheduled Cancel scheduled screen-pool change Cancels a screen-pool reduction that is queued to take effect at renewal, keeping the current plan instead. Calling this when nothing is scheduled has no effect. **Notes.** - Succeeds when nothing is queued too. Auth: Bearer token. Permission: `billing.edit`. ```bash curl -X DELETE "https://api.brixsignage.com/v1/account/screen-pool/scheduled" \ -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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `no_subscription`, `account_cancelled`, or `under_contract`. | 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 502: The billing provider did not answer; nothing changed. | 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/account/support-access Get support access status Returns the workspace's current Brix support access policy (always allowed, allowed with notification, or approval required), every access request Brix support has raised along with its decision, and every support session opened on the workspace, with any still in progress marked as live. **Notes.** - A read that can write: pending requests past their expiry are marked `expired` first. Auth: Bearer token. Permission: `settings.view`. ```bash curl "https://api.brixsignage.com/v1/account/support-access" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.policy` | SupportAccessPolicy | | | `data.policy.mode` | "open" \| "notify" \| "approve" | `open`: Brix support may open the workspace. `notify`: it may, and the owners are emailed. `approve`: a person here must approve each request. | | `data.policy.grantMinutes` | integer | How long an approval lasts. | | `data.policy.requestTtlMinutes` | integer | How long a request waits for an answer before it expires. | | `data.modes` | array of object | | | `data.modes[].id` | "open" \| "notify" \| "approve" | | | `data.modes[].label` | string | | | `data.modes[].what` | string | | | `data.requests` | array of object | Newest 100. | | `data.requests[].id` | string | | | `data.requests[].staffEmail` | string | The Brix support person who asked. | | `data.requests[].reason` | string | | | `data.requests[].reasonCategory` | string \| null | | | `data.requests[].status` | "pending" \| "approved" \| "denied" \| "expired" \| "revoked" | | | `data.requests[].requestedAt` | string | ISO-8601 timestamp (UTC). | | `data.requests[].requestExpiresAt` | string | ISO-8601 timestamp (UTC). | | `data.requests[].decidedAt` | string \| null | | | `data.requests[].decidedByEmail` | string \| null | | | `data.requests[].decidedByName` | string \| null | | | `data.requests[].grantExpiresAt` | string \| null | | | `data.requests[].decisionNote` | string \| null | | | `data.requests[].grantLive` | boolean | | | `data.sessions` | array of object | Times Brix support opened the workspace, newest 100. | | `data.sessions[].id` | string | | | `data.sessions[].staffEmail` | string | | | `data.sessions[].reason` | string | | | `data.sessions[].startedAt` | string | ISO-8601 timestamp (UTC). | | `data.sessions[].endedAt` | string \| null | | | `data.sessions[].expiresAt` | string \| null | | | `data.sessions[].live` | boolean | | | `data.sessions[].recorded` | boolean | The session was screen-recorded. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/support-access/policy Set support access policy Sets whether Brix support may open the workspace: always allowed and recorded, allowed but the workspace is notified as it happens, or only with explicit approval for each request. Auth: Bearer token. Permission: `settings.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `mode` | "open" \| "notify" \| "approve" | no | `open`: Brix support may open the workspace. `notify`: it may, and the owners are emailed. `approve`: a person here must approve each request. | | `grantMinutes` | integer | no | How long an approval lasts. | | `requestTtlMinutes` | integer | no | How long a request waits for an answer before it expires. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/account/support-access/policy" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | SupportAccessPolicy | | | `data.mode` | "open" \| "notify" \| "approve" | `open`: Brix support may open the workspace. `notify`: it may, and the owners are emailed. `approve`: a person here must approve each request. | | `data.grantMinutes` | integer | How long an approval lasts. | | `data.requestTtlMinutes` | integer | How long a request waits for an answer before it expires. | 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: A Brix support session cannot change this. Also: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/support-access/revoke Revoke support access Immediately withdraws consent for Brix support access: ends every live support session and cancels any outstanding approval, in that order. Auth: Bearer token. Permission: `settings.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/account/support-access/revoke" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.sessionsEnded` | integer | | | `data.grantsRevoked` | 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: A Brix support session cannot do this. Also: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/account/upgrade-annual Switch to annual billing Switches the workspace's monthly subscription to annual, upfront billing. Returns 409 if the workspace is already on annual billing or has no active subscription. Auth: Bearer token. Permission: `billing.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/account/upgrade-annual" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.cadence` | "annual" | | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `no_subscription`, `under_contract`, or `already_annual`. | 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 502: The switch failed. | 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 503: `annual_switch_unavailable`: the switch is not self-serve yet; contact 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. | 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/account/upgrade-annual/estimate Estimate annual upgrade cost Returns the prorated cost of switching from monthly to annual billing before committing to it, including which card would be charged. Auth: Bearer token. Permission: `billing.view`. ```bash curl "https://api.brixsignage.com/v1/account/upgrade-annual/estimate" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.lines` | array of object | Line items of the invoice raised today. | | `data.lines[].description` | string | | | `data.lines[].amount` | number | | | `data.creditsApplied` | number | Credit for the unused part of the current term. | | `data.subTotal` | number | | | `data.amountDueNow` | number | Charged now, after credits (currency units, not cents). | | `data.currencyCode` | string | | | `data.nextBillingAt` | string \| null | | | `data.payWith` | string \| null | The payment method that is charged (`Visa •••• 4242`). | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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: `no_subscription` or `under_contract`. | 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 502: `estimate_unavailable`. | 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. | ## Ad slots Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/ad-slots List ad slots List the programmatic ad slots configured in the workspace. Each slot includes its provider, endpoint, venue or device identifiers, and its minimum and maximum duration limits. The exchange credential itself is never included; only the name of the stored credential reference is returned. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/ad-slots" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/ad-slots Create an ad slot with a `name` and a `config` object containing `provider`, `endpoint`, `credsRef`, `minSeconds`, `maxSeconds`, `podSeconds`, and `allowAudio`, plus optional `venueId`, `deviceTypeId`, and `nodeId`. The slot is created disabled and cannot request ads until you enable it with a later update. Auth: Bearer token. Permission: `integration.create`. ```bash curl -X POST "https://api.brixsignage.com/v1/ad-slots" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/ad-slots/{id} Get an ad slot Retrieve one ad slot, including the names of the credentials stored against it. Credential values are write-only and are never returned by this or any other endpoint. Auth: Bearer token. Permission: `integration.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl "https://api.brixsignage.com/v1/ad-slots/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/ad-slots/{id} Update an ad slot Edit an ad slot's name, node, or exchange configuration, or turn ad requesting on and off. Enabling a slot is what allows it to start spending, so this action is recorded in the activity log. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/ad-slots/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/ad-slots/{id} Delete an ad slot and stop it from requesting ads. Cached creatives and the record of impressions already played are kept, so past playback history remains available after the slot is removed. Auth: Bearer token. Permission: `integration.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/ad-slots/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/ad-slots/{id}/impressions List an ad slot's impressions List what an ad slot has actually played, and whether each impression beacon was recorded successfully. Results are returned newest first and the response is capped at 200 records. Use this to reconcile playback against an advertiser's invoice. Auth: Bearer token. Permission: `integration.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl "https://api.brixsignage.com/v1/ad-slots/{id}/impressions" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ### PUT /v1/ad-slots/{id}/secrets Set an ad slot's credentials Store the exchange credential for an ad slot. Send a map of reference names to values; the values are encrypted at rest and merged into any existing credentials. Setting a value to `null` deletes that reference. Only the credential names are ever returned, never the values. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X PUT "https://api.brixsignage.com/v1/ad-slots/{id}/secrets" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## AI Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/ai/events List AI activity events Return the workspace's record of every AI-powered call it made, including the feature, model, outcome, actor, duration, and number of redactions. Calls that were refused, for example because the feature is disabled or a usage limit was reached, also appear as entries, since a blocked request is only evidenced by a record of the refusal. The prompt and output text are never stored. Only events at locations where the caller holds the audit log view permission are returned, as in the audit log. The actor is returned in full only where the caller also holds the user view permission; otherwise only its kind, for example user or staff. Results are paginated and can be filtered by feature, outcome, and a starting point in time; a page can hold fewer entries than the limit. Auth: Bearer token. Permission: `audit-log.view`. ```bash curl "https://api.brixsignage.com/v1/ai/events" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/ai/events.csv Export AI activity events as CSV Return the same AI activity data as a CSV file, suitable for sharing or importing elsewhere. It applies the same filters, the same location scope and the same actor rule as the JSON list, so the export always matches what the list view shows. Auth: Bearer token. ```bash curl "https://api.brixsignage.com/v1/ai/events.csv" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/ai/models List AI models in use Return which AI model powers each AI feature, when it was adopted, what it replaced, and the safe-use guidance shown to users at the point of use. This reflects the exact configuration currently enforced, so it cannot drift out of step with actual behavior. Auth: Bearer token. ```bash curl "https://api.brixsignage.com/v1/ai/models" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/ai/summary Summarize AI activity Return counts of AI calls grouped by feature, model, and outcome over a time window that defaults to 30 days. Totals separate calls that ran successfully, calls that failed, and calls that were refused due to policy or usage limits, so a refusal is never confused with a failure. Only calls at locations where the caller holds the audit log view permission are counted. Auth: Bearer token. Permission: `audit-log.view`. ```bash curl "https://api.brixsignage.com/v1/ai/summary" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Alert channels Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### POST /v1/alert-channels/teams/test Send a test Microsoft Teams alert Send a sample alert card to a Microsoft Teams webhook to confirm it is configured correctly. Provide the webhook `url` in the request body; it must be a Microsoft Teams Workflow URL. The response returns `ok`, `status`, and an optional `message`, and never includes the URL you sent. This operation is rate-limited to 10 requests per minute. A refusal from Microsoft Teams is NOT an HTTP error: the answer is 200 with `ok: false` and a `message` to show. Auth: Bearer token. Permission: `alert-rule.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | A Microsoft Teams workflow URL, or the masked URL a saved rule shows (then send `ruleId` too). | | `ruleId` | string | no | With a masked `url`: the rule whose saved URL to use. | ```bash curl -X POST "https://api.brixsignage.com/v1/alert-channels/teams/test" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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: The URL is not a Microsoft Teams workflow URL, or a masked URL matches no URL saved on `ruleId`. | 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 10 calls in 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. | ## Alert events Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/alert-events List alert events List alerts that have fired against your screens, each combined with the screen's name. This endpoint is read-only: alerts open automatically when a condition is detected and close automatically on recovery. Only currently open alerts are returned, as a limited list, so a long history does not push them out. Open events (up to 300, newest first) followed by recently closed ones (up to 300). Not paginated. **Notes.** - The route-registry description names `alert-rule.view`, but the route is gated by `screen.view`; `screen.view` is what a key needs. Auth: Bearer token. Permission: `screen.view`. ```bash curl "https://api.brixsignage.com/v1/alert-events" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of AlertEvent | | | `data[].id` | string | Alert event id. | | `data[].code` | string | What fired, e.g. `connection-lost`, `display-off`, `storage-critical`. | | `data[].severity` | string | `info`, `warning` or `critical`. | | `data[].screenId` | string | | | `data[].screenName` | string | `(unknown screen)` if the screen is gone. | | `data[].message` | string | | | `data[].nextAction` | string | Suggested next step; may be empty. | | `data[].openedAt` | string | ISO-8601 timestamp (UTC). | | `data[].closedAt` | string \| null | Null while open. | | `data[].detail` | object \| null | Structured detail keyed by `kind` (`recovery`, `outage-history`, `network-unstable`, `display-off`), or 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/alert-events/{id}/close Close an alert event Manually close an alert event before it would close automatically on recovery. Access is checked against the screen where the alert fired. **Notes.** - The route-registry description names `alert-rule.edit`, but the route is gated by `screen.edit` at the event's location. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Alert event id. | ```bash curl -X POST "https://api.brixsignage.com/v1/alert-events/{id}/close" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | AlertEvent | One firing of an alert rule against a screen. | | `data.id` | string | Alert event id. | | `data.code` | string | What fired, e.g. `connection-lost`, `display-off`, `storage-critical`. | | `data.severity` | string | `info`, `warning` or `critical`. | | `data.screenId` | string | | | `data.screenName` | string | `(unknown screen)` if the screen is gone. | | `data.message` | string | | | `data.nextAction` | string | Suggested next step; may be empty. | | `data.openedAt` | string | ISO-8601 timestamp (UTC). | | `data.closedAt` | string \| null | Null while open. | | `data.detail` | object \| null | Structured detail keyed by `kind` (`recovery`, `outage-history`, `network-unstable`, `display-off`), or 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: No such event. | 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: `already_closed`. | 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. | ## Alert rules Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/alert-rules List alert rules List the alert rules configured for your workspace. Each rule includes its name, trigger, scope configuration, and whether it is enabled. Results are limited to the organization nodes you have access to. Microsoft Teams workflow URLs in `config.teamsUrls` and web addresses in `config.recipients` are credentials, so the response shows them masked: the host and the last four characters, for example `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. Auth: Bearer token. Permission: `alert-rule.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Page size. Omit to get every row; pass it to page by `cursor`. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | ```bash curl "https://api.brixsignage.com/v1/alert-rules" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of AlertRule | | | `data[].id` | string | Alert rule id. | | `data[].spaceId` | string | | | `data[].name` | string | | | `data[].trigger` | string | The first trigger code, e.g. `connection-lost`. | | `data[].config` | AlertRuleConfig | The rule's trigger, scope and delivery settings. Other keys pass through unchanged. | | `data[].config.codes` | array of string | Every trigger code the rule watches; the first is also `trigger`. | | `data[].config.severity` | string | | | `data[].config.scopeKind` | string | `workspace`, `org_unit`, `location`, `screen_group` or `screen`. | | `data[].config.scopeId` | string | | | `data[].config.thresholds` | object | | | `data[].config.channels` | array of string | `in-app`, `email`, `webhook`, `teams`. | | `data[].config.recipients` | array of string | Email addresses, `role:` tokens and webhook URLs. Webhook URLs are Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `data[].config.teamsUrls` | array of string | Microsoft Teams workflow URLs for the `teams` channel. Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `data[].enabled` | boolean | | | `data[].nodeId` | string \| null | Home location; null = workspace root. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | | | `nextCursor` | string \| null | Present when `?limit` was passed. Send it back as `?cursor=` for the next page; null on the last page. | | `total` | integer | Total matching rows, when the route computes it. | 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/alert-rules Create an alert rule with a `name`, `trigger`, optional `config`, optional `enabled` flag, and optional `nodeId`. If the scope named in `config` does not resolve to an existing target, the request is refused rather than saved. A Microsoft Teams channel is also refused unless `config.teamsUrls` contains at least one valid Microsoft Teams Workflow HTTPS URL. The response shows each URL masked. **Notes.** - The 201 body is the row as written, not re-read: `nodeId` is absent when the create did not set one. Auth: Bearer token. Permission: `alert-rule.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `trigger` | string | yes | | | `config` | AlertRuleConfig | no | The rule's trigger, scope and delivery settings. Other keys pass through unchanged. | | `config.codes` | array of string | no | Every trigger code the rule watches; the first is also `trigger`. | | `config.severity` | string | no | | | `config.scopeKind` | string | no | `workspace`, `org_unit`, `location`, `screen_group` or `screen`. | | `config.scopeId` | string | no | | | `config.thresholds` | object | no | | | `config.channels` | array of string | no | `in-app`, `email`, `webhook`, `teams`. | | `config.recipients` | array of string | no | Email addresses, `role:` tokens and webhook URLs. Webhook URLs are Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `config.teamsUrls` | array of string | no | Microsoft Teams workflow URLs for the `teams` channel. Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `enabled` | boolean | no | | | `nodeId` | string \| null | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/alert-rules" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Alert rule id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.trigger` | string | The first trigger code, e.g. `connection-lost`. | | `data.config` | AlertRuleConfig | The rule's trigger, scope and delivery settings. Other keys pass through unchanged. | | `data.config.codes` | array of string | Every trigger code the rule watches; the first is also `trigger`. | | `data.config.severity` | string | | | `data.config.scopeKind` | string | `workspace`, `org_unit`, `location`, `screen_group` or `screen`. | | `data.config.scopeId` | string | | | `data.config.thresholds` | object | | | `data.config.channels` | array of string | `in-app`, `email`, `webhook`, `teams`. | | `data.config.recipients` | array of string | Email addresses, `role:` tokens and webhook URLs. Webhook URLs are Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `data.config.teamsUrls` | array of string | Microsoft Teams workflow URLs for the `teams` channel. Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `data.enabled` | boolean | | | `data.nodeId` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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 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 422: `name` or `trigger` is missing, the scope target does not exist, or a Microsoft Teams URL is not a workflow URL. | 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/alert-rules/{id} Get an alert rule Get one alert rule, including its full trigger configuration. Access is limited to the organization nodes you can see. Microsoft Teams workflow URLs in `config.teamsUrls` and web addresses in `config.recipients` are credentials, so the response shows them masked: the host and the last four characters, for example `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. Auth: Bearer token. Permission: `alert-rule.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Alert rule id. | ```bash curl "https://api.brixsignage.com/v1/alert-rules/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | AlertRule | | | `data.id` | string | Alert rule id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.trigger` | string | The first trigger code, e.g. `connection-lost`. | | `data.config` | AlertRuleConfig | The rule's trigger, scope and delivery settings. Other keys pass through unchanged. | | `data.config.codes` | array of string | Every trigger code the rule watches; the first is also `trigger`. | | `data.config.severity` | string | | | `data.config.scopeKind` | string | `workspace`, `org_unit`, `location`, `screen_group` or `screen`. | | `data.config.scopeId` | string | | | `data.config.thresholds` | object | | | `data.config.channels` | array of string | `in-app`, `email`, `webhook`, `teams`. | | `data.config.recipients` | array of string | Email addresses, `role:` tokens and webhook URLs. Webhook URLs are Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `data.config.teamsUrls` | array of string | Microsoft Teams workflow URLs for the `teams` channel. Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `data.enabled` | boolean | | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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. | ### PATCH /v1/alert-rules/{id} Update an alert rule's `name`, `trigger`, `config`, `enabled` flag, or `nodeId`. The same scope validation used when creating a rule applies here. To keep a saved URL, send its masked value back unchanged. To replace it, send the new URL. A masked value that matches no saved URL is refused. Auth: Bearer token. Permission: `alert-rule.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Alert rule id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `trigger` | string | no | | | `config` | AlertRuleConfig | no | The rule's trigger, scope and delivery settings. Other keys pass through unchanged. | | `config.codes` | array of string | no | Every trigger code the rule watches; the first is also `trigger`. | | `config.severity` | string | no | | | `config.scopeKind` | string | no | `workspace`, `org_unit`, `location`, `screen_group` or `screen`. | | `config.scopeId` | string | no | | | `config.thresholds` | object | no | | | `config.channels` | array of string | no | `in-app`, `email`, `webhook`, `teams`. | | `config.recipients` | array of string | no | Email addresses, `role:` tokens and webhook URLs. Webhook URLs are Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `config.teamsUrls` | array of string | no | Microsoft Teams workflow URLs for the `teams` channel. Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `enabled` | boolean | no | | | `nodeId` | string \| null | no | | | `baseUpdatedAt` | string | no | The `updatedAt` your edit is based on; 409 with the current row if it moved. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/alert-rules/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | AlertRule | | | `data.id` | string | Alert rule id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.trigger` | string | The first trigger code, e.g. `connection-lost`. | | `data.config` | AlertRuleConfig | The rule's trigger, scope and delivery settings. Other keys pass through unchanged. | | `data.config.codes` | array of string | Every trigger code the rule watches; the first is also `trigger`. | | `data.config.severity` | string | | | `data.config.scopeKind` | string | `workspace`, `org_unit`, `location`, `screen_group` or `screen`. | | `data.config.scopeId` | string | | | `data.config.thresholds` | object | | | `data.config.channels` | array of string | `in-app`, `email`, `webhook`, `teams`. | | `data.config.recipients` | array of string | Email addresses, `role:` tokens and webhook URLs. Webhook URLs are Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `data.config.teamsUrls` | array of string | Microsoft Teams workflow URLs for the `teams` channel. Masked: a delivery URL is a credential (a Microsoft Teams workflow URL carries it in `sig=`), so every response shows the host and the last four characters only — `https://prod-12.westus.logic.azure.com/…?sig=••••a1b2`. On update, send a masked value back unchanged to keep the saved URL, or a full URL to replace it. | | `data.enabled` | boolean | | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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 404: No rule 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: `conflict`: changed since `baseUpdatedAt`; the body carries `current`. | 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: The scope target does not exist, a Microsoft Teams URL is not a workflow URL, or a masked URL matches no saved URL. | 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/alert-rules/{id} Delete an alert rule. It moves to the recycle bin and stops firing immediately. Auth: Bearer token. Permission: `alert-rule.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Alert rule id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/alert-rules/{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 alert rule 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/alert-rules/{id}/restore Restore a deleted alert rule Restore an alert rule that was deleted within the last 30 days. The rule resumes firing once restored. Auth: Bearer token. Permission: `alert-rule.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Alert rule id. | ```bash curl -X POST "https://api.brixsignage.com/v1/alert-rules/{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 such alert rule in this workspace, or it was purged. | 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: `not_deleted`: the rule is not in the recycle bin. | 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. | ## API keys Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/api-keys List API keys Returns the workspace's API keys: name, assigned permissions, the location the key is pinned to (if any), and when each was created and last used. Key secrets are never returned. A key is listed only where the caller holds the API key view permission at the key's location; a workspace-wide key needs that permission for the whole workspace. **Notes.** - Not paginated. Revoked keys are listed too (`revokedAt` set). Auth: Bearer token. Permission: `api-key.view`. ```bash curl "https://api.brixsignage.com/v1/api-keys" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of ApiKey | | | `data[].id` | string | | | `data[].name` | string | | | `data[].preview` | string | The first characters of the key (`ak_us_live_Ab3x`), to recognise it. Not usable as a credential. | | `data[].permissions` | "all" \| array of string | `resource.action` permissions, or `"all"` (every permission, now and later). | | `data[].nodeId` | string \| null | The location the key is pinned to (it can act only there and below). Null = workspace-wide. | | `data[].lastUsedAt` | string \| null | | | `data[].expiresAt` | string \| null | Null = never expires. | | `data[].expired` | boolean | | | `data[].revokedAt` | string \| null | Set when the key was revoked; a revoked key no longer authenticates. | | `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. | ### POST /v1/api-keys Create API key Creates a new API key. Provide `name`, `permissions` (a list of `resource.action` strings, or `"all"`), and an optional `nodeId` to restrict the key to one location and everything beneath it. The raw key secret is returned exactly once, in this response; only its hash is stored afterward, so it cannot be retrieved again. Auth: Bearer token. Permission: `api-key.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `permissions` | "all" \| array of string | no | You must hold each one where the key applies. Default `[]` (a key that can do nothing). | | `nodeId` | string \| null | no | Pin the key to this location. Omit for a workspace-wide key, which needs its permissions workspace-wide. | | `expiresInDays` | number \| null | no | Days until the key stops working (at most 3650). Omit, null or 0 = never expires. | ```bash curl -X POST "https://api.brixsignage.com/v1/api-keys" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Menu board sync","permissions":["screen.view","media.create"],"expiresInDays":365}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.name` | string | | | `data.preview` | string | The first characters of the key (`ak_us_live_Ab3x`), to recognise it. Not usable as a credential. | | `data.permissions` | "all" \| array of string | `resource.action` permissions, or `"all"` (every permission, now and later). | | `data.nodeId` | string \| null | The location the key is pinned to (it can act only there and below). Null = workspace-wide. | | `data.lastUsedAt` | string \| null | | | `data.expiresAt` | string \| null | Null = never expires. | | `data.expired` | boolean | | | `data.revokedAt` | string \| null | Set when the key was revoked; a revoked key no longer authenticates. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.secret` | string | The full key. Shown ONLY in this response; store it now. Send it as `Authorization: Bearer `. | 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: You do not hold every permission you grant, or not at the key's location (a workspace-wide key needs them workspace-wide). Also refused for a Brix support session. | 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` missing, `expiresInDays` over 3650 or not a number, or `nodeId` not a location of 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. | ### DELETE /v1/api-keys/{id} Revoke API key Revokes an API key immediately, so it can no longer be used. You must hold the permission to delete keys at or above the location the key is pinned to, not merely somewhere else in the workspace. **Notes.** - The key stays in the list with `revokedAt` set. Revoking a key again answers 200 and moves `revokedAt` to now. Auth: Bearer token. Permission: `api-key.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | API key id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/api-keys/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.revoked` | 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: You lack `api-key.delete` at the key'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 key 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/api-keys/{id}/rotate Rotate API key Replaces an API key's secret with a new one, invalidating the old secret immediately. The new secret is returned exactly once, in this response. You must hold the permission to create keys at or above the location the key is pinned to. **Notes.** - The key keeps its id, name, permissions, location and expiry; `lastUsedAt` is reset to null. An expired key can be rotated and stays expired. Auth: Bearer token. Permission: `api-key.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | API key id. | ```bash curl -X POST "https://api.brixsignage.com/v1/api-keys/{id}/rotate" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.name` | string | | | `data.preview` | string | The first characters of the key (`ak_us_live_Ab3x`), to recognise it. Not usable as a credential. | | `data.permissions` | "all" \| array of string | `resource.action` permissions, or `"all"` (every permission, now and later). | | `data.nodeId` | string \| null | The location the key is pinned to (it can act only there and below). Null = workspace-wide. | | `data.lastUsedAt` | string \| null | | | `data.expiresAt` | string \| null | Null = never expires. | | `data.expired` | boolean | | | `data.revokedAt` | string \| null | Set when the key was revoked; a revoked key no longer authenticates. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.secret` | string | The full key. Shown ONLY in this response; store it now. Send it as `Authorization: Bearer `. | 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 key holds permissions you cannot grant at its location, you lack `api-key.create` there, or a Brix support session. | 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 key 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 409: `revoked`: a revoked key cannot be rotated; create a new one. | 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. | ## App instances Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/app-instances List app instances List every app configured in the workspace, including its id, `appKey`, name, configuration, and node. This is the data behind the My apps grid. Auth: Bearer token. Permission: `app-instance.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Page size. Omit to get every row; pass it to page by `cursor`. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | | `count` | query | "1" | no | With `limit`: also return `total`, the number of matching rows. | | `usableAt` | query | string | no | Location id: only rows usable at that location (homed there, at the workspace root, or shared to it). | ```bash curl "https://api.brixsignage.com/v1/app-instances" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of AppInstance | | | `data[].id` | string | App instance id. | | `data[].spaceId` | string | Workspace id. | | `data[].appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). | | `data[].name` | string | | | `data[].config` | any | The app's settings (JSON). The keys depend on `appKey`. | | `data[].nodeId` | string \| null | Home location; null = workspace root. | | `data[].lastSnapshotKey` | string \| null | Internal key of the last rendered thumbnail. | | `data[].lastSnapshotAt` | string \| null | | | `data[].importSourceId` | string \| null | Id in the system it was imported from, if imported. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | | `nextCursor` | string \| null | Present when `?limit` was passed. Send it back as `?cursor=` for the next page; null on the last page. | | `total` | integer | Total matching rows, when the route computes it. | ```json { "data": [ { "id": "app_3c4d5e6f7a8b9c0d", "spaceId": "space_1a2b3c4d5e6f7a8b", "appKey": "clock", "name": "Lobby clock", "config": { "format": "24h" }, "nodeId": null, "lastSnapshotKey": null, "lastSnapshotAt": null, "importSourceId": null, "createdAt": "2026-09-28T09:00:00.000Z", "updatedAt": "2026-09-28T09:00:00.000Z", "deletedAt": 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/app-instances Create an app instance Configure an app for the workspace with an `appKey`, `name`, and optional `config` and `nodeId`. `appKey` must match one of the apps offered by the store; list the available keys first with GET /v1/apps/catalog. **Notes.** - The 201 body is the row as written, not re-read from the database, so columns the create does not set (for example `lastSnapshotAt`) are absent rather than null. `GET` returns every column. Auth: Bearer token. Permission: `app-instance.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `appKey` | string | yes | A key from `GET /v1/apps/catalog`. An unknown key is refused (422 `unknown_app`). | | `name` | string | yes | | | `config` | object \| string | no | The app's settings: a JSON object, or the same as a JSON string. Default `{}`. | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | ```bash curl -X POST "https://api.brixsignage.com/v1/app-instances" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"appKey":"clock","name":"Lobby clock","config":{"format":"24h"}}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | App instance id. | | `data.spaceId` | string | Workspace id. | | `data.appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). | | `data.name` | string | | | `data.config` | any | The app's settings (JSON). The keys depend on `appKey`. | | `data.nodeId` | string \| null | Home location, when one was set or derived. | | `data.lastSnapshotKey` | string \| null | | | `data.lastSnapshotAt` | string \| null | | | `data.importSourceId` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | ```json { "data": { "id": "app_3c4d5e6f7a8b9c0d", "spaceId": "space_1a2b3c4d5e6f7a8b", "appKey": "clock", "name": "Lobby clock", "config": { "format": "24h" }, "createdAt": "2026-09-28T09:00:00.000Z", "updatedAt": "2026-09-28T09:00:00.000Z", "deletedAt": 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 422: Missing `appKey`/`name`, invalid JSON config, unknown app (`unknown_app`), or a location outside 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/app-instances/{id} Get an app instance Retrieve one configured app instance, including its full configuration. Auth: Bearer token. Permission: `app-instance.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | App instance id. | ```bash curl "https://api.brixsignage.com/v1/app-instances/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | AppInstance | An installed, configured app. | | `data.id` | string | App instance id. | | `data.spaceId` | string | Workspace id. | | `data.appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). | | `data.name` | string | | | `data.config` | any | The app's settings (JSON). The keys depend on `appKey`. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.lastSnapshotKey` | string \| null | Internal key of the last rendered thumbnail. | | `data.lastSnapshotAt` | string \| null | | | `data.importSourceId` | string \| null | Id in the system it was imported from, if imported. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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 app instance 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/app-instances/{id} Update an app instance Edit a configured app's name, configuration, or node. `config` replaces the entire configuration, so read the current value first before submitting changes. Auth: Bearer token. Permission: `app-instance.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | App instance id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `appKey` | string | no | A key from `GET /v1/apps/catalog`. An unknown key is refused (422 `unknown_app`). | | `name` | string | no | | | `config` | object \| string | no | The app's settings: a JSON object, or the same as a JSON string. Default `{}`. | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | | `baseUpdatedAt` | string | no | Optimistic concurrency: the `updatedAt` you read. A stale value is refused with 409 `conflict` and the `current` row. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/app-instances/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | AppInstance | An installed, configured app. | | `data.id` | string | App instance id. | | `data.spaceId` | string | Workspace id. | | `data.appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). | | `data.name` | string | | | `data.config` | any | The app's settings (JSON). The keys depend on `appKey`. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.lastSnapshotKey` | string \| null | Internal key of the last rendered thumbnail. | | `data.lastSnapshotAt` | string \| null | | | `data.importSourceId` | string \| null | Id in the system it was imported from, if imported. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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 app instance 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 409: `conflict`: the row changed since `baseUpdatedAt`; the body carries `current`. | 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 JSON config or unknown app. | 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/app-instances/{id} Delete an app instance Delete a configured app instance. Screens and playlists that reference it stop showing it. Auth: Bearer token. Permission: `app-instance.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | App instance id. | | `force` | query | "true" | no | Delete even when it is shared into other places; the shares go with it. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/app-instances/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.deleted` | true | | | `data.sharesRemoved` | integer | Shares removed with it. | 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 app instance 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 409: `content_shared`: it is shared into other places; `shareCount`, `crossSpaceShares`, `contentShares` say where. Repeat with `?force=true` to delete it and those shares. | 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/app-instances/{id}/duplicate Duplicate an app instance Create a copy of a configured app instance, including its configuration and home node, named " copy". Use this to reuse a tuned configuration instead of re-entering it. Requires permission to create app instances at the source instance's node. **Notes.** - The 201 body is the row as written, not re-read from the database, so columns the create does not set (for example `lastSnapshotAt`) are absent rather than null. `GET` returns every column. Auth: Bearer token. Permission: `app-instance.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | App instance id. | ```bash curl -X POST "https://api.brixsignage.com/v1/app-instances/{id}/duplicate" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | App instance id. | | `data.spaceId` | string | Workspace id. | | `data.appKey` | string | The app type: a key from `GET /v1/apps/catalog` (`clock`, `weather`, `rss`, …). | | `data.name` | string | | | `data.config` | any | The app's settings (JSON). The keys depend on `appKey`. | | `data.nodeId` | string \| null | Home location, when one was set or derived. | | `data.lastSnapshotKey` | string \| null | | | `data.lastSnapshotAt` | string \| null | | | `data.importSourceId` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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 app instance 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/app-instances/{id}/restore Restore a deleted app instance Restore an app instance that was deleted within the last 30 days. The shares removed by the delete come back. Playlist items removed by the delete do not come back; add the app to those playlists again. Auth: Bearer token. Permission: `app-instance.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | App instance id. | ```bash curl -X POST "https://api.brixsignage.com/v1/app-instances/{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 such app instance in this workspace, or it was purged. | 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: `not_deleted`: it is not in the recycle bin. | 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/app-instances/{id}/thumbnail Get an app instance thumbnail Retrieve a shared preview image of an app instance. The same image is reused everywhere the app is previewed, so it loads quickly. Add `?fresh=1` to force a new image to be generated after a configuration change. **Notes.** - A cached image is `image/jpeg`. When there is none yet, the answer is a neutral `image/svg+xml` placeholder (header `x-brix-cache: pending`, not cached) while the image is drawn in the background; fetch it again shortly. Auth: Bearer token. Permission: `app-instance.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | App instance id. | | `fresh` | query | "1" | no | Skip the cached image and draw it again. | ```bash curl "https://api.brixsignage.com/v1/app-instances/{id}/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 such app instance 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 503: `browser_unavailable`: images cannot be drawn in this environment. | 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. | ## Approvals Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/approvals List approval requests the caller is allowed to see, newest first. Use `?state=` to narrow the list to requests still awaiting a decision. The response is capped because approval history only grows over time. Newest first. The route needs `screen.view`, but each request is listed only if the caller can also view (or approve) that content kind at its location — e.g. `playlist.view` for a playlist. A `screen.view`-only key gets an empty list. Auth: Bearer token. Permission: `screen.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `state` | query | "pending" \| "approved" \| "rejected" \| "withdrawn" | no | Only requests in this state (`pending` = the inbox). | | `limit` | query | integer | no | At most this many (default and max 500). | ```bash curl "https://api.brixsignage.com/v1/approvals" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of ApprovalRequest | | | `data[].id` | string | Approval request id. | | `data[].contentKind` | string | `playlist`, `schedule`, `creative`, `layout`, `creative-override`, `media`, `app`, … | | `data[].contentId` | string | | | `data[].contentName` | string | | | `data[].thumbnailUrl` | string \| null | | | `data[].nodeId` | string | The content's home location; empty string for the workspace root. | | `data[].nodeName` | string | | | `data[].requestedByName` | string | | | `data[].requestedAt` | string | ISO-8601 timestamp (UTC). | | `data[].note` | string \| null | | | `data[].changes` | array of any | What changed since the last approved version: `{kind, label, detail, thumbnailUrl?}` items for kinds the server diffs; for other kinds, whatever the requester sent. | | `data[].state` | "pending" \| "approved" \| "rejected" \| "withdrawn" | | | `data[].requestedById` | string | User id, or `apikey:` for a key. | | `data[].levels` | array of object | The approval chain, one entry per tier. | | `data[].levels[].nodeId` | string | | | `data[].levels[].nodeName` | string | | | `data[].levels[].approverIds` | array of string | | | `data[].levels[].approverNames` | array of string | | | `data[].currentLevel` | integer | Index into `levels` awaiting a decision while pending. | | `data[].decisions` | array of object | | | `data[].decisions[].level` | integer | | | `data[].decisions[].decidedById` | string | | | `data[].decisions[].decidedByName` | string \| null | | | `data[].decisions[].decidedAt` | string | ISO-8601 timestamp (UTC). | | `data[].decisions[].note` | string \| null | | | `data[].decidedByName` | string \| null | | | `data[].decidedAt` | string \| null | | | `data[].decisionNote` | 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/approvals Request approval for content Open an approval request for a piece of content. The authenticated caller becomes the requester. Decide the request with POST /v1/approvals/:id/decide, or cancel it with POST /v1/approvals/:id/withdraw. **Notes.** - The request goes to the content's own location; a `nodeId` in the body is ignored. The content's review state becomes `pending`. - The requester is the caller (`apikey:` for a key). Auth: Bearer token. Permission: `screen.view`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `contentKind` | string | yes | `playlist`, `schedule`, `creative`, `layout`, `media`, `app`, … | | `contentId` | string | yes | | | `contentName` | string | yes | | | `note` | string | no | | | `thumbnailUrl` | string | no | | | `changes` | array of any | no | Used only for content kinds the server does not compare itself. | ```bash curl -X POST "https://api.brixsignage.com/v1/approvals" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"contentKind":"playlist","contentId":"pl_2b3c4d5e6f7a8b9c","contentName":"Lobby Loop","note":"New spring menu"}' ``` Response 200: Success: A request for this content is already pending: that request is returned and nothing is created. | Field | Type | Description | | --- | --- | --- | | `data` | ApprovalRequest | A request to approve content before it can air. | | `data.id` | string | Approval request id. | | `data.contentKind` | string | `playlist`, `schedule`, `creative`, `layout`, `creative-override`, `media`, `app`, … | | `data.contentId` | string | | | `data.contentName` | string | | | `data.thumbnailUrl` | string \| null | | | `data.nodeId` | string | The content's home location; empty string for the workspace root. | | `data.nodeName` | string | | | `data.requestedByName` | string | | | `data.requestedAt` | string | ISO-8601 timestamp (UTC). | | `data.note` | string \| null | | | `data.changes` | array of any | What changed since the last approved version: `{kind, label, detail, thumbnailUrl?}` items for kinds the server diffs; for other kinds, whatever the requester sent. | | `data.state` | "pending" \| "approved" \| "rejected" \| "withdrawn" | | | `data.requestedById` | string | User id, or `apikey:` for a key. | | `data.levels` | array of object | The approval chain, one entry per tier. | | `data.levels[].nodeId` | string | | | `data.levels[].nodeName` | string | | | `data.levels[].approverIds` | array of string | | | `data.levels[].approverNames` | array of string | | | `data.currentLevel` | integer | Index into `levels` awaiting a decision while pending. | | `data.decisions` | array of object | | | `data.decisions[].level` | integer | | | `data.decisions[].decidedById` | string | | | `data.decisions[].decidedByName` | string \| null | | | `data.decisions[].decidedAt` | string | ISO-8601 timestamp (UTC). | | `data.decisions[].note` | string \| null | | | `data.decidedByName` | string \| null | | | `data.decidedAt` | string \| null | | | `data.decisionNote` | string \| null | | Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ApprovalRequest | A request to approve content before it can air. | | `data.id` | string | Approval request id. | | `data.contentKind` | string | `playlist`, `schedule`, `creative`, `layout`, `creative-override`, `media`, `app`, … | | `data.contentId` | string | | | `data.contentName` | string | | | `data.thumbnailUrl` | string \| null | | | `data.nodeId` | string | The content's home location; empty string for the workspace root. | | `data.nodeName` | string | | | `data.requestedByName` | string | | | `data.requestedAt` | string | ISO-8601 timestamp (UTC). | | `data.note` | string \| null | | | `data.changes` | array of any | What changed since the last approved version: `{kind, label, detail, thumbnailUrl?}` items for kinds the server diffs; for other kinds, whatever the requester sent. | | `data.state` | "pending" \| "approved" \| "rejected" \| "withdrawn" | | | `data.requestedById` | string | User id, or `apikey:` for a key. | | `data.levels` | array of object | The approval chain, one entry per tier. | | `data.levels[].nodeId` | string | | | `data.levels[].nodeName` | string | | | `data.levels[].approverIds` | array of string | | | `data.levels[].approverNames` | array of string | | | `data.currentLevel` | integer | Index into `levels` awaiting a decision while pending. | | `data.decisions` | array of object | | | `data.decisions[].level` | integer | | | `data.decisions[].decidedById` | string | | | `data.decisions[].decidedByName` | string \| null | | | `data.decisions[].decidedAt` | string | ISO-8601 timestamp (UTC). | | `data.decisions[].note` | string \| null | | | `data.decidedByName` | string \| null | | | `data.decidedAt` | string \| null | | | `data.decisionNote` | 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 content is approved, and you do not hold its approve permission (`playlist.approve`, …) at its 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 content, or it is at a location where you do not hold `screen.view`. | 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: `contentKind`, `contentId` or `contentName` 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/approvals/{id} Get an approval request Retrieve one approval request. If the request falls outside what you are allowed to see, this returns 404 rather than 403, so its existence is not revealed. **Notes.** - Besides `screen.view`, the caller must be able to view or approve the content kind at its location (e.g. `playlist.view`); otherwise 404. Auth: Bearer token. Permission: `screen.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Approval request id. | ```bash curl "https://api.brixsignage.com/v1/approvals/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ApprovalRequest | A request to approve content before it can air. | | `data.id` | string | Approval request id. | | `data.contentKind` | string | `playlist`, `schedule`, `creative`, `layout`, `creative-override`, `media`, `app`, … | | `data.contentId` | string | | | `data.contentName` | string | | | `data.thumbnailUrl` | string \| null | | | `data.nodeId` | string | The content's home location; empty string for the workspace root. | | `data.nodeName` | string | | | `data.requestedByName` | string | | | `data.requestedAt` | string | ISO-8601 timestamp (UTC). | | `data.note` | string \| null | | | `data.changes` | array of any | What changed since the last approved version: `{kind, label, detail, thumbnailUrl?}` items for kinds the server diffs; for other kinds, whatever the requester sent. | | `data.state` | "pending" \| "approved" \| "rejected" \| "withdrawn" | | | `data.requestedById` | string | User id, or `apikey:` for a key. | | `data.levels` | array of object | The approval chain, one entry per tier. | | `data.levels[].nodeId` | string | | | `data.levels[].nodeName` | string | | | `data.levels[].approverIds` | array of string | | | `data.levels[].approverNames` | array of string | | | `data.currentLevel` | integer | Index into `levels` awaiting a decision while pending. | | `data.decisions` | array of object | | | `data.decisions[].level` | integer | | | `data.decisions[].decidedById` | string | | | `data.decisions[].decidedByName` | string \| null | | | `data.decisions[].decidedAt` | string | ISO-8601 timestamp (UTC). | | `data.decisions[].note` | string \| null | | | `data.decidedByName` | string \| null | | | `data.decidedAt` | string \| null | | | `data.decisionNote` | 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 404: No such request, or you cannot view (or approve) that kind of content at its 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 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/approvals/{id}/decide Approve or reject content Approve or reject a pending approval request. A reason is required when rejecting. The decision is applied to the underlying content's approval status. Decides the CURRENT level of the chain. Approving the last level lets the content air; approving an earlier level passes it to the next. Besides `screen.view`, the caller must be a named approver for the level or hold the content kind's approve permission (`playlist.approve`, `schedule.approve`, …) at the level's location. Auth: Bearer token. Permission: `screen.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Approval request id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `decision` | "approved" \| "rejected" | yes | | | `decisionNote` | string | no | Required to reject. | ```bash curl -X POST "https://api.brixsignage.com/v1/approvals/{id}/decide" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ApprovalRequest | A request to approve content before it can air. | | `data.id` | string | Approval request id. | | `data.contentKind` | string | `playlist`, `schedule`, `creative`, `layout`, `creative-override`, `media`, `app`, … | | `data.contentId` | string | | | `data.contentName` | string | | | `data.thumbnailUrl` | string \| null | | | `data.nodeId` | string | The content's home location; empty string for the workspace root. | | `data.nodeName` | string | | | `data.requestedByName` | string | | | `data.requestedAt` | string | ISO-8601 timestamp (UTC). | | `data.note` | string \| null | | | `data.changes` | array of any | What changed since the last approved version: `{kind, label, detail, thumbnailUrl?}` items for kinds the server diffs; for other kinds, whatever the requester sent. | | `data.state` | "pending" \| "approved" \| "rejected" \| "withdrawn" | | | `data.requestedById` | string | User id, or `apikey:` for a key. | | `data.levels` | array of object | The approval chain, one entry per tier. | | `data.levels[].nodeId` | string | | | `data.levels[].nodeName` | string | | | `data.levels[].approverIds` | array of string | | | `data.levels[].approverNames` | array of string | | | `data.currentLevel` | integer | Index into `levels` awaiting a decision while pending. | | `data.decisions` | array of object | | | `data.decisions[].level` | integer | | | `data.decisions[].decidedById` | string | | | `data.decisions[].decidedByName` | string \| null | | | `data.decisions[].decidedAt` | string | ISO-8601 timestamp (UTC). | | `data.decisions[].note` | string \| null | | | `data.decidedByName` | string \| null | | | `data.decidedAt` | string \| null | | | `data.decisionNote` | 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: Not eligible to decide this level, or `self_approval` (another approver exists). | 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 request. | 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: `already_decided`. | 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: Bad `decision`, or a rejection without `decisionNote`. | 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/approvals/{id}/withdraw Withdraw an approval request Cancel your own pending approval request. The content it was attached to returns to draft status. **Notes.** - The content's review state goes back to `draft`. Auth: Bearer token. Permission: `screen.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Approval request id. | ```bash curl -X POST "https://api.brixsignage.com/v1/approvals/{id}/withdraw" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ApprovalRequest | A request to approve content before it can air. | | `data.id` | string | Approval request id. | | `data.contentKind` | string | `playlist`, `schedule`, `creative`, `layout`, `creative-override`, `media`, `app`, … | | `data.contentId` | string | | | `data.contentName` | string | | | `data.thumbnailUrl` | string \| null | | | `data.nodeId` | string | The content's home location; empty string for the workspace root. | | `data.nodeName` | string | | | `data.requestedByName` | string | | | `data.requestedAt` | string | ISO-8601 timestamp (UTC). | | `data.note` | string \| null | | | `data.changes` | array of any | What changed since the last approved version: `{kind, label, detail, thumbnailUrl?}` items for kinds the server diffs; for other kinds, whatever the requester sent. | | `data.state` | "pending" \| "approved" \| "rejected" \| "withdrawn" | | | `data.requestedById` | string | User id, or `apikey:` for a key. | | `data.levels` | array of object | The approval chain, one entry per tier. | | `data.levels[].nodeId` | string | | | `data.levels[].nodeName` | string | | | `data.levels[].approverIds` | array of string | | | `data.levels[].approverNames` | array of string | | | `data.currentLevel` | integer | Index into `levels` awaiting a decision while pending. | | `data.decisions` | array of object | | | `data.decisions[].level` | integer | | | `data.decisions[].decidedById` | string | | | `data.decisions[].decidedByName` | string \| null | | | `data.decisions[].decidedAt` | string | ISO-8601 timestamp (UTC). | | `data.decisions[].note` | string \| null | | | `data.decidedByName` | string \| null | | | `data.decidedAt` | string \| null | | | `data.decisionNote` | 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: You are not the requester, and not an approver for the current level. | 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 request. | 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: `already_decided`: the request is no longer pending. | 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. | ## Apps Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/apps/{key}/render Render a catalog app Get the rendered HTML for one app from the app catalog. This is the same output used by the player and by the content management system preview. Auth: none. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `key` | path | string | yes | Identifier for key. | ```bash curl "https://api.brixsignage.com/v1/apps/{key}/render" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/apps/catalog List the apps this workspace can add List the apps available to your workspace, including which ones are currently visible for your account to add. Auth: Bearer token. Permission: `app-instance.view`. ```bash curl "https://api.brixsignage.com/v1/apps/catalog" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.apps` | array of object | | | `data.apps[].key` | string | The `appKey` to create an instance with. | | `data.apps[].status` | "alpha" \| "beta" \| "ga" \| "deprecated" | | | `data.apps[].visible` | boolean | True when this workspace can add it: enabled and GA, or alpha/beta with early access. | 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/apps/counter/{token} Get a live counter value Get the current value of a live counter app, such as Now Serving or Goal Tracker. Access is controlled by the `token` in the URL rather than by an API key; anyone with the token can read the value. Auth: none. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `token` | path | string | yes | Identifier for token. | ```bash curl "https://api.brixsignage.com/v1/apps/counter/{token}" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/apps/counter/{token} Update a live counter value Set or adjust the value of a live counter app. Provide either an absolute value or a relative change, and the resulting value is clamped between 0 and 999999. Access is controlled by the `token` in the URL rather than by an API key. Auth: none. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `token` | path | string | yes | Identifier for token. | ```bash curl -X POST "https://api.brixsignage.com/v1/apps/counter/{token}" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/apps/counter/{token}/control Get the counter control page Get an HTML page with simple controls for adjusting a live counter from a phone. The page submits its changes back to the counter endpoint. Access is controlled by the `token` in the URL. Auth: none. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `token` | path | string | yes | Identifier for token. | ```bash curl "https://api.brixsignage.com/v1/apps/counter/{token}/control" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Audit Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/audit List activity log events Returns the workspace's append-only activity log, newest first, with each event's actor shown by name rather than raw id, whether the actor was a person, an API key, or the system. Page with `limit` and `cursor`; `nextCursor` sits beside `data`. There are no filters: use the export for a time window. A caller limited to some locations sees only the events at those locations. **Notes.** - Newest first. There are no filters: page with `cursor`. - A caller limited to some locations sees only events at those locations, so a page can hold fewer than `limit` events while `nextCursor` is still set. Auth: Bearer token. Permission: `audit-log.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Page size (default 200, max 500). | | `cursor` | query | string | no | `nextCursor` of the previous page. | ```bash curl "https://api.brixsignage.com/v1/audit" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of AuditEvent | | | `data[].id` | string | | | `data[].at` | string | ISO-8601 timestamp (UTC). | | `data[].workspaceId` | string | | | `data[].actor` | string | `user:`, `apikey:`, `system`, or another internal actor. | | `data[].actorKind` | "user" \| "api-key" \| "system" | | | `data[].actorName` | string | The person's or key's name; `System` for the system. | | `data[].actorEmail` | string | The person's email; `n/a` for a key; `system@brix` for the system. | | `data[].action` | string | What happened, e.g. `screen.renamed`. | | `data[].target` | string \| null | The id of the thing acted on. | | `data[].detail` | string \| null | | | `data[].nodeId` | string \| null | The location the event belongs to; null for the workspace. | | `data[].nodeName` | string \| null | | | `data[].ipAddress` | string \| null | | | `data[].userAgent` | string \| null | | | `nextCursor` | string \| null | Pass as `cursor` for the next (older) page; null on the last page. | 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/audit Add activity log event Appends one client event to the workspace's activity log, for example that a help panel was opened. The `action` must be in the `cms.` namespace, so only events of that kind can be added this way. The actor is always the caller, never taken from the request body. The activity log is append-only: events can be added but never updated or deleted. Limited to 120 events a minute. **Notes.** - Needs only `audit-log.view`. The actor is always the caller; `ipAddress` and `userAgent` come from the request. Auth: Bearer token. Permission: `audit-log.view`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `action` | string | yes | Must be in the `cms.` namespace: `cms.` then lower-case letters, digits, `.`, `_` or `-` (up to 122 more characters). | | `target` | string | no | | | `detail` | string | no | | | `nodeId` | string \| null | no | A location you can see. | ```bash curl -X POST "https://api.brixsignage.com/v1/audit" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | AuditEvent | One activity log event, with the actor resolved to a name. | | `data.id` | string | | | `data.at` | string | ISO-8601 timestamp (UTC). | | `data.workspaceId` | string | | | `data.actor` | string | `user:`, `apikey:`, `system`, or another internal actor. | | `data.actorKind` | "user" \| "api-key" \| "system" | | | `data.actorName` | string | The person's or key's name; `System` for the system. | | `data.actorEmail` | string | The person's email; `n/a` for a key; `system@brix` for the system. | | `data.action` | string | What happened, e.g. `screen.renamed`. | | `data.target` | string \| null | The id of the thing acted on. | | `data.detail` | string \| null | | | `data.nodeId` | string \| null | The location the event belongs to; null for the workspace. | | `data.nodeName` | string \| null | | | `data.ipAddress` | string \| null | | | `data.userAgent` | 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 422: `action` is missing or outside the `cms.` namespace, or `nodeId` is not a location you can see. | 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 120 events 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/audit/export Export activity log feed Returns the activity log as a feed suitable for a log collector or SIEM tool. Unlike the regular activity log listing, results are returned oldest first, so a collector can page forward from a saved position without missing or re-reading events. Returns newline-delimited JSON by default; pass `?format=json` for a human-readable array. Use the `from` and `to` parameters for a half-open time window, and the `x-brix-next-cursor` and `x-brix-has-more` response headers to page through results. **Notes.** - Oldest first. The default body is NDJSON: one `AuditEvent` object per line. The next-page cursor is in the `X-Brix-Next-Cursor` header (empty on the last page) and `X-Brix-Has-More` is `1` or `0`. - With `format=json` the body is JSON: `{ data: AuditEvent[], nextCursor: string | null }`. Auth: Bearer token. Permission: `audit-log.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Page size (default 500, max 1000). | | `cursor` | query | string | no | The `X-Brix-Next-Cursor` header (or `nextCursor`) of the previous page. | | `from` | query | string | no | ISO-8601; events at or after this time. | | `to` | query | string | no | ISO-8601; events before this time. | | `format` | query | "json" | no | `json` answers `{ data: AuditEvent[], nextCursor }` instead of NDJSON. | ```bash curl "https://api.brixsignage.com/v1/audit/export" \ -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 422: `from` or `to` is not an ISO-8601 timestamp. | 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/audit/integrity Get activity log integrity chain Returns the tamper-evidence chain for the workspace's activity log: a digest to record for later comparison, and the daily checkpoints behind it. Each complete UTC day of events is combined into one checkpoint linked to the day before. The current, still-open day is deliberately not yet included, since a checkpoint over data still being written would have to be recalculated. Auth: Bearer token. Permission: `audit-log.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Checkpoints to return, newest first (default 90, max 400). | ```bash curl "https://api.brixsignage.com/v1/audit/integrity" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.latestDigest` | string \| null | The value to record. Null before the first day is covered. | | `data.coveredThrough` | string \| null | The end of the last covered day. | | `data.canonicalVersion` | string | The version of the row encoding the hashes use. | | `data.note` | string | | | `data.checkpoints` | array of object | | | `data.checkpoints[].seq` | integer | | | `data.checkpoints[].periodStart` | string | ISO-8601 timestamp (UTC). | | `data.checkpoints[].periodEnd` | string | ISO-8601 timestamp (UTC). | | `data.checkpoints[].rowCount` | integer | | | `data.checkpoints[].firstEventId` | string \| null | | | `data.checkpoints[].lastEventId` | string \| null | | | `data.checkpoints[].merkleRoot` | string | | | `data.checkpoints[].prevDigest` | string | The previous checkpoint's digest, or `genesis`. | | `data.checkpoints[].digest` | 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. | ### POST /v1/audit/integrity/verify Verify activity log integrity Re-reads every event behind the integrity chain and recomputes it, to prove the activity log has not been altered since a digest was recorded. Returns HTTP 200 with `ok: false` when verification fails, rather than an error status, so a monitoring script can distinguish "the log was altered" from "the check itself failed". This is rate-limited because it re-reads the full log. **Notes.** - Answers 200 with `ok: false` when verification fails. Auth: Bearer token. Permission: `audit-log.view`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | Checkpoints to check, newest first (default 90). | ```bash curl -X POST "https://api.brixsignage.com/v1/audit/integrity/verify" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.checkpoints` | array of object | | | `data.checkpoints[].id` | string | | | `data.checkpoints[].seq` | integer | | | `data.checkpoints[].periodStart` | string | ISO-8601 timestamp (UTC). | | `data.checkpoints[].periodEnd` | string | ISO-8601 timestamp (UTC). | | `data.checkpoints[].storedRowCount` | integer | | | `data.checkpoints[].actualRowCount` | integer | | | `data.checkpoints[].recomputedRoot` | string \| null | | | `data.checkpoints[].rootMatches` | boolean | | | `data.checkpoints[].digestMatches` | boolean | | | `data.checkpoints[].chainIntact` | boolean | | | `data.checkpoints[].ok` | boolean | | | `data.ok` | boolean | False when any checkpoint failed: the log was changed. | | `data.latestDigest` | 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 429: More than 10 checks in 5 minutes. | 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. | ## Banners Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/banners List announcement banners List the announcement banners this workspace has created for display inside the content management system, including each banner's title, body, tone, audience, and active state. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/banners" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/banners Create an announcement banner with a `title` and optional `body`, `tone`, `audience`, and `active` state. `tone` defaults to "info" when not specified. Auth: Bearer token. Permission: `integration.create`. ```bash curl -X POST "https://api.brixsignage.com/v1/banners" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/banners/{id} Get an announcement banner Retrieve one announcement banner. Auth: Bearer token. Permission: `integration.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl "https://api.brixsignage.com/v1/banners/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/banners/{id} Update an announcement banner Edit an announcement banner's title, body, tone, audience, or active state. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/banners/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/banners/{id} Delete an announcement banner. Auth: Bearer token. Permission: `integration.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/banners/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/banners/{id}/restore Restore a deleted banner Restore a previously deleted announcement banner so it is active again. Auth: Bearer token. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/banners/{id}/restore" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Billing groups Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/billing-groups List billing groups Returns the workspace's billing groups (who pays for which screens), each with its current count of billable screens. **Notes.** - Sorted by name; not paginated. Unlike the single read, list rows have no `createdAt` / `updatedAt`. Auth: Bearer token. Permission: `billing.view`. ```bash curl "https://api.brixsignage.com/v1/billing-groups" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | string | | | `data[].name` | string | | | `data[].financeContactName` | string \| null | | | `data[].financeContactEmail` | string \| null | Where this group's invoices go. | | `data[].chargebeeCustomerId` | string \| null | The billing customer linked by Brix; null until linked. | | `data[].chargebeeSubscriptionId` | string \| null | The subscription linked by Brix; null until linked. | | `data[].subscriptionStatus` | string \| null | | | `data[].paymentTermsDays` | integer \| null | Net payment terms in days; null = the workspace default. | | `data[].screenCount` | integer | Screens billed to this group. | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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/billing-groups Create billing group Creates a billing group with a name and finance contact. Connecting it to an account with the billing provider is a separate step; one is never created automatically here. **Notes.** - The group bills nothing until Brix links it to a billing account. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `financeContactName` | string | no | | | `financeContactEmail` | string | no | Stored in lower case. | | `paymentTermsDays` | number | no | Net terms in days; rounded. Not range-checked on create. | ```bash curl -X POST "https://api.brixsignage.com/v1/billing-groups" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | BillingGroup | A group of screens billed separately (its own invoice and finance contact). | | `data.id` | string | | | `data.name` | string | | | `data.financeContactName` | string \| null | | | `data.financeContactEmail` | string \| null | Where this group's invoices go. | | `data.chargebeeCustomerId` | string \| null | The billing customer linked by Brix; null until linked. | | `data.chargebeeSubscriptionId` | string \| null | The subscription linked by Brix; null until linked. | | `data.subscriptionStatus` | string \| null | | | `data.paymentTermsDays` | integer \| null | Net payment terms in days; null = the workspace default. | | `data.screenCount` | integer | Screens billed to this group. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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` 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/billing-groups/{id} Get billing group Returns one billing group by id. Auth: Bearer token. Permission: `billing.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Billing group id. | ```bash curl "https://api.brixsignage.com/v1/billing-groups/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | BillingGroup | A group of screens billed separately (its own invoice and finance contact). | | `data.id` | string | | | `data.name` | string | | | `data.financeContactName` | string \| null | | | `data.financeContactEmail` | string \| null | Where this group's invoices go. | | `data.chargebeeCustomerId` | string \| null | The billing customer linked by Brix; null until linked. | | `data.chargebeeSubscriptionId` | string \| null | The subscription linked by Brix; null until linked. | | `data.subscriptionStatus` | string \| null | | | `data.paymentTermsDays` | integer \| null | Net payment terms in days; null = the workspace default. | | `data.screenCount` | integer | Screens billed to this group. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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 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 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/billing-groups/{id} Update billing group Updates a billing group's name, finance contact, or invoice terms. **Notes.** - `paymentTermsDays` is range-checked here (1–365) but not on create. Auth: Bearer token. Permission: `billing.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Billing group id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `financeContactName` | string \| null | no | | | `financeContactEmail` | string \| null | no | | | `paymentTermsDays` | number \| null | no | 1–365, or null to use the workspace default. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/billing-groups/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | BillingGroup | A group of screens billed separately (its own invoice and finance contact). | | `data.id` | string | | | `data.name` | string | | | `data.financeContactName` | string \| null | | | `data.financeContactEmail` | string \| null | Where this group's invoices go. | | `data.chargebeeCustomerId` | string \| null | The billing customer linked by Brix; null until linked. | | `data.chargebeeSubscriptionId` | string \| null | The subscription linked by Brix; null until linked. | | `data.subscriptionStatus` | string \| null | | | `data.paymentTermsDays` | integer \| null | Net payment terms in days; null = the workspace default. | | `data.screenCount` | integer | Screens billed to this group. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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 group. | 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 `name`, or `paymentTermsDays` outside 1–365. | 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/billing-groups/{id} Delete billing group Soft-deletes a billing group. Returns 409 if any screens are still billed to it; move or remove those screens first. Auth: Bearer token. Permission: `billing.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Billing group id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/billing-groups/{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: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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 group. | 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: `group_in_use`: screens still bill to it; move them first (`PATCH /v1/screens/:id` `billingGroupId`). | 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/billing-groups/{id}/screens Assign screens to billing group Assigns a batch of screens to a billing group. This updates the destination group, every group the screens are moving from, and the workspace subscription, so screen counts and billing stay in sync. Auth: Bearer token. Permission: `billing.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Billing group id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `screenIds` | array of string | yes | Up to 500 are read; more are ignored. Ids that are not live screens are skipped. | ```bash curl -X POST "https://api.brixsignage.com/v1/billing-groups/{id}/screens" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.assigned` | integer | Screens moved into the group. | | `data.skipped` | integer | Ids not moved (unknown, or already in the group). | | `data.screenCount` | 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: You lack `billing.edit` at one of the screens' locations (the response names them in `screenIds`); nothing changes. Also: You do not hold the permission for the whole workspace (a grant at one location is not enough). | 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 group, or none of the ids is a live screen. | 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: `screenIds` missing or 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. | ## Casts Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/casts List casts, which are timed content takeovers, newest first. Use `?status=active` to filter to casts currently on air, and `?limit` (up to 500, default 200) to bound the number of results. Cast history is kept indefinitely. Auth: Bearer token. Permission: `screen.cast`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `status` | query | "active" \| "cleared" \| "expired" | no | Only casts in this state. | | `limit` | query | integer | no | At most this many, newest first (default 200, max 500). | ```bash curl "https://api.brixsignage.com/v1/casts" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of Override | | | `data[].id` | string | Override (cast_… or emg_…) id. | | `data[].spaceId` | string | Workspace id. | | `data[].kind` | "emergency" \| "cast" | | | `data[].status` | "active" \| "cleared" \| "expired" | | | `data[].severity` | "info" \| "warning" \| "critical" | Always `info` for a cast. | | `data[].headline` | string | An emergency's message; for a cast, the content's name (or "Cast"). | | `data[].body` | string \| null | | | `data[].contentKind` | "media" \| "creative" \| "playlist" \| "app" \| "schedule" \| null | | | `data[].contentId` | string \| null | | | `data[].scopeKind` | "all" \| "node" \| "screens" | | | `data[].scopeNodeId` | string \| null | The location, when `scopeKind` is `node`. | | `data[].nodeId` | string \| null | Location the override is attributed to (null = workspace root). | | `data[].screenCount` | integer | Screens targeted, snapshotted when it started. | | `data[].triggeredBy` | string | User id, or `system`. | | `data[].triggeredAt` | string | ISO-8601 timestamp (UTC). | | `data[].expiresAt` | string \| null | Auto-clear time; null = until cleared. | | `data[].clearedBy` | string \| null | | | `data[].clearedAt` | string \| null | | | `data[].triggeredByName` | string | Display name of `triggeredBy`. | | `data[].clearedByName` | string \| null | | | `data[].scopeName` | string \| null | Location name, `All screens`, or null for a screen list. | | `data[].confirmedCount` | integer \| null | Active emergencies only: screens confirmed showing it now. Null otherwise. | ```json { "data": [ { "id": "cast_9f2c4a1b7d3e5f60", "spaceId": "space_1a2b3c4d5e6f7a8b", "kind": "cast", "status": "active", "severity": "info", "headline": "Friday lunch special", "body": null, "contentKind": "media", "contentId": "med_0c1d2e3f4a5b6c7d", "scopeKind": "screens", "scopeNodeId": null, "nodeId": null, "screenCount": 2, "triggeredBy": "usr_5e6f7a8b9c0d1e2f", "triggeredAt": "2026-09-28T11:30:00.000Z", "expiresAt": "2026-09-28T13:30:00.000Z", "clearedBy": null, "clearedAt": null, "triggeredByName": "Sam Rivera", "clearedByName": null, "scopeName": null, "confirmedCount": 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/casts Cast content to screens Put content on air across a set of screens for a period of time, after which the screens automatically revert to their normal content. Send `contentKind` (media, playlist, app, creative, or schedule), `contentId`, `scopeKind`, and optionally `scopeNodeId`, `screenIds`, and `expiresAt`. `scopeKind` accepts all, node, or screens and defaults to all: a request sent with no scope casts to every screen the caller can reach. An active emergency override takes priority over a cast, and the cast resumes automatically once the emergency is cleared. Puts one piece of content on the chosen screens now, above their schedule, until it expires or is cleared. An emergency still pre-empts a cast. Auth: Bearer token. Permission: `screen.cast`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `contentKind` | "media" \| "playlist" \| "app" \| "creative" \| "schedule" | yes | What kind of thing `contentId` names. | | `contentId` | string | yes | The media, playlist, app instance, creative or schedule to put on air. | | `contentName` | string | no | Label for the cast (shown in the console). Defaults to "Cast". | | `scopeKind` | "all" \| "node" \| "screens" | no | Which screens: every screen you can reach (`all`), one location's subtree (`node`), or a list (`screens`). **Defaults to `all`: omit it and the cast goes to EVERY screen you can reach.** | | `scopeNodeId` | string | no | Location id, with `scopeKind: node`. | | `screenIds` | array of string | no | Screen ids, with `scopeKind: screens`. | | `expiresAt` | string \| number \| null | no | When the cast ends by itself: ISO-8601 or epoch milliseconds. Omit or null to run until cleared. | ```bash curl -X POST "https://api.brixsignage.com/v1/casts" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"contentKind":"media","contentId":"med_0c1d2e3f4a5b6c7d","contentName":"Friday lunch special","scopeKind":"screens","screenIds":["scr_1a2b3c4d5e6f7a8b","scr_2b3c4d5e6f7a8b9c"],"expiresAt":"2026-09-28T13:30:00.000Z"}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Override | A cast or an emergency override. | | `data.id` | string | Override (cast_… or emg_…) id. | | `data.spaceId` | string | Workspace id. | | `data.kind` | "emergency" \| "cast" | | | `data.status` | "active" \| "cleared" \| "expired" | | | `data.severity` | "info" \| "warning" \| "critical" | Always `info` for a cast. | | `data.headline` | string | An emergency's message; for a cast, the content's name (or "Cast"). | | `data.body` | string \| null | | | `data.contentKind` | "media" \| "creative" \| "playlist" \| "app" \| "schedule" \| null | | | `data.contentId` | string \| null | | | `data.scopeKind` | "all" \| "node" \| "screens" | | | `data.scopeNodeId` | string \| null | The location, when `scopeKind` is `node`. | | `data.nodeId` | string \| null | Location the override is attributed to (null = workspace root). | | `data.screenCount` | integer | Screens targeted, snapshotted when it started. | | `data.triggeredBy` | string | User id, or `system`. | | `data.triggeredAt` | string | ISO-8601 timestamp (UTC). | | `data.expiresAt` | string \| null | Auto-clear time; null = until cleared. | | `data.clearedBy` | string \| null | | | `data.clearedAt` | string \| null | | | `data.triggeredByName` | string | Display name of `triggeredBy`. | | `data.clearedByName` | string \| null | | | `data.scopeName` | string \| null | Location name, `All screens`, or null for a screen list. | | `data.confirmedCount` | integer \| null | Active emergencies only: screens confirmed showing it now. Null otherwise. | ```json { "data": { "id": "cast_9f2c4a1b7d3e5f60", "spaceId": "space_1a2b3c4d5e6f7a8b", "kind": "cast", "status": "active", "severity": "info", "headline": "Friday lunch special", "body": null, "contentKind": "media", "contentId": "med_0c1d2e3f4a5b6c7d", "scopeKind": "screens", "scopeNodeId": null, "nodeId": null, "screenCount": 2, "triggeredBy": "usr_5e6f7a8b9c0d1e2f", "triggeredAt": "2026-09-28T11:30:00.000Z", "expiresAt": "2026-09-28T13:30:00.000Z", "clearedBy": null, "clearedAt": null, "triggeredByName": "Sam Rivera", "clearedByName": null, "scopeName": null, "confirmedCount": 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: `not_shared`: the content is not shared to one or more target locations. | 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 content does not exist 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 body, the scope matched no screens you can reach (`no_screens`), or the content 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/casts/{id}/clear End a cast immediately and return its screens to their scheduled content. Ends an active cast; its screens return to their scheduled content immediately. Auth: Bearer token. Permission: `screen.cast`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Cast id. | ```bash curl -X POST "https://api.brixsignage.com/v1/casts/{id}/clear" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Override | A cast or an emergency override. | | `data.id` | string | Override (cast_… or emg_…) id. | | `data.spaceId` | string | Workspace id. | | `data.kind` | "emergency" \| "cast" | | | `data.status` | "active" \| "cleared" \| "expired" | | | `data.severity` | "info" \| "warning" \| "critical" | Always `info` for a cast. | | `data.headline` | string | An emergency's message; for a cast, the content's name (or "Cast"). | | `data.body` | string \| null | | | `data.contentKind` | "media" \| "creative" \| "playlist" \| "app" \| "schedule" \| null | | | `data.contentId` | string \| null | | | `data.scopeKind` | "all" \| "node" \| "screens" | | | `data.scopeNodeId` | string \| null | The location, when `scopeKind` is `node`. | | `data.nodeId` | string \| null | Location the override is attributed to (null = workspace root). | | `data.screenCount` | integer | Screens targeted, snapshotted when it started. | | `data.triggeredBy` | string | User id, or `system`. | | `data.triggeredAt` | string | ISO-8601 timestamp (UTC). | | `data.expiresAt` | string \| null | Auto-clear time; null = until cleared. | | `data.clearedBy` | string \| null | | | `data.clearedAt` | string \| null | | | `data.triggeredByName` | string | Display name of `triggeredBy`. | | `data.clearedByName` | string \| null | | | `data.scopeName` | string \| null | Location name, `All screens`, or null for a screen list. | | `data.confirmedCount` | integer \| null | Active emergencies only: screens confirmed showing it now. Null otherwise. | 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 cast 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 409: `already_inactive`: the cast is already cleared or 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 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. | ## Celebration entries Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/celebration-entries List celebration entries Return the roster of birthdays, anniversaries, and other occasions that the Celebrations app displays. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/celebration-entries" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/celebration-entries Create a celebration entry Add an entry to the celebration roster. Requires name; also accepts occasionType, occasionMonth, occasionDay, occasionYear, photoUrl, department, title, location, customMessage, sourceId, and externalId, all optional. Auth: Bearer token. Permission: `integration.create`. ```bash curl -X POST "https://api.brixsignage.com/v1/celebration-entries" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/celebration-entries/{id} Get a celebration entry Return one entry from the celebration roster. Auth: Bearer token. Permission: `integration.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl "https://api.brixsignage.com/v1/celebration-entries/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/celebration-entries/{id} Update a celebration entry Edit one entry in the celebration roster. Accepts any of the fields used when creating an entry. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/celebration-entries/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/celebration-entries/{id} Delete a celebration entry Remove an entry from the celebration roster. The entry moves to the recycle bin and can be brought back with the restore operation. Auth: Bearer token. Permission: `integration.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/celebration-entries/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/celebration-entries/{id}/restore Restore a celebration entry Bring back a deleted celebration entry so it appears again in the celebration app's roster. Auth: Bearer token. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/celebration-entries/{id}/restore" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Connected apps Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/connected-apps List connected apps Returns the AI assistants and other MCP clients that people in this workspace connected with OAuth: the client, who connected it, its permissions, the location it is pinned to, and when it was last used. You see a connection only where you hold the permission to view API keys at its location. **Notes.** - Newest first. Only connections whose location the caller can see with `api-key.view` are listed. Auth: Bearer token. Permission: `api-key.view`. ```bash curl "https://api.brixsignage.com/v1/connected-apps" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | string | Connection id. | | `data[].clientId` | string | | | `data[].clientName` | string | The app's name, as it registered. | | `data[].redirectHost` | string \| null | The host the app signs in through. | | `data[].connectedBy` | object \| null | The person who approved it; null if they no longer exist. | | `data[].permissions` | "all" \| array of string | | | `data[].nodeId` | string \| null | The location the connection is limited to; null for the whole workspace. | | `data[].lastUsedAt` | string \| null | | | `data[].connectedAt` | 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. | ### DELETE /v1/connected-apps/{id} Disconnect connected app Disconnects an app that was connected with OAuth. Its access stops immediately. You must hold the permission to delete API keys at the location the connection is pinned to. **Notes.** - Answers the same for a connection that was already revoked. Auth: Bearer token. Permission: `api-key.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Connection id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/connected-apps/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.revoked` | 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: You do not hold `api-key.delete` where the connection is limited to. | 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 connection 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. | ## Connectors Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/connectors List connector types List the data-source connector types: the `type` to use when you create a data source, its label and category, and whether it is available yet. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/connectors" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of Connector | | | `data[].type` | string | The value for a data source's `type` (`http-json`, `csv`, `google-sheets`, …). | | `data[].label` | string | | | `data[].category` | string | Group the connector is listed under (`generic`, `celebrations`, …). | | `data[].available` | boolean | False for a connector that is listed as coming soon and cannot be used yet. | 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/connectors/probe Test a connector URL Test whether the platform can reach a given endpoint, behind the Test connection action used before creating a data source. The request is made from the server rather than the browser. Requests to private or internal network addresses are blocked. Sends a GET to the URL from the server and returns the status and the start of the body. Rate limited to 20 calls a minute. **Notes.** - A URL that cannot be reached is not an HTTP error: the answer is 200 with `ok: false`, `status: 0` and `error`. - A blocked private or internal address answers 400 with the `{ data: { ok: false, status: 0, error } }` body, not the usual error body. Auth: Bearer token. Permission: `integration.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | An http(s) URL on the public internet. | | `token` | string | no | Sent as `Authorization: Bearer `. Not stored. | ```bash curl -X POST "https://api.brixsignage.com/v1/connectors/probe" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | Response 400: `bad_request`: the URL is not http(s). A private or internal address also answers 400, with the `data` body (`ok: false`, `status: 0`, `error`) instead of an error body. | 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 429: More than 20 calls in 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. | ## Creative overrides Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/creative-overrides/{creativeId} Get a creative's overrides at a location Return the edits a location has made to a creative that was shared with it, the merged result of applying those edits, and any edits that have stopped applying. Auth: Bearer token. Permission: `creative.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `creativeId` | path | string | yes | Creative id: one of yours, or one shared into this workspace. | | `node` | query | string | no | Location id. Default: the workspace root. | ```bash curl "https://api.brixsignage.com/v1/creative-overrides/{creativeId}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.creativeId` | string | | | `data.nodeId` | string | The location the edits apply at. | | `data.values` | object | The location's edits: box id → the box fields it changes (for example `{ "price": { "text": "13.00" } }`). | | `data.overrideId` | string \| null | Id of the stored edits (what an approval request is opened against); null before the first save. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state of the edits (not of the design). | | `data.requiresApproval` | boolean | The location requires approval before the edits air. | | `data.skipsApproval` | boolean | The author waived that review for this design. | | `data.creative` | OverrideCreative | A creative's design: the boxes and scenes with their settings. | | `data.creative.id` | string | | | `data.creative.nodeId` | string \| null | | | `data.creative.name` | string | | | `data.creative.backgroundUrl` | string \| null | | | `data.creative.boxes` | array of object | | | `data.creative.scenes` | array of object | | | `data.creative.dataSourceId` | string \| null | | | `data.creative.stage` | string | Absent when not set. | | `data.creative.shareLockDefault` | any \| null | | | `data.creative.shareEditsSkipApproval` | boolean | | | `data.creative.stageWidth` | integer | | | `data.creative.stageHeight` | integer | | | `data.creative.touchEnabled` | boolean | | | `data.creative.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data.creative.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.orphanedBoxIds` | array of string | Edited box ids the design no longer has. | | `data.sharedFrom` | string \| null | The workspace that owns the design, when it was shared in from another workspace. | | `data.base` | OverrideCreative | A creative's design: the boxes and scenes with their settings. | | `data.base.id` | string | | | `data.base.nodeId` | string \| null | | | `data.base.name` | string | | | `data.base.backgroundUrl` | string \| null | | | `data.base.boxes` | array of object | | | `data.base.scenes` | array of object | | | `data.base.dataSourceId` | string \| null | | | `data.base.stage` | string | Absent when not set. | | `data.base.shareLockDefault` | any \| null | | | `data.base.shareEditsSkipApproval` | boolean | | | `data.base.stageWidth` | integer | | | `data.base.stageHeight` | integer | | | `data.base.touchEnabled` | boolean | | | `data.base.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data.base.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.rejectedFields` | array of string | `boxId.field` edits that no longer apply because the author locked the field. | | `data.updatedAt` | string \| null | When the edits were last saved; null before the first save. | 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: Unknown location, no such creative, or the creative is not usable at that 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 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. | ### PUT /v1/creative-overrides/{creativeId} Replace a creative's overrides at a location Replace a location's edits to a shared creative. If the creative's author has locked a field against editing, the request is rejected with a 422 error for that field. **Notes.** - The response has no `sharedFrom`, `base` or `rejectedFields` (GET has them). A save returns approved edits to `draft`. Auth: Bearer token. Permission: `creative.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `creativeId` | path | string | yes | Creative id: one of yours, or one shared into this workspace. | | `node` | query | string | no | Location id. Default: the workspace root. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `values` | object | yes | Every edit to keep, at most 300 boxes. Edits left out are removed. | ```bash curl -X PUT "https://api.brixsignage.com/v1/creative-overrides/{creativeId}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.creativeId` | string | | | `data.nodeId` | string | The location the edits apply at. | | `data.values` | object | The location's edits: box id → the box fields it changes (for example `{ "price": { "text": "13.00" } }`). | | `data.overrideId` | string \| null | Id of the stored edits (what an approval request is opened against); null before the first save. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state of the edits (not of the design). | | `data.requiresApproval` | boolean | The location requires approval before the edits air. | | `data.skipsApproval` | boolean | The author waived that review for this design. | | `data.creative` | OverrideCreative | A creative's design: the boxes and scenes with their settings. | | `data.creative.id` | string | | | `data.creative.nodeId` | string \| null | | | `data.creative.name` | string | | | `data.creative.backgroundUrl` | string \| null | | | `data.creative.boxes` | array of object | | | `data.creative.scenes` | array of object | | | `data.creative.dataSourceId` | string \| null | | | `data.creative.stage` | string | Absent when not set. | | `data.creative.shareLockDefault` | any \| null | | | `data.creative.shareEditsSkipApproval` | boolean | | | `data.creative.stageWidth` | integer | | | `data.creative.stageHeight` | integer | | | `data.creative.touchEnabled` | boolean | | | `data.creative.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data.creative.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.orphanedBoxIds` | array of string | Edited box ids the design no longer has. | | `data.updatedAt` | 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: You lack creative.edit at that 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: Unknown location, no such creative, or the creative is not usable at that 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 422: `validation_error` (bad `values`, more than 300 boxes, unknown box ids in `unknownBoxes`) or `locked` (fields the author locked, in `violations`). | 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. | ## Creatives Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/creatives List creatives Return the workspace's canvas creatives, including each one's name, stage size, location, and sharing state. The full layout of shapes is not included; use the get-creative operation to retrieve that. Auth: Bearer token. Permission: `creative.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Page size. Omit to get every row; pass it to page by `cursor`. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | | `count` | query | "1" | no | With `limit`: also return `total`, the number of matching rows. | | `usableAt` | query | string | no | Location id: only rows usable at that location (homed there, at the workspace root, or shared to it). | ```bash curl "https://api.brixsignage.com/v1/creatives" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of Creative | | | `data[].id` | string | Creative id. | | `data[].spaceId` | string | | | `data[].name` | string | | | `data[].backgroundUrl` | string \| null | | | `data[].boxes` | array of object | The design's boxes (text, image, data-bound fields…), in paint order. | | `data[].dataSourceId` | string \| null | The data source the boxes bind to, if any. | | `data[].stage` | string \| null | Stage preset name. | | `data[].stageWidth` | integer \| null | | | `data[].stageHeight` | integer \| null | | | `data[].scenes` | array of object | Scenes, for a multi-scene design; often empty. | | `data[].touchEnabled` | boolean \| null | | | `data[].nodeId` | string \| null | | | `data[].approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data[].approvedSnapshot` | string \| null | JSON TEXT of the last approved version (not parsed). | | `data[].sourceSignage` | any \| null | The template it was made from (JSON), if any. | | `data[].look` | any \| null | Accent / Brand Kit / light-dark settings (JSON), if set. | | `data[].masterId` | string \| null | The master template id, for a design made from one. | | `data[].shareLockDefault` | any \| null | Which boxes recipients may edit when shared (JSON), if set. | | `data[].shareEditsSkipApproval` | boolean | | | `data[].recalledAt` | string \| null | | | `data[].recalledBy` | string \| null | | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | | `nextCursor` | string \| null | Present when `?limit` was passed. Send it back as `?cursor=` for the next page; null on the last page. | | `total` | integer | Total matching rows, when the route computes it. | 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/creatives Create a creative Create a canvas creative. Accepts name and optional boxes, scenes, stageWidth, stageHeight, backgroundUrl, dataSourceId, touchEnabled, nodeId, and masterId. Safe to retry with the same Idempotency-Key header without creating duplicates. **Notes.** - The 201 body is the row as written, not re-read from the database, so columns the create does not set (for example `lastSnapshotAt`) are absent rather than null. `GET` returns every column. - Send an `Idempotency-Key` header to make a retry safe. Auth: Bearer token. Permission: `creative.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `backgroundUrl` | string \| null | no | An unsafe URL scheme is stored as null. | | `boxes` | array of object | no | The whole box list; each box is checked and filled out with defaults. Default `[]`. | | `dataSourceId` | string \| null | no | A data source in this workspace. | | `stage` | string \| null | no | | | `stageWidth` | integer \| null | no | | | `stageHeight` | integer \| null | no | | | `scenes` | array of object | no | Default `[]`. | | `touchEnabled` | boolean \| null | no | | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | | `sourceSignage` | any | no | | | `masterId` | string \| null | no | The master template it was made from (`GET /v1/signage-master-templates`). | | `shareLockDefault` | any | no | Which box fields recipients may edit when it is shared (JSON). | | `shareEditsSkipApproval` | boolean | no | Recipients' edits air without review. Needs `creative.approve`. | | `look` | object \| null | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/creatives" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Creative id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.backgroundUrl` | string \| null | | | `data.boxes` | array of object | The design's boxes (text, image, data-bound fields…), in paint order. | | `data.dataSourceId` | string \| null | The data source the boxes bind to, if any. | | `data.stage` | string \| null | Stage preset name. | | `data.stageWidth` | integer \| null | | | `data.stageHeight` | integer \| null | | | `data.scenes` | array of object | Scenes, for a multi-scene design; often empty. | | `data.touchEnabled` | boolean \| null | | | `data.nodeId` | string \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data.approvedSnapshot` | string \| null | JSON TEXT of the last approved version (not parsed). | | `data.sourceSignage` | any \| null | The template it was made from (JSON), if any. | | `data.look` | any \| null | Accent / Brand Kit / light-dark settings (JSON), if set. | | `data.masterId` | string \| null | The master template id, for a design made from one. | | `data.shareLockDefault` | any \| null | Which boxes recipients may edit when shared (JSON), if set. | | `data.shareEditsSkipApproval` | boolean | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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: Setting `shareEditsSkipApproval` without `creative.approve`. | 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: `dataSourceId` names no data source 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: Missing name, invalid JSON, malformed boxes, scenes or look, or a location outside 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/creatives/{id} Get a creative Return one creative in full, including its boxes, scenes, background, any linked data source, and stage dimensions. Auth: Bearer token. Permission: `creative.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Creative id. | ```bash curl "https://api.brixsignage.com/v1/creatives/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Creative id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.backgroundUrl` | string \| null | | | `data.boxes` | array of object | The design's boxes (text, image, data-bound fields…), in paint order. | | `data.dataSourceId` | string \| null | The data source the boxes bind to, if any. | | `data.stage` | string \| null | Stage preset name. | | `data.stageWidth` | integer \| null | | | `data.stageHeight` | integer \| null | | | `data.scenes` | array of object | Scenes, for a multi-scene design; often empty. | | `data.touchEnabled` | boolean \| null | | | `data.nodeId` | string \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data.approvedSnapshot` | string \| null | JSON TEXT of the last approved version (not parsed). | | `data.sourceSignage` | any \| null | The template it was made from (JSON), if any. | | `data.look` | any \| null | Accent / Brand Kit / light-dark settings (JSON), if set. | | `data.masterId` | string \| null | The master template id, for a design made from one. | | `data.shareLockDefault` | any \| null | Which boxes recipients may edit when shared (JSON), if set. | | `data.shareEditsSkipApproval` | boolean | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | | `data.requiresApproval` | boolean | The home location requires approval before content airs. | | `data.canEditBase` | boolean | The caller may edit the design itself (not only its unlocked boxes). | | `data.canWaiveApproval` | boolean | The caller holds `creative.approve` at its home location. | 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 creative 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/creatives/{id} Update a creative Edit a creative. The boxes and scenes fields each replace the entire existing list, so retrieve the creative first and send back the complete array with changes included. If the location requires approval before content goes live, updating the creative resets that approval. **Notes.** - The response is the stored row: it has no `requiresApproval`, `canEditBase` or `canWaiveApproval` (GET has them). Auth: Bearer token. Permission: `creative.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Creative id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `backgroundUrl` | string \| null | no | An unsafe URL scheme is stored as null. | | `boxes` | array of object | no | The whole box list; each box is checked and filled out with defaults. Default `[]`. | | `dataSourceId` | string \| null | no | A data source in this workspace. | | `stage` | string \| null | no | | | `stageWidth` | integer \| null | no | | | `stageHeight` | integer \| null | no | | | `scenes` | array of object | no | Default `[]`. | | `touchEnabled` | boolean \| null | no | | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | | `sourceSignage` | any | no | | | `masterId` | string \| null | no | The master template it was made from (`GET /v1/signage-master-templates`). | | `shareLockDefault` | any | no | Which box fields recipients may edit when it is shared (JSON). | | `shareEditsSkipApproval` | boolean | no | Recipients' edits air without review. Needs `creative.approve`. | | `look` | object \| null | no | | | `baseUpdatedAt` | string | no | Optimistic concurrency: the `updatedAt` you read. A stale value is refused with 409 `conflict` and the `current` row. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/creatives/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Creative | A canvas design (data-bound boxes on a stage). | | `data.id` | string | Creative id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.backgroundUrl` | string \| null | | | `data.boxes` | array of object | The design's boxes (text, image, data-bound fields…), in paint order. | | `data.dataSourceId` | string \| null | The data source the boxes bind to, if any. | | `data.stage` | string \| null | Stage preset name. | | `data.stageWidth` | integer \| null | | | `data.stageHeight` | integer \| null | | | `data.scenes` | array of object | Scenes, for a multi-scene design; often empty. | | `data.touchEnabled` | boolean \| null | | | `data.nodeId` | string \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data.approvedSnapshot` | string \| null | JSON TEXT of the last approved version (not parsed). | | `data.sourceSignage` | any \| null | The template it was made from (JSON), if any. | | `data.look` | any \| null | Accent / Brand Kit / light-dark settings (JSON), if set. | | `data.masterId` | string \| null | The master template id, for a design made from one. | | `data.shareLockDefault` | any \| null | Which boxes recipients may edit when shared (JSON), if set. | | `data.shareEditsSkipApproval` | boolean | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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: Moving it to a location where you lack creative.edit, or changing `shareEditsSkipApproval` without `creative.approve`. | 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 creative (or `dataSourceId`) 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 409: `conflict`: the row changed since `baseUpdatedAt`; the body carries `current`. | 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 JSON or malformed boxes, scenes or look. | 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/creatives/{id} Delete a creative Move a creative to the recycle bin. If the creative is shared into other locations, the request fails with a 409 error unless the deletion is explicitly confirmed, since deleting a shared creative removes it everywhere it is shared. Auth: Bearer token. Permission: `creative.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Creative id. | | `force` | query | "true" | no | Delete even when it is shared into other places; the shares go with it. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/creatives/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.deleted` | true | | | `data.sharesRemoved` | integer | Shares removed with it. | 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 creative 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 409: `content_shared`: it is shared into other places; `shareCount`, `crossSpaceShares`, `contentShares` say where. Repeat with `?force=true` to delete it and those shares. | 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/creatives/{id}/restore Restore a creative Bring back a deleted creative so it returns to the library. The shares removed by the delete come back with it. Auth: Bearer token. Permission: `creative.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Creative id. | ```bash curl -X POST "https://api.brixsignage.com/v1/creatives/{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 such creative in this workspace, or it was purged. | 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: `not_deleted`: the creative is not in the recycle bin. | 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/creatives/{id}/thumbnail Get a creative's thumbnail Return an image of the creative, showing its shapes and pictures along with a representative preview of each content slot, such as a file's poster image, a playlist's first item, or an app's most recent snapshot. Text boxes are not drawn. The image is cached; add ?fresh=1 to bypass the cache after making an edit. Auth: Bearer token. Permission: `creative.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Creative id. | | `fresh` | query | "1" | no | Skip the cached image and draw it again. | ```bash curl "https://api.brixsignage.com/v1/creatives/{id}/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 such creative 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. | ## Cross space shares Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/cross-space-shares List cross-space shares List content this space shares into its child spaces, and content shared with it in turn. A space receiving a share can read it but cannot change or remove it. Auth: Bearer token. Permission: `playlist.view`. ```bash curl "https://api.brixsignage.com/v1/cross-space-shares" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/cross-space-shares Share content into a child space Share a playlist, layout, schedule, or creative from this space into a child space. The shared content appears in the child space alongside its own content. Only the space that owns the content can create the share. Requires view permission on shares plus the appropriate permission on the content itself. Auth: Bearer token. ```bash curl -X POST "https://api.brixsignage.com/v1/cross-space-shares" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/cross-space-shares/{id} Remove a cross-space share Withdraw a share. Only the space that created the share can remove it; the receiving space cannot. Auth: Bearer token. Permission: `playlist.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/cross-space-shares/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Data sources Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/data-sources List data sources List the data feeds, such as spreadsheets, APIs, and other connectors, that creatives and apps can connect to. Credentials are encrypted at rest and are never returned. **Notes.** - Not paginated: every data source you can see comes back in one response (no `?limit`/`?cursor`, unlike the app instance, creative and layout lists). Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/data-sources" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of DataSource | | | `data[].id` | string | Data source id. | | `data[].spaceId` | string | | | `data[].name` | string | | | `data[].type` | string | Connector type (`csv`, `http-json`, `rest`, `google-sheets`, `ical`, …). See `GET /v1/connectors`. | | `data[].config` | any | Connector settings with every credential-shaped value (keys, tokens, secrets, passwords) replaced by `••••••••`. Send the mask back unchanged to keep the stored value. | | `data[].cachedData` | any \| null | The last successful fetch (JSON), or null before the first sync. | | `data[].connected` | boolean | The last sync succeeded. | | `data[].lastSyncedAt` | string \| null | | | `data[].syncError` | string \| null | Why the last sync failed; null when it succeeded. | | `data[].nodeId` | string \| null | | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | 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/data-sources Create a data source Connect a data feed with a `name`, a connector `type`, a `config`, and an optional `nodeId`. Any credentials included in `config` are encrypted at rest. Test the configuration first with POST /v1/data-sources/test before creating it. **Notes.** - Gated by `integration.edit`, not `integration.create`. Auth: Bearer token. Permission: `integration.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `type` | string | yes | Connector type from `GET /v1/connectors`. The value is not checked here: an unknown type fails at the first sync. | | `config` | object | no | Connector settings (JSON). The keys depend on `type`. Credentials are encrypted at rest and every later read shows them as `••••••••`. | | `nodeId` | string \| null | no | Home location; null or absent = workspace root. The key needs the permission there. | ```bash curl -X POST "https://api.brixsignage.com/v1/data-sources" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | DataSource | A connected data feed. Credentials are never returned. | | `data.id` | string | Data source id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.type` | string | Connector type (`csv`, `http-json`, `rest`, `google-sheets`, `ical`, …). See `GET /v1/connectors`. | | `data.config` | any | Connector settings with every credential-shaped value (keys, tokens, secrets, passwords) replaced by `••••••••`. Send the mask back unchanged to keep the stored value. | | `data.cachedData` | any \| null | The last successful fetch (JSON), or null before the first sync. | | `data.connected` | boolean | The last sync succeeded. | | `data.lastSyncedAt` | string \| null | | | `data.syncError` | string \| null | Why the last sync failed; null when it succeeded. | | `data.nodeId` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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 422: `name` or `type` is missing, or `invalid_node`: the location 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. | ### GET /v1/data-sources/{id} Get a data source Retrieve one data source, including its connector configuration, the time of its last sync, and its row schema. Secret values are redacted. Auth: Bearer token. Permission: `integration.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Data source id. | ```bash curl "https://api.brixsignage.com/v1/data-sources/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | DataSource | A connected data feed. Credentials are never returned. | | `data.id` | string | Data source id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.type` | string | Connector type (`csv`, `http-json`, `rest`, `google-sheets`, `ical`, …). See `GET /v1/connectors`. | | `data.config` | any | Connector settings with every credential-shaped value (keys, tokens, secrets, passwords) replaced by `••••••••`. Send the mask back unchanged to keep the stored value. | | `data.cachedData` | any \| null | The last successful fetch (JSON), or null before the first sync. | | `data.connected` | boolean | The last sync succeeded. | | `data.lastSyncedAt` | string \| null | | | `data.syncError` | string \| null | Why the last sync failed; null when it succeeded. | | `data.nodeId` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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 such data source 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/data-sources/{id} Update a data source Edit a data source's name, connector configuration, or node. Send `••••••••` back for a credential to keep the stored value. To point the source at a different address or connector type, enter the credential again. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Data source id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `type` | string | no | A change of type needs `config` with the credentials entered again. | | `config` | object | no | Connector settings (JSON). The keys depend on `type`. Credentials are encrypted at rest and every later read shows them as `••••••••`. | | `nodeId` | string \| null | no | Move it; the key needs `integration.edit` at the destination too. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/data-sources/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | DataSource | A connected data feed. Credentials are never returned. | | `data.id` | string | Data source id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.type` | string | Connector type (`csv`, `http-json`, `rest`, `google-sheets`, `ical`, …). See `GET /v1/connectors`. | | `data.config` | any | Connector settings with every credential-shaped value (keys, tokens, secrets, passwords) replaced by `••••••••`. Send the mask back unchanged to keep the stored value. | | `data.cachedData` | any \| null | The last successful fetch (JSON), or null before the first sync. | | `data.connected` | boolean | The last sync succeeded. | | `data.lastSyncedAt` | string \| null | | | `data.syncError` | string \| null | Why the last sync failed; null when it succeeded. | | `data.nodeId` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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: No `integration.edit` at the destination 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 data source 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: `credential_reentry_required`: the change moves a masked credential to a new address or connector type (`fields` names what moved); or `invalid_node`: the destination 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. | ### DELETE /v1/data-sources/{id} Delete a data source. It can be restored later with POST /v1/data-sources/:id/restore. Creatives connected to it display their fallback values while it is deleted. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Data source id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/data-sources/{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 data source 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/data-sources/{id}/restore Restore a deleted data source connector. Apps and creatives connected to it begin receiving fresh data again the next time their content refreshes. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Data source id. | ```bash curl -X POST "https://api.brixsignage.com/v1/data-sources/{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 such data source 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 409: `not_deleted`: the data source is not in the recycle bin. | 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/data-sources/{id}/sync Sync a data source now Pull fresh data for one data source immediately instead of waiting for its regular schedule. Runs the connector once and caches the result. A connector failure is NOT an HTTP error: the answer is 200 with `synced: false` and the reason. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Data source id. | ```bash curl -X POST "https://api.brixsignage.com/v1/data-sources/{id}/sync" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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 data source 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/data-sources/refresh-all Sync every data source Pull fresh data for every data source in the workspace immediately. This action is rate-limited to 5 requests per minute. Syncs, one after another, every data source the caller may edit. Rate limited to 5 calls a minute. Auth: Bearer token. Permission: `integration.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/data-sources/refresh-all" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.total` | integer | | | `data.ok` | integer | | | `data.failed` | 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 429: More than 5 calls in 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/data-sources/sheets-methods List Google Sheets sharing methods List the ways a Google Sheet can be shared with the platform, and the address to share it with. The response may include `publicLink`, `serviceAccount`, and `oauth`, depending on what the platform supports; a method is listed only if it is actually available. `serviceAccount` is not a secret; it is the address you enter into Google's Share dialog. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/data-sources/sheets-methods" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.publicLink` | true | A sheet shared as "anyone with the link" always works. | | `data.serviceAccount` | string \| null | The address to share a private sheet with; null when this method is not available. | | `data.oauth` | boolean | A private sheet can be picked by signing in with Google. | 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/data-sources/test Test a data source config Validate a connector configuration without saving it. Returns `{ ok: true, preview }` on success or `{ ok: false, error }` on failure, so a misconfigured feed can be caught before it is created. Runs the connector once with a config that is not saved. A connector failure is NOT an HTTP error: the answer is 200 with `ok: false` and the reason. Rate limited to 20 calls a minute. Auth: Bearer token. Permission: `integration.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `type` | string | yes | | | `config` | object | no | Connector settings (JSON). The keys depend on `type`. Credentials are encrypted at rest and every later read shows them as `••••••••`. | ```bash curl -X POST "https://api.brixsignage.com/v1/data-sources/test" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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 429: More than 20 calls in 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. | ## Device preassignments Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/device-preassignments List pre-assigned devices List devices pre-assigned to locations in your workspace. Each entry shows the end of the device serial number, the location, the screen name, and whether the device is still waiting to be claimed or has already been claimed. Results are limited to the organization nodes you can see. **Notes.** - Carries `available` beside `data`. Auth: Bearer token. Permission: `screen.view`. ```bash curl "https://api.brixsignage.com/v1/device-preassignments" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | string | | | `data[].serialHint` | string \| null | The last 4 characters of the serial. The full serial is not stored. | | `data[].nodeId` | string | | | `data[].locationName` | string \| null | | | `data[].locationId` | string \| null | The location's own id (its Location ID), when set. | | `data[].name` | string \| null | | | `data[].status` | "waiting" \| "claimed" | | | `data[].claimedAt` | string \| null | | | `data[].claimedScreenId` | string \| null | | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `available` | boolean | False when the workspace's data region does not support pre-assignment yet (`data` is then empty). | 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/device-preassignments Pre-assign devices to locations Pre-assign device serial numbers to locations, identified by Location ID or `nodeId`, so each device pairs automatically into its assigned location the first time it is plugged in. Submit up to 1000 rows in one request; all rows are validated before any are saved. Re-uploading the same rows is safe and does not create duplicates, and `dryRun` validates rows without saving them. A serial number already assigned to another workspace is refused with a conflict error, and the action is recorded in the activity log. Auth: Bearer token. Permission: `screen.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `rows` | array of object | yes | | | `rows[].serial` | string | yes | The device serial number. | | `rows[].locationId` | string | no | The location's Location ID (case-insensitive). Give this or `nodeId`. | | `rows[].nodeId` | string | no | | | `rows[].name` | string | no | Screen name; default: the location name and the serial's last 4 characters. | | `dryRun` | boolean | no | True: check and report, change nothing (answers 200). | ```bash curl -X POST "https://api.brixsignage.com/v1/device-preassignments" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success: a dry run (`dryRun: true`): the same report, nothing changed. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.dryRun` | boolean | | | `data.counts` | object | | | `data.counts.claimed` | integer | | | `data.counts.created` | integer | | | `data.counts.updated` | integer | | | `data.counts.unchanged` | integer | | | `data.rows` | array of object | | | `data.rows[].index` | integer | | | `data.rows[].serial` | string | The serial's last 4 characters. | | `data.rows[].outcome` | "claimed" \| "created" \| "updated" \| "unchanged" | | | `data.rows[].nodeId` | string | | | `data.rows[].locationName` | string | | | `data.rows[].locationId` | string \| null | | | `data.rows[].name` | string | | Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.dryRun` | boolean | | | `data.counts` | object | | | `data.counts.claimed` | integer | | | `data.counts.created` | integer | | | `data.counts.updated` | integer | | | `data.counts.unchanged` | integer | | | `data.rows` | array of object | | | `data.rows[].index` | integer | | | `data.rows[].serial` | string | The serial's last 4 characters. | | `data.rows[].outcome` | "claimed" \| "created" \| "updated" \| "unchanged" | | | `data.rows[].nodeId` | string | | | `data.rows[].locationName` | string | | | `data.rows[].locationId` | string \| null | | | `data.rows[].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: A serial is held by another workspace, or the data region does not support pre-assignment. | 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: No rows, too many rows, or a row is invalid; `data.errors` lists each by `index` and `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. | ### DELETE /v1/device-preassignments/{id} Cancel a device pre-assignment Cancel a pending device pre-assignment. If the device has already been claimed and turned into a screen, that screen is not affected. The action is recorded in the activity log. Auth: Bearer token. Permission: `screen.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Pre-assignment id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/device-preassignments/{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 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. | ## Emergencies Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/emergencies List emergency overrides, newest first. Use `?status=active` to filter to overrides currently taking over screens. Auth: Bearer token. Permission: `emergency-override.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `status` | query | "active" \| "cleared" \| "expired" | no | Only overrides in this state. | | `limit` | query | integer | no | At most this many, newest first (default 200, max 500). | ```bash curl "https://api.brixsignage.com/v1/emergencies" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of Override | | | `data[].id` | string | Override (cast_… or emg_…) id. | | `data[].spaceId` | string | Workspace id. | | `data[].kind` | "emergency" \| "cast" | | | `data[].status` | "active" \| "cleared" \| "expired" | | | `data[].severity` | "info" \| "warning" \| "critical" | Always `info` for a cast. | | `data[].headline` | string | An emergency's message; for a cast, the content's name (or "Cast"). | | `data[].body` | string \| null | | | `data[].contentKind` | "media" \| "creative" \| "playlist" \| "app" \| "schedule" \| null | | | `data[].contentId` | string \| null | | | `data[].scopeKind` | "all" \| "node" \| "screens" | | | `data[].scopeNodeId` | string \| null | The location, when `scopeKind` is `node`. | | `data[].nodeId` | string \| null | Location the override is attributed to (null = workspace root). | | `data[].screenCount` | integer | Screens targeted, snapshotted when it started. | | `data[].triggeredBy` | string | User id, or `system`. | | `data[].triggeredAt` | string | ISO-8601 timestamp (UTC). | | `data[].expiresAt` | string \| null | Auto-clear time; null = until cleared. | | `data[].clearedBy` | string \| null | | | `data[].clearedAt` | string \| null | | | `data[].triggeredByName` | string | Display name of `triggeredBy`. | | `data[].clearedByName` | string \| null | | | `data[].scopeName` | string \| null | Location name, `All screens`, or null for a screen list. | | `data[].confirmedCount` | integer \| null | Active emergencies only: screens confirmed showing it now. Null otherwise. | ```json { "data": [ { "id": "emg_4d5e6f7a8b9c0d1e", "spaceId": "space_1a2b3c4d5e6f7a8b", "kind": "emergency", "status": "active", "severity": "critical", "headline": "Evacuate the building now", "body": "Use the nearest exit. Do not use the lifts.", "contentKind": null, "contentId": null, "scopeKind": "all", "scopeNodeId": null, "nodeId": null, "screenCount": 42, "triggeredBy": "usr_5e6f7a8b9c0d1e2f", "triggeredAt": "2026-09-28T11:30:00.000Z", "expiresAt": null, "clearedBy": null, "clearedAt": null, "triggeredByName": "Sam Rivera", "clearedByName": null, "scopeName": "All screens", "confirmedCount": 0 } ] } ``` 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/emergencies Trigger an emergency takeover across a set of screens. An emergency takes the highest priority and layers over each screen's normal content, so clearing it restores exactly what was playing before. Takes over the chosen screens now, above every schedule and cast, until cleared or expired. Clearing restores exactly what was playing. **Notes.** - The route is gated by `emergency-override.create`; the route registry's description names `emergency-override.trigger`, which is not a permission the API checks. Auth: Bearer token. Permission: `emergency-override.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `headline` | string | yes | The message on every targeted screen. Required, non-blank. | | `body` | string | no | Optional second line. | | `severity` | "info" \| "warning" \| "critical" | no | Default `critical`. | | `contentKind` | "media" \| "creative" | no | Attach content to show under the banner. Needs `contentId`. | | `contentId` | string | no | The media or creative id. Needs `contentKind`. | | `scopeKind` | "all" \| "node" \| "screens" | no | Which screens: every screen you can reach (`all`), one location's subtree (`node`), or a list (`screens`). **Defaults to `all`: omit it and the emergency goes to EVERY screen you can reach.** | | `scopeNodeId` | string | no | Location id, with `scopeKind: node`. | | `screenIds` | array of string | no | Screen ids, with `scopeKind: screens`. | | `expiresAt` | string \| number \| null | no | When it clears by itself: ISO-8601 or epoch milliseconds. Omit or null to run until cleared. | ```bash curl -X POST "https://api.brixsignage.com/v1/emergencies" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"headline":"Evacuate the building now","body":"Use the nearest exit. Do not use the lifts.","severity":"critical","scopeKind":"all"}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Override | A cast or an emergency override. | | `data.id` | string | Override (cast_… or emg_…) id. | | `data.spaceId` | string | Workspace id. | | `data.kind` | "emergency" \| "cast" | | | `data.status` | "active" \| "cleared" \| "expired" | | | `data.severity` | "info" \| "warning" \| "critical" | Always `info` for a cast. | | `data.headline` | string | An emergency's message; for a cast, the content's name (or "Cast"). | | `data.body` | string \| null | | | `data.contentKind` | "media" \| "creative" \| "playlist" \| "app" \| "schedule" \| null | | | `data.contentId` | string \| null | | | `data.scopeKind` | "all" \| "node" \| "screens" | | | `data.scopeNodeId` | string \| null | The location, when `scopeKind` is `node`. | | `data.nodeId` | string \| null | Location the override is attributed to (null = workspace root). | | `data.screenCount` | integer | Screens targeted, snapshotted when it started. | | `data.triggeredBy` | string | User id, or `system`. | | `data.triggeredAt` | string | ISO-8601 timestamp (UTC). | | `data.expiresAt` | string \| null | Auto-clear time; null = until cleared. | | `data.clearedBy` | string \| null | | | `data.clearedAt` | string \| null | | | `data.triggeredByName` | string | Display name of `triggeredBy`. | | `data.clearedByName` | string \| null | | | `data.scopeName` | string \| null | Location name, `All screens`, or null for a screen list. | | `data.confirmedCount` | integer \| null | Active emergencies only: screens confirmed showing it now. Null otherwise. | ```json { "data": { "id": "emg_4d5e6f7a8b9c0d1e", "spaceId": "space_1a2b3c4d5e6f7a8b", "kind": "emergency", "status": "active", "severity": "critical", "headline": "Evacuate the building now", "body": "Use the nearest exit. Do not use the lifts.", "contentKind": null, "contentId": null, "scopeKind": "all", "scopeNodeId": null, "nodeId": null, "screenCount": 42, "triggeredBy": "usr_5e6f7a8b9c0d1e2f", "triggeredAt": "2026-09-28T11:30:00.000Z", "expiresAt": null, "clearedBy": null, "clearedAt": null, "triggeredByName": "Sam Rivera", "clearedByName": null, "scopeName": "All screens", "confirmedCount": 0 } } ``` 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 one or more target locations. | 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 content does not exist 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: Missing headline, a half content pointer, a bad `expiresAt`, no reachable screens (`no_screens`), or content that cannot play (`content_unplayable`). | 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/emergencies/{id} Get an emergency override Retrieve one emergency override, including its scope, content, and expiry. Auth: Bearer token. Permission: `emergency-override.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Emergency id. | ```bash curl "https://api.brixsignage.com/v1/emergencies/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Override | A cast or an emergency override. | | `data.id` | string | Override (cast_… or emg_…) id. | | `data.spaceId` | string | Workspace id. | | `data.kind` | "emergency" \| "cast" | | | `data.status` | "active" \| "cleared" \| "expired" | | | `data.severity` | "info" \| "warning" \| "critical" | Always `info` for a cast. | | `data.headline` | string | An emergency's message; for a cast, the content's name (or "Cast"). | | `data.body` | string \| null | | | `data.contentKind` | "media" \| "creative" \| "playlist" \| "app" \| "schedule" \| null | | | `data.contentId` | string \| null | | | `data.scopeKind` | "all" \| "node" \| "screens" | | | `data.scopeNodeId` | string \| null | The location, when `scopeKind` is `node`. | | `data.nodeId` | string \| null | Location the override is attributed to (null = workspace root). | | `data.screenCount` | integer | Screens targeted, snapshotted when it started. | | `data.triggeredBy` | string | User id, or `system`. | | `data.triggeredAt` | string | ISO-8601 timestamp (UTC). | | `data.expiresAt` | string \| null | Auto-clear time; null = until cleared. | | `data.clearedBy` | string \| null | | | `data.clearedAt` | string \| null | | | `data.triggeredByName` | string | Display name of `triggeredBy`. | | `data.clearedByName` | string \| null | | | `data.scopeName` | string \| null | Location name, `All screens`, or null for a screen list. | | `data.confirmedCount` | integer \| null | Active emergencies only: screens confirmed showing it now. Null otherwise. | 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 emergency in this workspace (or outside your locations). | 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/emergencies/{id}/clear Clear an emergency override Clear an active emergency override. This releases its screens back to their normal content, and the action is recorded in the activity log. Auth: Bearer token. Permission: `emergency-override.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Emergency id. | ```bash curl -X POST "https://api.brixsignage.com/v1/emergencies/{id}/clear" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Override | A cast or an emergency override. | | `data.id` | string | Override (cast_… or emg_…) id. | | `data.spaceId` | string | Workspace id. | | `data.kind` | "emergency" \| "cast" | | | `data.status` | "active" \| "cleared" \| "expired" | | | `data.severity` | "info" \| "warning" \| "critical" | Always `info` for a cast. | | `data.headline` | string | An emergency's message; for a cast, the content's name (or "Cast"). | | `data.body` | string \| null | | | `data.contentKind` | "media" \| "creative" \| "playlist" \| "app" \| "schedule" \| null | | | `data.contentId` | string \| null | | | `data.scopeKind` | "all" \| "node" \| "screens" | | | `data.scopeNodeId` | string \| null | The location, when `scopeKind` is `node`. | | `data.nodeId` | string \| null | Location the override is attributed to (null = workspace root). | | `data.screenCount` | integer | Screens targeted, snapshotted when it started. | | `data.triggeredBy` | string | User id, or `system`. | | `data.triggeredAt` | string | ISO-8601 timestamp (UTC). | | `data.expiresAt` | string \| null | Auto-clear time; null = until cleared. | | `data.clearedBy` | string \| null | | | `data.clearedAt` | string \| null | | | `data.triggeredByName` | string | Display name of `triggeredBy`. | | `data.clearedByName` | string \| null | | | `data.scopeName` | string \| null | Location name, `All screens`, or null for a screen list. | | `data.confirmedCount` | integer \| null | Active emergencies only: screens confirmed showing it now. Null otherwise. | 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 emergency 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 409: `already_inactive`: already cleared or 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 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/emergencies/from-template/{templateId} Trigger an emergency from a template Trigger an emergency using a stored template. Any scope or expiry sent with the request overrides the template's defaults. Fires a stored template. Scope and expiry given here override the template's defaults. Send `{}` to use the template as stored. Auth: Bearer token. Permission: `emergency-override.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `templateId` | path | string | yes | Emergency template id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `scopeKind` | "all" \| "node" \| "screens" | no | Which screens: every screen you can reach (`all`), one location's subtree (`node`), or a list (`screens`). **Defaults to `all`: omit it and the emergency goes to EVERY screen you can reach.** | | `scopeNodeId` | string | no | Location id, with `scopeKind: node`. | | `screenIds` | array of string | no | Screen ids, with `scopeKind: screens`. | | `expiresAt` | string \| number \| null | no | When it clears by itself: ISO-8601 or epoch milliseconds. Omit or null to run until cleared. | ```bash curl -X POST "https://api.brixsignage.com/v1/emergencies/from-template/{templateId}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Override | A cast or an emergency override. | | `data.id` | string | Override (cast_… or emg_…) id. | | `data.spaceId` | string | Workspace id. | | `data.kind` | "emergency" \| "cast" | | | `data.status` | "active" \| "cleared" \| "expired" | | | `data.severity` | "info" \| "warning" \| "critical" | Always `info` for a cast. | | `data.headline` | string | An emergency's message; for a cast, the content's name (or "Cast"). | | `data.body` | string \| null | | | `data.contentKind` | "media" \| "creative" \| "playlist" \| "app" \| "schedule" \| null | | | `data.contentId` | string \| null | | | `data.scopeKind` | "all" \| "node" \| "screens" | | | `data.scopeNodeId` | string \| null | The location, when `scopeKind` is `node`. | | `data.nodeId` | string \| null | Location the override is attributed to (null = workspace root). | | `data.screenCount` | integer | Screens targeted, snapshotted when it started. | | `data.triggeredBy` | string | User id, or `system`. | | `data.triggeredAt` | string | ISO-8601 timestamp (UTC). | | `data.expiresAt` | string \| null | Auto-clear time; null = until cleared. | | `data.clearedBy` | string \| null | | | `data.clearedAt` | string \| null | | | `data.triggeredByName` | string | Display name of `triggeredBy`. | | `data.clearedByName` | string \| null | | | `data.scopeName` | string \| null | Location name, `All screens`, or null for a screen list. | | `data.confirmedCount` | integer \| null | Active emergencies only: screens confirmed showing it now. Null otherwise. | 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 template's content is not shared to one or more target locations. | 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 template, or its content is gone. | 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: Bad `expiresAt`, no reachable screens (`no_screens`), or content that cannot play. | 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. | ## Emergency templates Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/emergency-templates List emergency templates List pre-configured emergency recipes, such as Lockdown, Severe weather, or Fire drill, used for one-click triggering. A template with `prestage` set to true has its content cached on every screen in scope in advance, before it is ever triggered. Auth: Bearer token. Permission: `emergency-override.view`. ```bash curl "https://api.brixsignage.com/v1/emergency-templates" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of EmergencyTemplate | | | `data[].id` | string | Emergency template id. | | `data[].spaceId` | string | | | `data[].name` | string | | | `data[].severity` | "info" \| "warning" \| "critical" | | | `data[].headline` | string | | | `data[].body` | string \| null | | | `data[].contentKind` | "media" \| "creative" \| "playlist" \| "app" \| null | | | `data[].contentId` | string \| null | | | `data[].scopeKind` | "all" \| "node" \| "screens" | | | `data[].scopeNodeId` | string \| null | | | `data[].nodeId` | string \| null | Home location (null = workspace root); permissions are checked here. | | `data[].prestage` | boolean | Content is cached on every in-scope screen before any trigger. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | 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/emergency-templates Create an emergency template Create a reusable emergency recipe with a `name`, `contentKind`, `contentId`, `scope`, `severity`, and an optional `prestage` flag. Trigger it later with POST /v1/emergencies/from-template/:templateId. Auth: Bearer token. Permission: `emergency-override.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Default "Untitled template". | | `headline` | string | no | Default "Emergency". | | `body` | string | no | | | `severity` | "info" \| "warning" \| "critical" | no | Default `critical`. | | `contentKind` | "media" \| "creative" \| "playlist" \| "app" | no | | | `contentId` | string | no | | | `scopeKind` | "all" \| "node" \| "screens" | no | Default `all`. | | `scopeNodeId` | string | no | Required with `scopeKind: node`. | | `nodeId` | string | no | Home location. Default: the API key's location, else the workspace root. | | `prestage` | boolean | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/emergency-templates" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | EmergencyTemplate | A pre-armed emergency recipe. | | `data.id` | string | Emergency template id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.severity` | "info" \| "warning" \| "critical" | | | `data.headline` | string | | | `data.body` | string \| null | | | `data.contentKind` | "media" \| "creative" \| "playlist" \| "app" \| null | | | `data.contentId` | string \| null | | | `data.scopeKind` | "all" \| "node" \| "screens" | | | `data.scopeNodeId` | string \| null | | | `data.nodeId` | string \| null | Home location (null = workspace root); permissions are checked here. | | `data.prestage` | boolean | Content is cached on every in-scope screen before any trigger. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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: Missing `emergency-override.edit` at the home or scope 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: The content or a location does not exist 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: A content kind outside media/creative/playlist/app, a half content pointer, or `scopeKind: node` without `scopeNodeId`. | 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/emergency-templates/{id} Update an emergency template Edit an emergency template's content, scope, severity, or prestaging setting. Auth: Bearer token. Permission: `emergency-override.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/emergency-templates/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/emergency-templates/{id} Delete an emergency template. Emergencies already triggered from it are unaffected. Auth: Bearer token. Permission: `emergency-override.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/emergency-templates/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## GDPR Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### POST /v1/gdpr/erase Erase user under GDPR Permanently anonymizes a user's profile and deletes their associated personal data, including linked identities, passkeys, sessions, and pending email verifications, to satisfy a right-to-be-forgotten request. You cannot erase your own account or the last remaining account owner. Only an account owner can do this to an owner, and you cannot do it to a person who holds a permission you do not hold. An API key is never an owner. **Notes.** - Irreversible. Unlike `POST /v1/users/:id/erase`, the person does not have to be deactivated first. Auth: Bearer token. Permission: `user.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `userId` | string | yes | | ```bash curl -X POST "https://api.brixsignage.com/v1/gdpr/erase" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.userId` | string | | | `data.erased` | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). Also `owner_required` or `outranked`: the person is an owner, or holds a permission you do not hold. | 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 person 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 409: `cant_erase_self` or `last_owner`. | 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: `userId` 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/gdpr/export Export user data (GDPR) Assembles a data subject access request export for the whole workspace: every person, screen and piece of content, and the last 365 days of the activity log, as one JSON file with secret values withheld. The key or person must have `billing.edit` for the whole workspace. Each table is capped; `complete` is false when one was cut. **Notes.** - Exports the whole workspace, not one person: every person, screen and piece of content, and the last 365 days of the activity log. - No `{ data }` envelope: the body is the export file (`Content-Disposition: attachment`). - Credentials are never exported: their columns are kept with `null` or a `[secret; not exported]` / `[encrypted; not exported]` marker. Auth: Bearer token. Permission: `billing.edit`. ```bash curl "https://api.brixsignage.com/v1/gdpr/export" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `schemaVersion` | 1 | | | `generatedAt` | string | ISO-8601 timestamp (UTC). | | `subject` | object | | | `subject.spaceId` | string | The workspace exported. | | `subject.requestedByUserId` | string \| null | Null for an API key. | | `limits` | object | | | `limits.perTable` | integer | At most this many rows per table. | | `limits.auditEvents` | integer | At most this many activity log events. | | `limits.auditWindowDays` | integer | Activity log events from this many days back. | | `truncated` | object | The tables cut at the limit; empty when none. | | `complete` | boolean | False when a table was cut at the limit. | | `tables` | object | | | `tables.workspace` | object \| null | The workspace record, every column (the Screen Lock PIN hash replaced by `kioskHasGlobalPin` inside `prefs`). | | `tables.users` | array of object | | | `tables.users[].id` | string | | | `tables.users[].name` | string | | | `tables.users[].email` | string | | | `tables.users[].title` | string \| null | | | `tables.users[].phone` | string \| null | | | `tables.users[].status` | string | | | `tables.users[].lastLoginAt` | string \| null | | | `tables.users[].emailVerifiedAt` | string \| null | | | `tables.users[].createdAt` | string | ISO-8601 timestamp (UTC). | | `tables.users[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `tables.users[].deletedAt` | string \| null | | | `tables.screens` | array of object | Every column; credentials withheld, and `kioskHasCustomPin` / `kioskHasRecovery` added. | | `tables.media` | array of object | | | `tables.playlists` | array of object | | | `tables.schedules` | array of object | | | `tables.layouts` | array of object | | | `tables.creatives` | array of object | | | `tables.appInstances` | array of object | | | `tables.dataSources` | array of object | `config` is `[encrypted; not exported]` (or null). | | `tables.banners` | array of object | | | `tables.auditEvents` | array of object | Newest first. | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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/gdpr/export Export user data (GDPR) Same as the GET version of this operation: assembles a data subject access request export for the whole workspace as one JSON file, with secret values withheld. The key or person must have `billing.edit` for the whole workspace. Takes no request body. **Notes.** - Exports the whole workspace, not one person: every person, screen and piece of content, and the last 365 days of the activity log. - No `{ data }` envelope: the body is the export file (`Content-Disposition: attachment`). - Credentials are never exported: their columns are kept with `null` or a `[secret; not exported]` / `[encrypted; not exported]` marker. - Takes no body; the same export as the GET. Auth: Bearer token. Permission: `billing.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/gdpr/export" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `schemaVersion` | 1 | | | `generatedAt` | string | ISO-8601 timestamp (UTC). | | `subject` | object | | | `subject.spaceId` | string | The workspace exported. | | `subject.requestedByUserId` | string \| null | Null for an API key. | | `limits` | object | | | `limits.perTable` | integer | At most this many rows per table. | | `limits.auditEvents` | integer | At most this many activity log events. | | `limits.auditWindowDays` | integer | Activity log events from this many days back. | | `truncated` | object | The tables cut at the limit; empty when none. | | `complete` | boolean | False when a table was cut at the limit. | | `tables` | object | | | `tables.workspace` | object \| null | The workspace record, every column (the Screen Lock PIN hash replaced by `kioskHasGlobalPin` inside `prefs`). | | `tables.users` | array of object | | | `tables.users[].id` | string | | | `tables.users[].name` | string | | | `tables.users[].email` | string | | | `tables.users[].title` | string \| null | | | `tables.users[].phone` | string \| null | | | `tables.users[].status` | string | | | `tables.users[].lastLoginAt` | string \| null | | | `tables.users[].emailVerifiedAt` | string \| null | | | `tables.users[].createdAt` | string | ISO-8601 timestamp (UTC). | | `tables.users[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `tables.users[].deletedAt` | string \| null | | | `tables.screens` | array of object | Every column; credentials withheld, and `kioskHasCustomPin` / `kioskHasRecovery` added. | | `tables.media` | array of object | | | `tables.playlists` | array of object | | | `tables.schedules` | array of object | | | `tables.layouts` | array of object | | | `tables.creatives` | array of object | | | `tables.appInstances` | array of object | | | `tables.dataSources` | array of object | `config` is `[encrypted; not exported]` (or null). | | `tables.banners` | array of object | | | `tables.auditEvents` | array of object | Newest first. | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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. | ## Interactions Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/interactions List interaction events List engagement events from your screens, such as on-screen taps, QR code scans, presenter joins, and form submissions. **Notes.** - Newest first, at most 1000, from screens at locations the caller can see. Auth: Bearer token. Permission: `playback-log.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `from` | query | string | no | ISO-8601 start (default: 7 days ago). | | `to` | query | string | no | ISO-8601 end (default: now). | | `screenId` | query | string | no | | ```bash curl "https://api.brixsignage.com/v1/interactions" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | string | | | `data[].screenId` | string | | | `data[].screenName` | string | The screen id when the screen no longer exists. | | `data[].at` | string | ISO-8601 timestamp (UTC). | | `data[].kind` | "touch" \| "qr-scan" \| "screenshare-join" \| "form-submit" | | | `data[].detail` | string | A short human-readable summary. | 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. | ## Layouts Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/layouts List layouts List the workspace's multi-zone layouts, including each layout's name, resolution, and zone count. Auth: Bearer token. Permission: `layout.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Page size. Omit to get every row; pass it to page by `cursor`. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | | `count` | query | "1" | no | With `limit`: also return `total`, the number of matching rows. | | `usableAt` | query | string | no | Location id: only rows usable at that location (homed there, at the workspace root, or shared to it). | ```bash curl "https://api.brixsignage.com/v1/layouts" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of Layout | | | `data[].id` | string | Layout id. | | `data[].spaceId` | string | | | `data[].name` | string | | | `data[].resolution` | object | Design canvas size in pixels. | | `data[].resolution.w` | number | | | `data[].resolution.h` | number | | | `data[].zones` | array of object | Zones: geometry, bound content, frame, radius and role. `GET /v1/layouts/{id}/zones` returns the same list. | | `data[].autoFullscreenForVideo` | boolean \| null | | | `data[].approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data[].approvedSnapshot` | string \| null | JSON TEXT of the last approved version (not parsed). | | `data[].nodeId` | string \| null | | | `data[].importSourceId` | string \| null | | | `data[].theme` | any \| null | Brand Kit look (`{ source: "brand", mode?, radius? }`), or null for a bare canvas. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | | `data[].usedByScreenCount` | integer | Screens showing this layout now, directly or through a playlist or schedule. | | `nextCursor` | string \| null | Present when `?limit` was passed. Send it back as `?cursor=` for the next page; null on the last page. | | `total` | integer | Total matching rows, when the route computes it. | 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/layouts Create a layout Create a multi-zone layout with a `name` and optional `resolution`, `zones`, `autoFullscreenForVideo`, `nodeId`, and `theme`. Setting `theme` to `{ source: "brand", mode?, radius? }` paints the layout using the workspace Brand Kit; omit it or set it to `null` for a plain canvas. Each zone can also carry a frame style (bare, card, accent, or glass), a corner radius in pixels, and a role of "logo" for a zone that shows the Brand Kit logo without needing its own content. **Notes.** - The 201 body is the row as written, not re-read from the database, so columns the create does not set (for example `lastSnapshotAt`) are absent rather than null. `GET` returns every column. - The 201 body has no `usedByScreenCount`. Auth: Bearer token. Permission: `layout.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `resolution` | object | no | Canvas size in pixels. Default 1920 x 1080. | | `resolution.w` | number | yes | | | `resolution.h` | number | yes | | | `zones` | array of LayoutZone | no | The whole zone list. Default: one full-canvas zone named Main. | | `zones[].id` | string | yes | | | `zones[].name` | string | yes | | | `zones[].x` | number | yes | Left edge, in layout pixels. | | `zones[].y` | number | yes | | | `zones[].w` | number | yes | | | `zones[].h` | number | yes | | | `zones[].locked` | boolean | yes | | | `zones[].contentName` | string | no | Label of the bound content. Absent on the zone a new layout starts with. | | `zones[].content` | object \| null | no | What the zone plays: a media file, playlist, schedule, app, web link or creative (`canvas`). | | `zones[].zIndex` | number | no | | | `zones[].ownerScope` | "workspace" \| "org_unit" \| "location" | no | | | `zones[].ownerNodeId` | string \| null | no | | | `zones[].frame` | "bare" \| "card" \| "accent" \| "glass" | no | Paint style on a themed layout. | | `zones[].radius` | number | no | Corner radius in layout pixels. | | `zones[].role` | "logo" | no | `logo`: the zone shows the Brand Kit logo instead of content. | | `autoFullscreenForVideo` | boolean \| null | no | | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | | `theme` | object \| null | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/layouts" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Layout id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.resolution` | object | Design canvas size in pixels. | | `data.resolution.w` | number | | | `data.resolution.h` | number | | | `data.zones` | array of object | Zones: geometry, bound content, frame, radius and role. `GET /v1/layouts/{id}/zones` returns the same list. | | `data.autoFullscreenForVideo` | boolean \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Absent: the create does not set it (it is `draft`). | | `data.approvedSnapshot` | string \| null | | | `data.nodeId` | string \| null | | | `data.importSourceId` | string \| null | | | `data.theme` | any \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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`: zone content that is not usable at the layout'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 422: Missing name, invalid JSON, a bad theme or zone paint, zone content that does not exist or loops, or a location outside 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/layouts/{id} Get a layout Retrieve one layout, including its zone geometry with each zone's content assignment and frame, radius, and role styling, its theme (the Brand Kit look, or null), and its video-fullscreen behaviour. Auth: Bearer token. Permission: `layout.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | ```bash curl "https://api.brixsignage.com/v1/layouts/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Layout id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.resolution` | object | Design canvas size in pixels. | | `data.resolution.w` | number | | | `data.resolution.h` | number | | | `data.zones` | array of object | Zones: geometry, bound content, frame, radius and role. `GET /v1/layouts/{id}/zones` returns the same list. | | `data.autoFullscreenForVideo` | boolean \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data.approvedSnapshot` | string \| null | JSON TEXT of the last approved version (not parsed). | | `data.nodeId` | string \| null | | | `data.importSourceId` | string \| null | | | `data.theme` | any \| null | Brand Kit look (`{ source: "brand", mode?, radius? }`), or null for a bare canvas. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | | `data.usedByScreenCount` | integer | Screens showing this layout now, directly or through a playlist or schedule. | | `data.requiresApproval` | boolean | The home location requires approval before content airs. | 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 layout 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/layouts/{id} Update a layout Edit a layout's name, resolution, zones, `autoFullscreenForVideo`, node, or theme. Setting `theme` to `{ source: "brand", mode?, radius? }` turns the Brand Kit look on, and `null` turns it off. Zones accept a frame style (bare, card, accent, or glass), a radius, and a role of "logo". An invalid theme, or an unrecognized frame or role, returns 422. **Notes.** - The response is the stored row: it has no `usedByScreenCount` or `requiresApproval` (GET has them). Auth: Bearer token. Permission: `layout.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `resolution` | object | no | Canvas size in pixels. Default 1920 x 1080. | | `resolution.w` | number | yes | | | `resolution.h` | number | yes | | | `zones` | array of LayoutZone | no | The whole zone list. Default: one full-canvas zone named Main. | | `zones[].id` | string | yes | | | `zones[].name` | string | yes | | | `zones[].x` | number | yes | Left edge, in layout pixels. | | `zones[].y` | number | yes | | | `zones[].w` | number | yes | | | `zones[].h` | number | yes | | | `zones[].locked` | boolean | yes | | | `zones[].contentName` | string | no | Label of the bound content. Absent on the zone a new layout starts with. | | `zones[].content` | object \| null | no | What the zone plays: a media file, playlist, schedule, app, web link or creative (`canvas`). | | `zones[].zIndex` | number | no | | | `zones[].ownerScope` | "workspace" \| "org_unit" \| "location" | no | | | `zones[].ownerNodeId` | string \| null | no | | | `zones[].frame` | "bare" \| "card" \| "accent" \| "glass" | no | Paint style on a themed layout. | | `zones[].radius` | number | no | Corner radius in layout pixels. | | `zones[].role` | "logo" | no | `logo`: the zone shows the Brand Kit logo instead of content. | | `autoFullscreenForVideo` | boolean \| null | no | | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | | `theme` | object \| null | no | | | `baseUpdatedAt` | string | no | Optimistic concurrency: the `updatedAt` you read. A stale value is refused with 409 `conflict` and the `current` row. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/layouts/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Layout id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.resolution` | object | Design canvas size in pixels. | | `data.resolution.w` | number | | | `data.resolution.h` | number | | | `data.zones` | array of object | Zones: geometry, bound content, frame, radius and role. `GET /v1/layouts/{id}/zones` returns the same list. | | `data.autoFullscreenForVideo` | boolean \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Review state. Editing an approved row returns it to `draft`. | | `data.approvedSnapshot` | string \| null | JSON TEXT of the last approved version (not parsed). | | `data.nodeId` | string \| null | | | `data.importSourceId` | string \| null | | | `data.theme` | any \| null | Brand Kit look (`{ source: "brand", mode?, radius? }`), or null for a bare canvas. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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: Moving it to a location where you lack layout.edit, or zone content that is not usable there (`not_shared`). | 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 layout 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 409: `conflict`: the row changed since `baseUpdatedAt`; the body carries `current`. | 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 JSON, a bad theme or zone paint, zone content that does not exist or loops back into this layout. | 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/layouts/{id} Delete a layout. This fails with 409 `content_shared` if the layout is actively shared into other spaces; pass `?force=true` to delete it anyway. Auth: Bearer token. Permission: `layout.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | | `force` | query | "true" | no | Delete even when it is shared into other places; the shares go with it. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/layouts/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.deleted` | true | | | `data.sharesRemoved` | integer | Shares removed with it. | 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 layout 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 409: `content_shared`: it is shared into other places; `shareCount`, `crossSpaceShares`, `contentShares` say where. Repeat with `?force=true` to delete it and those shares. | 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/layouts/{id}/restore Restore a deleted layout so it returns to the layout library. Auth: Bearer token. Permission: `layout.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | ```bash curl -X POST "https://api.brixsignage.com/v1/layouts/{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 such layout in this workspace, or it was purged. | 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: `not_deleted`: the layout is not in the recycle bin. | 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/layouts/{id}/thumbnail Get a layout thumbnail Retrieve a preview image of a layout, showing each zone at its real position filled with a still of its content. The same image is used everywhere the layout is previewed. Add `?fresh=1` to regenerate it after an edit. Auth: Bearer token. Permission: `layout.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | | `fresh` | query | "1" | no | Skip the cached image and draw it again. | ```bash curl "https://api.brixsignage.com/v1/layouts/{id}/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 such layout 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/layouts/{id}/zones List a layout's zones, including the content attached to each one. Auth: Bearer token. Permission: `layout.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | ```bash curl "https://api.brixsignage.com/v1/layouts/{id}/zones" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of LayoutZone | | | `data[].id` | string | | | `data[].name` | string | | | `data[].x` | number | Left edge, in layout pixels. | | `data[].y` | number | | | `data[].w` | number | | | `data[].h` | number | | | `data[].locked` | boolean | | | `data[].contentName` | string | Label of the bound content. Absent on the zone a new layout starts with. | | `data[].content` | object \| null | What the zone plays: a media file, playlist, schedule, app, web link or creative (`canvas`). | | `data[].zIndex` | number | | | `data[].ownerScope` | "workspace" \| "org_unit" \| "location" | | | `data[].ownerNodeId` | string \| null | | | `data[].frame` | "bare" \| "card" \| "accent" \| "glass" | Paint style on a themed layout. | | `data[].radius` | number | Corner radius in layout pixels. | | `data[].role` | "logo" | `logo`: the zone shows the Brand Kit logo instead of content. | 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 layout 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/layouts/{id}/zones Add a zone to a layout Add one zone to a layout, specifying its geometry, the content attached to it, and, on a themed layout, its frame (bare, card, accent, or glass), radius, and role. A zone with role "logo" does not need content of its own. **Notes.** - Editing zones returns an approved layout to `draft` where approval is required. Auth: Bearer token. Permission: `layout.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Default `Zone `. | | `x` | number | no | Default 0. | | `y` | number | no | Default 0. | | `w` | number | no | Default 480. | | `h` | number | no | Default 270. | | `locked` | boolean | no | | | `contentName` | string | no | | | `content` | object \| null | no | What the zone plays. It must exist in this workspace, be usable at the layout's location, and not contain this layout. | | `ownerScope` | "workspace" \| "org_unit" \| "location" | no | | | `ownerNodeId` | string \| null | no | | | `frame` | "bare" \| "card" \| "accent" \| "glass" | no | | | `radius` | number | no | | | `role` | "logo" | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/layouts/{id}/zones" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Layout id. | | `data.zones` | array of LayoutZone | Every zone, in paint order. | | `data.zones[].id` | string | | | `data.zones[].name` | string | | | `data.zones[].x` | number | Left edge, in layout pixels. | | `data.zones[].y` | number | | | `data.zones[].w` | number | | | `data.zones[].h` | number | | | `data.zones[].locked` | boolean | | | `data.zones[].contentName` | string | Label of the bound content. Absent on the zone a new layout starts with. | | `data.zones[].content` | object \| null | What the zone plays: a media file, playlist, schedule, app, web link or creative (`canvas`). | | `data.zones[].zIndex` | number | | | `data.zones[].ownerScope` | "workspace" \| "org_unit" \| "location" | | | `data.zones[].ownerNodeId` | string \| null | | | `data.zones[].frame` | "bare" \| "card" \| "accent" \| "glass" | Paint style on a themed layout. | | `data.zones[].radius` | number | Corner radius in layout pixels. | | `data.zones[].role` | "logo" | `logo`: the zone shows the Brand Kit logo instead of content. | | `data.zone` | LayoutZone | A zone. Open: a whole-array write (`PUT /v1/layouts/{id}/zones` with `zones`, or `PATCH /v1/layouts/{id}`) stores each zone as sent, so other keys come back too. | | `data.zone.id` | string | | | `data.zone.name` | string | | | `data.zone.x` | number | Left edge, in layout pixels. | | `data.zone.y` | number | | | `data.zone.w` | number | | | `data.zone.h` | number | | | `data.zone.locked` | boolean | | | `data.zone.contentName` | string | Label of the bound content. Absent on the zone a new layout starts with. | | `data.zone.content` | object \| null | What the zone plays: a media file, playlist, schedule, app, web link or creative (`canvas`). | | `data.zone.zIndex` | number | | | `data.zone.ownerScope` | "workspace" \| "org_unit" \| "location" | | | `data.zone.ownerNodeId` | string \| null | | | `data.zone.frame` | "bare" \| "card" \| "accent" \| "glass" | Paint style on a themed layout. | | `data.zone.radius` | number | Corner radius in layout pixels. | | `data.zone.role` | "logo" | `logo`: the zone shows the Brand Kit logo instead of content. | 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 usable at the layout'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 layout (or zone) 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 409: `conflict`: the layout changed during the write three times running; retry. | 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: A bad frame, radius or role, content that does not exist, or zone content that plays this layout again (a loop). | 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. | ### PUT /v1/layouts/{id}/zones Replace a layout's zones Replace a layout's entire set of zones in one call. Send every zone you want to keep, including each zone's frame, radius, and role on a themed layout; a zone sent without those becomes a plain, unframed zone. Send `zones` to replace the whole list, or `zoneIds` (every current zone id, once) to reorder it. **Notes.** - `zones` is stored as sent: keys the API does not know are kept and returned. Auth: Bearer token. Permission: `layout.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | Request body (`application/json`): ```bash curl -X PUT "https://api.brixsignage.com/v1/layouts/{id}/zones" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Layout id. | | `data.zones` | array of LayoutZone | Every zone, in paint order. | | `data.zones[].id` | string | | | `data.zones[].name` | string | | | `data.zones[].x` | number | Left edge, in layout pixels. | | `data.zones[].y` | number | | | `data.zones[].w` | number | | | `data.zones[].h` | number | | | `data.zones[].locked` | boolean | | | `data.zones[].contentName` | string | Label of the bound content. Absent on the zone a new layout starts with. | | `data.zones[].content` | object \| null | What the zone plays: a media file, playlist, schedule, app, web link or creative (`canvas`). | | `data.zones[].zIndex` | number | | | `data.zones[].ownerScope` | "workspace" \| "org_unit" \| "location" | | | `data.zones[].ownerNodeId` | string \| null | | | `data.zones[].frame` | "bare" \| "card" \| "accent" \| "glass" | Paint style on a themed layout. | | `data.zones[].radius` | number | Corner radius in layout pixels. | | `data.zones[].role` | "logo" | `logo`: the zone shows the Brand Kit logo instead of content. | 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 usable at the layout'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 layout (or zone) 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 409: `conflict`: the layout changed during the write three times running; retry. | 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: `zoneIds` that are not every zone exactly once, neither `zones` nor `zoneIds`, or a bad or looping zone. | 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/layouts/{id}/zones/{zoneId} Update a layout zone Edit one zone's geometry, attached content, or, on a themed layout, its frame, radius, or role. Send `null` for any of those to clear it. Rejects a change that would create a loop back into the same layout. Auth: Bearer token. Permission: `layout.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | | `zoneId` | path | string | yes | Zone id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Default `Zone `. | | `x` | number | no | Default 0. | | `y` | number | no | Default 0. | | `w` | number | no | Default 480. | | `h` | number | no | Default 270. | | `locked` | boolean | no | | | `contentName` | string | no | | | `content` | object \| null | no | What the zone plays. It must exist in this workspace, be usable at the layout's location, and not contain this layout. | | `ownerScope` | "workspace" \| "org_unit" \| "location" | no | | | `ownerNodeId` | string \| null | no | | | `frame` | "bare" \| "card" \| "accent" \| "glass" \| null | no | `null` removes it. | | `radius` | number \| null | no | `null` removes it. | | `role` | "logo" \| null | no | `null` removes it. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/layouts/{id}/zones/{zoneId}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Layout id. | | `data.zones` | array of LayoutZone | Every zone, in paint order. | | `data.zones[].id` | string | | | `data.zones[].name` | string | | | `data.zones[].x` | number | Left edge, in layout pixels. | | `data.zones[].y` | number | | | `data.zones[].w` | number | | | `data.zones[].h` | number | | | `data.zones[].locked` | boolean | | | `data.zones[].contentName` | string | Label of the bound content. Absent on the zone a new layout starts with. | | `data.zones[].content` | object \| null | What the zone plays: a media file, playlist, schedule, app, web link or creative (`canvas`). | | `data.zones[].zIndex` | number | | | `data.zones[].ownerScope` | "workspace" \| "org_unit" \| "location" | | | `data.zones[].ownerNodeId` | string \| null | | | `data.zones[].frame` | "bare" \| "card" \| "accent" \| "glass" | Paint style on a themed layout. | | `data.zones[].radius` | number | Corner radius in layout pixels. | | `data.zones[].role` | "logo" | `logo`: the zone shows the Brand Kit logo instead of content. | | `data.zone` | LayoutZone | A zone. Open: a whole-array write (`PUT /v1/layouts/{id}/zones` with `zones`, or `PATCH /v1/layouts/{id}`) stores each zone as sent, so other keys come back too. | | `data.zone.id` | string | | | `data.zone.name` | string | | | `data.zone.x` | number | Left edge, in layout pixels. | | `data.zone.y` | number | | | `data.zone.w` | number | | | `data.zone.h` | number | | | `data.zone.locked` | boolean | | | `data.zone.contentName` | string | Label of the bound content. Absent on the zone a new layout starts with. | | `data.zone.content` | object \| null | What the zone plays: a media file, playlist, schedule, app, web link or creative (`canvas`). | | `data.zone.zIndex` | number | | | `data.zone.ownerScope` | "workspace" \| "org_unit" \| "location" | | | `data.zone.ownerNodeId` | string \| null | | | `data.zone.frame` | "bare" \| "card" \| "accent" \| "glass" | Paint style on a themed layout. | | `data.zone.radius` | number | Corner radius in layout pixels. | | `data.zone.role` | "logo" | `logo`: the zone shows the Brand Kit logo instead of content. | 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 usable at the layout'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 layout (or zone) 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 409: `conflict`: the layout changed during the write three times running; retry. | 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: A bad frame, radius or role, content that does not exist, or zone content that plays this layout again (a loop). | 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/layouts/{id}/zones/{zoneId} Delete a layout zone Remove one zone from a layout. Auth: Bearer token. Permission: `layout.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Layout id. | | `zoneId` | path | string | yes | Zone id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/layouts/{id}/zones/{zoneId}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Layout id. | | `data.zones` | array of LayoutZone | Every zone, in paint order. | | `data.zones[].id` | string | | | `data.zones[].name` | string | | | `data.zones[].x` | number | Left edge, in layout pixels. | | `data.zones[].y` | number | | | `data.zones[].w` | number | | | `data.zones[].h` | number | | | `data.zones[].locked` | boolean | | | `data.zones[].contentName` | string | Label of the bound content. Absent on the zone a new layout starts with. | | `data.zones[].content` | object \| null | What the zone plays: a media file, playlist, schedule, app, web link or creative (`canvas`). | | `data.zones[].zIndex` | number | | | `data.zones[].ownerScope` | "workspace" \| "org_unit" \| "location" | | | `data.zones[].ownerNodeId` | string \| null | | | `data.zones[].frame` | "bare" \| "card" \| "accent" \| "glass" | Paint style on a themed layout. | | `data.zones[].radius` | number | Corner radius in layout pixels. | | `data.zones[].role` | "logo" | `logo`: the zone shows the Brand Kit logo instead of content. | 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 layout or zone 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 409: `conflict`: the layout changed during the write three times running; retry. | 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. | ## Me Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/me Describe the calling credential Returns who is calling and what they are allowed to do: whether the caller is a signed-in person, an API key, or Brix support; the location an API key is pinned to, if any; the workspace; and the caller's full set of permissions. Who is calling (API key, user, or Brix support), which workspace the call resolves to, and the permissions held. Call it first when a request is refused: `actor.nodeId` shows a key pinned to one location. Auth: Bearer token. Permission: `screen.view`. ```bash curl "https://api.brixsignage.com/v1/me" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.actor` | object \| object \| object | The credential making the call: an API key, a user session, or Brix support. | | `data.workspace` | WorkspaceRef | | | `data.workspace.id` | string | Workspace id. | | `data.workspace.name` | string \| null | Workspace name; null only if the workspace row is missing. | | `data.permissions` | "all" \| array of string | `all` for an owner-style credential, else every `resource.verb` held anywhere in the workspace. A planning hint: node-scoped checks still run on each call. | ```json { "data": { "actor": { "kind": "key", "id": "key_6f7a8b9c0d1e2f3a", "name": "Menu board sync", "nodeId": null }, "workspace": { "id": "space_1a2b3c4d5e6f7a8b", "name": "Riverside Coffee" }, "permissions": [ "screen.view", "screen.cast", "media.view", "media.create" ] } } ``` 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. | ## Media Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/media List media Return the media library: images, videos, PDFs, presentations, fonts, web links, and app-backed assets. Use limit and cursor to page through results, count=1 to include a total count, search to search by name or tag, and the comma-separated kind, state, and folderId parameters to filter. Paging is optional but recommended, since a library can hold thousands of items. Without `limit`, every visible asset comes back unordered and without `nextCursor`. With `limit`, rows are newest first. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `search` | query | string | no | Case-insensitive match on name or tags. | | `kind` | query | string | no | Comma-separated kinds, e.g. `image,video`. | | `state` | query | string | no | Comma-separated processing states. | | `folderId` | query | string | no | Comma-separated folder ids. | | `usableAt` | query | string | no | Location id: only assets that may play there (own, root library, or shared in). | | `limit` | query | integer | no | Page size (max 500). Omit to get every row. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | | `count` | query | "1" | no | With `limit`: also return `total`. | ```bash curl "https://api.brixsignage.com/v1/media" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of MediaAsset | | | `data[].id` | string | Media asset id. | | `data[].spaceId` | string | Workspace id. | | `data[].name` | string | | | `data[].kind` | string | Media kind: image, video, audio, pdf, powerpoint, web, dashboard, weather, rss, clock, qr, menu, directory, donor-wall, hall-of-fame, birthday-board, recognition, wayfinding, check-in, emergency, touch-kiosk, package, font. Free text in storage, so a very old row may carry another value. | | `data[].url` | string | Where the bytes live: a `/v1/media//file` path, an `https` URL, `r2://…`, or (for a web link) the page address. | | `data[].bytes` | integer | Stored size in bytes (0 for links and apps). | | `data[].checksum` | string \| null | | | `data[].active` | boolean | False when archived. | | `data[].folderId` | string \| null | | | `data[].tags` | any | Tags — a JSON array of strings for every row written by the API (decoded from storage; left as the raw text if the stored value is not valid JSON). | | `data[].altText` | string \| null | | | `data[].thumbnailUrl` | string \| null | | | `data[].width` | integer \| null | | | `data[].height` | integer \| null | | | `data[].durationSec` | number \| null | Video/audio length in seconds. | | `data[].pdfPageCount` | integer \| null | | | `data[].loopDurationMs` | integer \| null | Animated image loop length. | | `data[].fontFamily` | string \| null | | | `data[].fontWeights` | any \| null | Font assets: the weights in the file (decoded JSON). | | `data[].state` | string | Processing state: `ready`, `processing`, `ready_with_warnings`, `blocked`, … | | `data[].stateReason` | string \| null | | | `data[].stateCode` | string \| null | | | `data[].stateFault` | "user" \| "platform" \| null | | | `data[].stateProgress` | integer \| null | | | `data[].codec` | string \| null | | | `data[].playableRev` | integer | Bumped whenever the playable bytes change. | | `data[].loopState` | "queued" \| "ready" \| "failed" \| null | | | `data[].loopCodecs` | string \| null | | | `data[].loopRev` | integer \| null | | | `data[].loopRotation` | integer \| null | | | `data[].loopBytes` | integer \| null | | | `data[].masterKey` | string \| null | | | `data[].masterBytes` | integer \| null | | | `data[].masterProbe` | string \| null | | | `data[].rendition4k` | string \| null | | | `data[].frameLumaMean` | number \| null | | | `data[].frameLumaVariance` | number \| null | | | `data[].frameEdgeDensity` | number \| null | | | `data[].startsAt` | string \| null | Plays only from this time. | | `data[].expiresAt` | string \| null | Stops playing after this time. | | `data[].autoArchiveOnExpiry` | boolean | | | `data[].qr` | any \| null | QR overlay settings (decoded JSON). | | `data[].webConfig` | any \| null | Web link settings — refresh, zoom, header auth, … (decoded JSON). | | `data[].replayState` | "healthy" \| "replay_pending" \| "replay_failed" \| null | | | `data[].lastReplayAt` | string \| null | | | `data[].lastReplaySuccessAt` | string \| null | | | `data[].lastFailedStep` | integer \| null | | | `data[].lastFailedReason` | string \| null | | | `data[].autoCaption` | boolean | | | `data[].captionTrackKey` | string \| null | | | `data[].captionState` | "pending" \| "ready" \| "failed" \| null | | | `data[].showCaptions` | boolean | | | `data[].audioEnabled` | boolean | | | `data[].focalRegion` | string \| null | Smart-fit focal region, as stored JSON text. | | `data[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" | How the asset fills a box. | | `data[].autoSmartFit` | boolean | | | `data[].rotation` | integer | Clockwise rotation in degrees. | | `data[].originalFormat` | string \| null | | | `data[].packageEntry` | string \| null | | | `data[].packageFiles` | integer \| null | | | `data[].packageBytes` | integer \| null | | | `data[].importSourceId` | string \| null | | | `data[].nodeId` | string \| null | Home location; null = workspace library root. | | `data[].recalledAt` | string \| null | | | `data[].recalledBy` | string \| null | | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | | | `data[].usageCount` | integer | Playlists, schedules, layouts, creatives, boards and screens that use this asset. | | `data[].uploadedByName` | string \| null | Who uploaded it, from the audit log. | | `data[].uploadSource` | "ui" \| "api" \| "mcp" \| null | How it was uploaded. | | `nextCursor` | string \| null | Present when `?limit` was passed. Send it back as `?cursor=` for the next page; null on the last page. | | `total` | integer | Total matching rows, when the route computes it. | ```json { "data": [ { "id": "med_0c1d2e3f4a5b6c7d", "spaceId": "space_1a2b3c4d5e6f7a8b", "name": "lunch-special.jpg", "kind": "image", "url": "/v1/media/med_0c1d2e3f4a5b6c7d/file", "bytes": 482113, "checksum": null, "active": true, "folderId": null, "tags": [ "menu", "lunch" ], "altText": "Grilled chicken wrap with fries", "thumbnailUrl": null, "width": 1920, "height": 1080, "durationSec": null, "pdfPageCount": null, "loopDurationMs": null, "fontFamily": null, "fontWeights": null, "state": "ready", "stateReason": null, "stateCode": null, "stateFault": null, "stateProgress": null, "codec": null, "playableRev": 0, "loopState": null, "loopCodecs": null, "loopRev": null, "loopRotation": null, "loopBytes": null, "masterKey": null, "masterBytes": null, "masterProbe": null, "rendition4k": null, "frameLumaMean": null, "frameLumaVariance": null, "frameEdgeDensity": null, "startsAt": null, "expiresAt": null, "autoArchiveOnExpiry": false, "qr": null, "webConfig": null, "replayState": null, "lastReplayAt": null, "lastReplaySuccessAt": null, "lastFailedStep": null, "lastFailedReason": null, "autoCaption": false, "captionTrackKey": null, "captionState": null, "showCaptions": true, "audioEnabled": false, "focalRegion": null, "fit": "contain", "autoSmartFit": true, "rotation": 0, "originalFormat": null, "packageEntry": null, "packageFiles": null, "packageBytes": null, "importSourceId": null, "nodeId": null, "recalledAt": null, "recalledBy": null, "createdAt": "2026-09-20T10:15:00.000Z", "updatedAt": "2026-09-20T10:15:00.000Z", "deletedAt": null, "usageCount": 2, "uploadedByName": "Sam Rivera", "uploadSource": "ui" } ], "nextCursor": null, "total": 1 } ``` 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/media Create a media item Create a media asset record for a file whose bytes are already stored elsewhere, by providing name, kind, and url. This creates only the metadata record; to upload new file bytes, use POST /v1/media/upload, PUT /v1/media/upload/:uploadId, or POST /v1/media/import-url instead. **Notes.** - The 201 body is the row as written, not re-read: columns the create does not set are absent rather than null. GET returns every column. - Use this for links (web pages, streams, dashboards). To add a FILE, use POST /v1/media/upload, the multipart upload, or POST /v1/media/import-url. Auth: Bearer token. Permission: `media.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `kind` | string | yes | | | `url` | string | yes | An https URL, a /v1/ path, or (for a web link) the page address. Private and internal network addresses are refused. | | `active` | boolean | no | | | `folderId` | string \| null | no | | | `tags` | array of string | no | | | `altText` | string \| null | no | | | `thumbnailUrl` | string \| null | no | | | `width` | integer \| null | no | | | `height` | integer \| null | no | | | `durationSec` | number \| null | no | | | `startsAt` | string \| null | no | | | `expiresAt` | string \| null | no | | | `autoArchiveOnExpiry` | boolean | no | | | `qr` | object \| null | no | | | `webConfig` | object \| null | no | Web link settings (refresh, zoom, …). | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | | `audioEnabled` | boolean | no | | | `fit` | "contain" \| "cover" \| "fill" \| "blur-fill" | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/media" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Media asset id. | | `data.spaceId` | string | Workspace id. | | `data.name` | string | | | `data.kind` | string | Media kind: image, video, audio, pdf, powerpoint, web, dashboard, weather, rss, clock, qr, menu, directory, donor-wall, hall-of-fame, birthday-board, recognition, wayfinding, check-in, emergency, touch-kiosk, package, font. Free text in storage, so a very old row may carry another value. | | `data.url` | string | Where the bytes live: a `/v1/media//file` path, an `https` URL, `r2://…`, or (for a web link) the page address. | | `data.bytes` | integer | Stored size in bytes (0 for links and apps). | | `data.active` | boolean | False when archived. | | `data.tags` | any | Tags — a JSON array of strings for every row written by the API (decoded from storage; left as the raw text if the stored value is not valid JSON). | | `data.state` | string | Processing state: `ready`, `processing`, `ready_with_warnings`, `blocked`, … | | `data.autoArchiveOnExpiry` | boolean | | | `data.nodeId` | string \| null | Home location; null = workspace library root. | | `data.folderId` | string \| null | | | `data.altText` | string \| null | | | `data.thumbnailUrl` | string \| null | | | `data.width` | integer \| null | | | `data.height` | integer \| null | | | `data.durationSec` | number \| null | Video/audio length in seconds. | | `data.startsAt` | string \| null | Plays only from this time. | | `data.expiresAt` | string \| null | Stops playing after this time. | | `data.qr` | any \| null | QR overlay settings (decoded JSON). | | `data.webConfig` | any \| null | Web link settings — refresh, zoom, header auth, … (decoded JSON). | | `data.audioEnabled` | boolean | | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" | How the asset fills a box. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | null | | Response 400: `fit` is not a known value. | 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: The folder or 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: `name`, `kind` or `url` is missing, or `kind` is unknown. | 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/media/{id} Get a media asset Return one media asset with its full details, including kind, url, dimensions, duration, tags, folder, play window, and web configuration. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl "https://api.brixsignage.com/v1/media/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | MediaAssetDetail | One media asset, as GET /v1/media/{id} returns it. | | `data.id` | string | Media asset id. | | `data.spaceId` | string | Workspace id. | | `data.name` | string | | | `data.kind` | string | Media kind: image, video, audio, pdf, powerpoint, web, dashboard, weather, rss, clock, qr, menu, directory, donor-wall, hall-of-fame, birthday-board, recognition, wayfinding, check-in, emergency, touch-kiosk, package, font. Free text in storage, so a very old row may carry another value. | | `data.url` | string | Where the bytes live: a `/v1/media//file` path, an `https` URL, `r2://…`, or (for a web link) the page address. | | `data.bytes` | integer | Stored size in bytes (0 for links and apps). | | `data.checksum` | string \| null | | | `data.active` | boolean | False when archived. | | `data.folderId` | string \| null | | | `data.tags` | any | Tags — a JSON array of strings for every row written by the API (decoded from storage; left as the raw text if the stored value is not valid JSON). | | `data.altText` | string \| null | | | `data.thumbnailUrl` | string \| null | | | `data.width` | integer \| null | | | `data.height` | integer \| null | | | `data.durationSec` | number \| null | Video/audio length in seconds. | | `data.pdfPageCount` | integer \| null | | | `data.loopDurationMs` | integer \| null | Animated image loop length. | | `data.fontFamily` | string \| null | | | `data.fontWeights` | any \| null | Font assets: the weights in the file (decoded JSON). | | `data.state` | string | Processing state: `ready`, `processing`, `ready_with_warnings`, `blocked`, … | | `data.stateReason` | string \| null | | | `data.stateCode` | string \| null | | | `data.stateFault` | "user" \| "platform" \| null | | | `data.stateProgress` | integer \| null | | | `data.codec` | string \| null | | | `data.playableRev` | integer | Bumped whenever the playable bytes change. | | `data.loopState` | "queued" \| "ready" \| "failed" \| null | | | `data.loopCodecs` | string \| null | | | `data.loopRev` | integer \| null | | | `data.loopRotation` | integer \| null | | | `data.loopBytes` | integer \| null | | | `data.masterKey` | string \| null | | | `data.masterBytes` | integer \| null | | | `data.masterProbe` | string \| null | | | `data.rendition4k` | string \| null | | | `data.frameLumaMean` | number \| null | | | `data.frameLumaVariance` | number \| null | | | `data.frameEdgeDensity` | number \| null | | | `data.startsAt` | string \| null | Plays only from this time. | | `data.expiresAt` | string \| null | Stops playing after this time. | | `data.autoArchiveOnExpiry` | boolean | | | `data.qr` | any \| null | QR overlay settings (decoded JSON). | | `data.webConfig` | any \| null | Web link settings — refresh, zoom, header auth, … (decoded JSON). | | `data.replayState` | "healthy" \| "replay_pending" \| "replay_failed" \| null | | | `data.lastReplayAt` | string \| null | | | `data.lastReplaySuccessAt` | string \| null | | | `data.lastFailedStep` | integer \| null | | | `data.lastFailedReason` | string \| null | | | `data.autoCaption` | boolean | | | `data.captionTrackKey` | string \| null | | | `data.captionState` | "pending" \| "ready" \| "failed" \| null | | | `data.showCaptions` | boolean | | | `data.audioEnabled` | boolean | | | `data.focalRegion` | string \| null | Smart-fit focal region, as stored JSON text. | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" | How the asset fills a box. | | `data.autoSmartFit` | boolean | | | `data.rotation` | integer | Clockwise rotation in degrees. | | `data.originalFormat` | string \| null | | | `data.packageEntry` | string \| null | | | `data.packageFiles` | integer \| null | | | `data.packageBytes` | integer \| null | | | `data.importSourceId` | string \| null | | | `data.nodeId` | string \| null | Home location; null = workspace library root. | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.usageCount` | integer | Playlists, schedules, layouts, creatives, boards and screens that use this asset. | | `data.uploadedByName` | string \| null | Who uploaded it, from the audit log. | | `data.uploadSource` | "ui" \| "api" \| "mcp" \| null | How it was uploaded. | | `data.format` | string \| null | File format read from the stored bytes (`PNG`, `MP4 (hevc)`), only when the name has no usable extension; otherwise 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: No such asset in this workspace (or it is in the recycle bin). | 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/media/{id} Update a media asset Edit a media asset. Accepts name, altText, tags, folderId, node, play-window fields (startsAt, expiresAt, autoArchiveOnExpiry), audioEnabled, a QR code overlay, and web configuration. Changing the URL of a web-kind asset re-checks it to make sure it is safe to fetch. Send only the fields to change. `state` and `bytes` are set by processing and ignored here. **Notes.** - The response is the stored row, not the GET shape: it has no `usageCount`, `uploadedByName` or `uploadSource`. - An invalid `fit` answers 400 with only `error` (no `message`); every other validation failure is 422 `validation_error`. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `kind` | string | no | | | `url` | string | no | An https URL, a /v1/ path or a data:image URI; for a web link, the page address (private and internal network addresses are refused). | | `active` | boolean | no | False archives the asset. | | `folderId` | string \| null | no | | | `tags` | array of string | no | | | `altText` | string \| null | no | | | `thumbnailUrl` | string \| null | no | | | `width` | integer \| null | no | | | `height` | integer \| null | no | | | `durationSec` | number \| null | no | | | `startsAt` | string \| null | no | | | `expiresAt` | string \| null | no | | | `autoArchiveOnExpiry` | boolean | no | | | `qr` | object \| null | no | | | `webConfig` | object \| null | no | | | `nodeId` | string \| null | no | Move to another location (needs media.edit there). | | `audioEnabled` | boolean | no | | | `fit` | "contain" \| "cover" \| "fill" \| "blur-fill" | no | | | `baseUpdatedAt` | string | no | The `updatedAt` your edit is based on. When set and the row has moved since, the write is refused with 409 and the current row. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/media/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | MediaAssetUpdated | The stored row after a PATCH, without the list-only fields. | | `data.id` | string | Media asset id. | | `data.spaceId` | string | Workspace id. | | `data.name` | string | | | `data.kind` | string | Media kind: image, video, audio, pdf, powerpoint, web, dashboard, weather, rss, clock, qr, menu, directory, donor-wall, hall-of-fame, birthday-board, recognition, wayfinding, check-in, emergency, touch-kiosk, package, font. Free text in storage, so a very old row may carry another value. | | `data.url` | string | Where the bytes live: a `/v1/media//file` path, an `https` URL, `r2://…`, or (for a web link) the page address. | | `data.bytes` | integer | Stored size in bytes (0 for links and apps). | | `data.checksum` | string \| null | | | `data.active` | boolean | False when archived. | | `data.folderId` | string \| null | | | `data.tags` | any | Tags — a JSON array of strings for every row written by the API (decoded from storage; left as the raw text if the stored value is not valid JSON). | | `data.altText` | string \| null | | | `data.thumbnailUrl` | string \| null | | | `data.width` | integer \| null | | | `data.height` | integer \| null | | | `data.durationSec` | number \| null | Video/audio length in seconds. | | `data.pdfPageCount` | integer \| null | | | `data.loopDurationMs` | integer \| null | Animated image loop length. | | `data.fontFamily` | string \| null | | | `data.fontWeights` | any \| null | Font assets: the weights in the file (decoded JSON). | | `data.state` | string | Processing state: `ready`, `processing`, `ready_with_warnings`, `blocked`, … | | `data.stateReason` | string \| null | | | `data.stateCode` | string \| null | | | `data.stateFault` | "user" \| "platform" \| null | | | `data.stateProgress` | integer \| null | | | `data.codec` | string \| null | | | `data.playableRev` | integer | Bumped whenever the playable bytes change. | | `data.loopState` | "queued" \| "ready" \| "failed" \| null | | | `data.loopCodecs` | string \| null | | | `data.loopRev` | integer \| null | | | `data.loopRotation` | integer \| null | | | `data.loopBytes` | integer \| null | | | `data.masterKey` | string \| null | | | `data.masterBytes` | integer \| null | | | `data.masterProbe` | string \| null | | | `data.rendition4k` | string \| null | | | `data.frameLumaMean` | number \| null | | | `data.frameLumaVariance` | number \| null | | | `data.frameEdgeDensity` | number \| null | | | `data.startsAt` | string \| null | Plays only from this time. | | `data.expiresAt` | string \| null | Stops playing after this time. | | `data.autoArchiveOnExpiry` | boolean | | | `data.qr` | any \| null | QR overlay settings (decoded JSON). | | `data.webConfig` | any \| null | Web link settings — refresh, zoom, header auth, … (decoded JSON). | | `data.replayState` | "healthy" \| "replay_pending" \| "replay_failed" \| null | | | `data.lastReplayAt` | string \| null | | | `data.lastReplaySuccessAt` | string \| null | | | `data.lastFailedStep` | integer \| null | | | `data.lastFailedReason` | string \| null | | | `data.autoCaption` | boolean | | | `data.captionTrackKey` | string \| null | | | `data.captionState` | "pending" \| "ready" \| "failed" \| null | | | `data.showCaptions` | boolean | | | `data.audioEnabled` | boolean | | | `data.focalRegion` | string \| null | Smart-fit focal region, as stored JSON text. | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" | How the asset fills a box. | | `data.autoSmartFit` | boolean | | | `data.rotation` | integer | Clockwise rotation in degrees. | | `data.originalFormat` | string \| null | | | `data.packageEntry` | string \| null | | | `data.packageFiles` | integer \| null | | | `data.packageBytes` | integer \| null | | | `data.importSourceId` | string \| null | | | `data.nodeId` | string \| null | Home location; null = workspace library root. | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | Response 400: `fit` is not one of contain, cover, fill, blur-fill. | 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: Moving it to a location where you lack media.edit, or attaching an SSO connection without settings.edit. | 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 asset 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 409: `conflict`: `baseUpdatedAt` is stale; the body carries `current`. | 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, URL, folder or 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 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/media/{id} Delete a media asset Move a media asset to the recycle bin. Playlists and screens that reference it show a gap where the asset was until it is restored or replaced. Moves the asset to the recycle bin (restorable for 30 days) and removes it from every playlist. Screens casting it return to their schedule. Auth: Bearer token. Permission: `media.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | | `force` | query | "true" | no | Delete even when it is shared into other places. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/media/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.deleted` | true | | | `data.sharesRemoved` | integer | Shares removed with it. | 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 asset 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 409: `content_shared`: the item is shared into other places; `shareCount`, `crossSpaceShares`, `contentShares` say where. Repeat with `?force=true` to delete it and those shares. | 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/media/{id}/captions Get a video's captions Return a video's caption track as WebVTT. Add ?lang= to request a machine-translated version, for example ?lang=es for Spanish. This URL is stable, so it can be referenced directly by a player. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | | `lang` | query | string | no | Two- or three-letter language code: a machine translation of the track (default: the original). | ```bash curl "https://api.brixsignage.com/v1/media/{id}/captions" \ -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 media with this id, or it has no captions. | 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 reads 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. | ### POST /v1/media/{id}/captions Generate captions for a video Automatically generate a caption track for a video from its audio and attach it to the asset. To upload a caption file instead, use the caption upload operation. To show or hide an existing caption track, use the caption visibility operation. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/{id}/captions" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.ok` | true | | | `data.key` | string | Storage path of the caption track. | | `data.state` | "ready" | | Response 400: `wrong_kind`: not a video. | 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 media with this id, or no bytes. | 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 413: Over 40 MB: send the audio to /captions/transcribe instead. | 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 502: `transcription_failed`. | 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/media/{id}/captions/transcribe Generate captions from a video's audio Convert a short audio clip, extracted from a video as 16 kHz mono WAV, into a caption track. Use this instead of the standard caption-generation operation when the full video file is too large to process directly. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | Request body (`application/octet-stream`): ```bash curl -X POST "https://api.brixsignage.com/v1/media/{id}/captions/transcribe" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.ok` | true | | | `data.key` | string | Storage path of the caption track. | | `data.state` | "ready" | | Response 400: `wrong_kind`: not a video. | 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 media 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 413: Over 20 MB. | 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 body. | 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 502: `transcription_failed`. | 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/media/{id}/captions/upload Upload a caption file Attach a caption file to a video. SRT files are converted to WebVTT; WebVTT files are stored as-is. This is the alternative to automatic caption generation. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | Request body (`text/vtt`): ```bash curl -X POST "https://api.brixsignage.com/v1/media/{id}/captions/upload" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.ok` | true | | | `data.key` | string | Storage path of the caption track. | | `data.state` | "ready" | | Response 400: `wrong_kind`: not a video. | 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 media 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 413: Over 5 MB. | 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 415: Not a .vtt or .srt track. | 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 body. | 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/media/{id}/captions/visibility Show or hide a video's captions Turn a video's caption track on or off for playback. Hiding the track keeps it in storage so it can be shown again later. If no caption track exists yet, the response indicates that one must be generated first. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `show` | boolean | no | True shows captions; anything else hides them. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/{id}/captions/visibility" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.state` | "hidden" \| "ready" \| "needs_audio" | `needs_audio`: shown, but the video has no caption track yet. | Response 400: `wrong_kind`: not a video. | 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 media 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 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/media/{id}/delta Check for a media delta download Used by playback devices to check whether their cached copy of a file is current. The caller sends the checksum of its cached copy and receives either the data needed to update it, or a flag indicating no partial update is available along with a URL to download the full file. **Notes.** - Reserved: delta downloads are not offered yet, so this always answers `deltaAvailable: false`. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl "https://api.brixsignage.com/v1/media/{id}/delta" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.deltaAvailable` | false | | | `data.downloadUrl` | 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/media/{id}/file Download a media file Stream the media asset's file bytes. Supports HTTP Range requests for partial downloads. **Notes.** - HTML, SVG and script files are served as `application/octet-stream` attachments, never inline. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | | `w` | query | string | no | Images: resize to this width (a thumbnail). | | `h` | query | string | no | Images: resize to this height. | | `fit` | query | "cover" \| "contain" \| "scale-down" \| "crop" \| "pad" | no | How a resized copy fits `w` × `h` (default `cover`). | ```bash curl "https://api.brixsignage.com/v1/media/{id}/file" \ -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 media with this id, or the file is not in storage. | 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/media/{id}/focal-region Detect an image's focal region Detect the main subject of an image and cache its position as a normalized x, y, width, and height. This lets the image be cropped around its subject when shown in a differently shaped area, instead of being cropped from the center. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/{id}/focal-region" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.region` | object \| null | Fractions (0–1) of the image. Null when nothing stood out; the stored region is then unchanged. | Response 400: `wrong_kind`: not an image. | 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 media with this id, or no bytes. | 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 413: Over 32 MB. | 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/media/{id}/playback-quality Get a video's playback quality Return dropped-frame data reported by screens that have played this video, used to flag videos that are not playing smoothly. A null health value means the asset has not been measured yet, not that it is playing well. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl "https://api.brixsignage.com/v1/media/{id}/playback-quality" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.health` | object \| null | Null until a screen reports on this video. | | `data.stutterDropPct` | number | Dropped-frame percentage above which a screen counts as struggling. | | `data.minSamplesToWarn` | integer | | | `data.screens` | array of object | | | `data.screens[].screenId` | string | | | `data.screens[].worstDropPct` | number | | | `data.screens[].meanDropPct` | number | | | `data.screens[].samples` | integer | | | `data.screens[].videoWidth` | integer \| null | | | `data.screens[].videoHeight` | integer \| null | | | `data.screens[].lastSeenAt` | 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/media/{id}/poster Get a media poster image Return the poster image attached to a media asset, used as its thumbnail for videos and PDFs. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | | `w` | query | string | no | Resize to this width. | | `h` | query | string | no | Resize to this height. | | `fit` | query | "cover" \| "contain" \| "scale-down" \| "crop" \| "pad" | no | | ```bash curl "https://api.brixsignage.com/v1/media/{id}/poster" \ -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 media with this id, or it has no poster. | 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/media/{id}/poster Set or generate a media poster Attach a poster image to a media asset, used as its thumbnail. The file must be an image and no larger than 4 MB. **Notes.** - With `?generate=1` the body is `{ thumbnailUrl, generated }` and no request body is read. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | | `generate` | query | "1" | no | `1`: generate the poster from the media itself (send no body). | Request body (`image/*`): ```bash curl -X POST "https://api.brixsignage.com/v1/media/{id}/poster" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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 media 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 413: Over 4 MB. | 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 415: Not a recognised image. | 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 body. | 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/media/{id}/render Render a web link now Capture and store a screenshot of a web or link asset. If screenshot rendering is not available for this workspace, the response reports that it is not configured rather than failing. **Notes.** - Usually queued (`queued: true`); the frame is then at GET /v1/media/{id}/render-frame. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/{id}/render" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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 media 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 429: More than 60 renders 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 502: The render failed. | 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/media/{id}/render-frame Get a web link's latest render Return the most recent screenshot generated for a web or link asset by the render operation. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl "https://api.brixsignage.com/v1/media/{id}/render-frame" \ -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 media with this id, or the link has not been rendered yet (a render is then queued). | 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/media/{id}/render-source Stream source bytes for poster rendering Read-only byte stream used to generate a video's poster image, with support for partial (Range) reads. Access is scoped to a single media asset and workspace and expires after two minutes. This is not a general-purpose way to download or stream media; use the media file operation for that. Auth: Bearer token. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl "https://api.brixsignage.com/v1/media/{id}/render-source" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/media/{id}/restore Restore a deleted media item Bring back a deleted media asset. Any playlist or screen still referencing it resumes rendering it. Auth: Bearer token. Permission: `media.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/{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 media 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: `not_deleted`: it is not in the recycle bin. | 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/media/{id}/rotate Rotate a photo or video 90, 180, or 270 degrees clockwise. This rewrites the stored file rather than only changing how it is displayed, since playback devices cannot rotate video at display time. Images are rotated immediately; video rotation is queued, and the asset keeps playing its current version until the rotated file is ready. **Notes.** - Photos rotate now (`rotated`); videos are re-encoded in the background (`queued`). Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `degrees` | 90 \| 180 \| 270 | yes | Clockwise. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/{id}/rotate" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | Response 400: `degrees` is not 90, 180 or 270, or the item is not a photo or video. | 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 media with this id, or its bytes are gone. | 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: A rotation is already running. | 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 502: The image service failed. | 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 503: Rotation is not available. | 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/media/{id}/suggest Suggest alt text and tags for an image Analyze an image asset and return suggested altText and tags. This only returns suggestions; it does not save them, so use the update-media-asset operation to accept them. Only images are supported. If this feature is temporarily unavailable, the response returns empty values rather than failing. **Notes.** - Suggestions only: nothing is saved. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/{id}/suggest" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.altText` | string \| null | | | `data.tags` | array of string | | Response 400: `wrong_kind`: not an image. | 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 media with this id, or no bytes. | 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 413: Over 32 MB. | 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/media/{id}/usage Show where a media asset is used Return where a media asset is used, across playlists, creatives, and layouts, along with when it was last played. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | ```bash curl "https://api.brixsignage.com/v1/media/{id}/usage" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | MediaUsage | | | `data.containers` | array of object | | | `data.containers[].id` | string | | | `data.containers[].name` | string | | | `data.containers[].kind` | "playlist" \| "schedule" \| "layout" \| "creative" \| "signage" | `signage` = a signage template (board). | | `data.containers[].itemCount` | integer | Playlists only: total items in the playlist. | | `data.screens` | array of object | Active screens showing it now, directly or through one of the containers. | | `data.screens[].id` | string | | | `data.screens[].name` | string | | | `data.screens[].location` | string \| null | | | `data.screens[].status` | string | `online`, `offline` or `pairing`. | | `data.lastPlayedAt` | string \| null | Last completed play reported by a screen; null if never played. | 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 asset 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. | ### PUT /v1/media/{id}/web-secrets Set a web link's secrets Store credentials, such as authentication tokens or login passwords, for a web asset. Values are encrypted at rest and write-only: send an object mapping reference names to values, where a null value deletes that reference. Only the reference names, never the values, can be read back later. This merges with any existing secrets rather than replacing them. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media asset id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `secrets` | object | yes | Secret name → value (at most 50, values ≤ 8192 characters). `null` or `""` removes one; names not sent are kept. | ```bash curl -X PUT "https://api.brixsignage.com/v1/media/{id}/web-secrets" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.ok` | true | | | `data.refs` | array of string | The secret NAMES now stored. Values are never returned. | Response 400: Too many secrets, or a name or value too long. | 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 media 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 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/media/backfill-avif Convert AVIF images Convert this workspace's existing AVIF image assets to JPEG. Auth: Bearer token. Permission: `media.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | | | `after` | string | no | `nextAfter` of the previous run. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/backfill-avif" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.scanned` | integer | | | `data.transcoded` | integer | | | `data.rows` | array of object | | | `data.rows[].id` | string | | | `data.rows[].name` | string \| null | | | `data.rows[].result` | "source_gone" \| "undecodable" \| "service_error" \| "binding_absent" \| "transcoded" \| "already_done" \| "not_avif" | | | `data.nextAfter` | string \| null | Pass as `after` to continue; null when done. | 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 429: More than 10 runs 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 503: `binding_absent`: image conversion is not available. | 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/media/backfill-codec-warnings Re-check codec warnings Recompute codec-compatibility warnings for this workspace's video assets under the current compatibility rules, and clear any outdated re-export warnings. Safe to run more than once. Auth: Bearer token. Permission: `media.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/media/backfill-codec-warnings" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.scanned` | integer | | | `data.cleared` | 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 429: More than 10 runs 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. | ### POST /v1/media/backfill-heic Convert HEIC images Convert this workspace's existing HEIC image assets to JPEG so they display correctly on devices that cannot decode HEIC. Safe to run more than once, and applies only to your own workspace. Returns a 503 error if the conversion feature is temporarily unavailable. Auth: Bearer token. Permission: `media.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | | | `after` | string | no | `nextAfter` of the previous run. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/backfill-heic" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.scanned` | integer | | | `data.transcoded` | integer | | | `data.rows` | array of object | | | `data.rows[].id` | string | | | `data.rows[].name` | string \| null | | | `data.rows[].result` | "source_gone" \| "undecodable" \| "service_error" \| "binding_absent" \| "transcoded" \| "already_done" \| "not_heic" | | | `data.nextAfter` | 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 429: More than 10 runs 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 503: `binding_absent`: HEIC conversion is not available. | 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/media/backfill-video-metadata Fill in missing video details Scan this workspace's existing video files to fill in missing width, height, and duration values, and correct codec information that was guessed from the filename. Also updates the codec-compatibility warning where applicable. Auth: Bearer token. Permission: `media.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | Most videos to read this run. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/backfill-video-metadata" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.scanned` | integer | | | `data.updated` | integer | | | `data.reclassified` | 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 429: More than 10 runs 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. | ### POST /v1/media/import-url Import media from a URL Import a file from a remote URL into the media library. The API fetches the file server-side, checking that the URL does not point to a disallowed destination, stores its bytes, and creates the media asset record. This is the same path used by the upload_media_from_url tool. Fetches a public http(s) URL into the library. SVG is refused. **Notes.** - The response is the row as built for insert, not the GET shape: `tags` is a JSON string (not an array), `sourceUrl` is present but not stored, and most columns (state, fit, width, …) are absent. Read GET /v1/media/{id} for the settled asset. Auth: Bearer token. Permission: `media.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | Public http(s) address of the file. | | `name` | string | no | Display name; defaults to the file name in the URL. | | `folderId` | string | no | | | `nodeId` | string | no | Home location; defaults to your own. | | `tags` | array of string | no | | | `autoCaption` | boolean | no | Videos: generate captions. | | `autoSmartFit` | boolean | no | Images: detect a focal region (default true). | ```bash curl -X POST "https://api.brixsignage.com/v1/media/import-url" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ImportedMedia | The row created by an import, before processing settles. | | `data.id` | string | Media asset id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.kind` | string | | | `data.url` | string | `/v1/media//file`. | | `data.bytes` | integer | | | `data.checksum` | string \| null | | | `data.active` | true | | | `data.folderId` | string \| null | | | `data.nodeId` | string \| null | | | `data.tags` | string | JSON-encoded array of strings — NOT decoded here, unlike GET /v1/media. | | `data.sourceUrl` | string | The URL that was imported. Not stored; returned by this route only. | | `data.autoCaption` | boolean | | | `data.captionState` | "pending" \| null | | | `data.autoSmartFit` | boolean | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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 413: `too_large`: the file is over the size limit for its kind. | 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 415: `unsupported_type`: SVG or an unknown content type. | 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: Missing or non-http URL, a blocked address (`url_blocked`), or an unknown folder or 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 502: `fetch_failed` / `source_status`: the source did not answer with the file. | 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/media/moderate Check text for unsafe content Run a safety classifier over a piece of user-submitted text, such as banner copy or a message board post, and return its verdict. This operation only classifies the text; it does not store it. Auth: Bearer token. Permission: `media.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `text` | string | yes | | ```bash curl -X POST "https://api.brixsignage.com/v1/media/moderate" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.flagged` | boolean | | | `data.reason` | string | Present when flagged. | | `data.checked` | boolean | False when the check could not run (the text is then not flagged). | Response 400: `text` 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 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/media/stats Get media library statistics Return summary counts for the media library in one response: total assets, unused assets, and a count per folder. Use this instead of listing all media when only summary counts are needed. Auth: Bearer token. Permission: `media.view`. ```bash curl "https://api.brixsignage.com/v1/media/stats" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.total` | integer | | | `data.unused` | integer | Items used nowhere. | | `data.folders` | array of object | Items per folder; `folderId` null = not in a folder. | | `data.folders[].folderId` | string \| null | | | `data.folders[].count` | 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/media/upload Upload a media file Upload a file in a single request using multipart form data. Accepts form fields file (required), and optional name, nodeId, and folderId. This stores the file and creates the media asset record in one step, and is safe to retry with the same Idempotency-Key header. For large files or programmatic clients, minting an upload URL and then uploading with PUT is usually simpler. **Notes.** - Form fields other than `file` are strings, as multipart sends them. - For files over ~100 MB use the multipart upload. Auth: Bearer token. Permission: `media.create`. Request body (`multipart/form-data`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `file` | string (binary) | yes | The file. SVG is refused. | | `name` | string | no | Display name; default the file name. | | `nodeId` | string | no | Home location; default the caller's own. | | `folderId` | string | no | | | `alt` | string | no | Alt text. | | `decorative` | "1" | no | `1`: the image is decorative (empty alt text). | | `autoCaption` | "1" | no | Videos: `1` generates captions. | | `autoSmartFit` | "0" \| "1" | no | Images: `0` skips focal-region detection (default on). | | `durationSec` | string | no | Videos: length in seconds, when the client measured it. | | `loopDurationMs` | string | no | Animated images: loop length. | | `width` | string | no | | | `height` | string | no | | | `codec` | string | no | | | `codecString` | string | no | | | `fps` | string | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/media/upload" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | UploadedMedia | The media row an upload creates, before processing settles. | | `data.id` | string | Media asset id. | | `data.spaceId` | string | Workspace id. | | `data.nodeId` | string \| null | Home location; null = workspace library root. | | `data.folderId` | string \| null | | | `data.name` | string | | | `data.kind` | string | Media kind: image, video, audio, pdf, powerpoint, web, dashboard, weather, rss, clock, qr, menu, directory, donor-wall, hall-of-fame, birthday-board, recognition, wayfinding, check-in, emergency, touch-kiosk, package, font. Free text in storage, so a very old row may carry another value. | | `data.url` | string | Where the bytes live: a `/v1/media//file` path, an `https` URL, `r2://…`, or (for a web link) the page address. | | `data.bytes` | integer | Stored size in bytes (0 for links and apps). | | `data.checksum` | string | SHA-256 of the bytes. | | `data.active` | boolean | False when archived. | | `data.altText` | string \| null | | | `data.width` | integer \| null | | | `data.height` | integer \| null | | | `data.durationSec` | number \| null | Video/audio length in seconds. | | `data.loopDurationMs` | integer \| null | Animated image loop length. | | `data.fontFamily` | string \| null | | | `data.fontWeights` | array of integer \| null | Font files: the weights in the file. | | `data.state` | "ready" \| "failed" \| "processing" \| "ready_with_warnings" \| "needs_action" | | | `data.stateReason` | string \| null | | | `data.stateCode` | string \| null | | | `data.stateFault` | "user" \| "platform" \| null | | | `data.codec` | string \| null | | | `data.frameLumaMean` | number \| null | | | `data.frameLumaVariance` | number \| null | | | `data.frameEdgeDensity` | number \| null | | | `data.autoCaption` | boolean | | | `data.captionState` | "pending" \| null | | | `data.autoSmartFit` | boolean | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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 folder or 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 413: The file is over the limit for its kind (images 250 MB, video 2 GB, documents 100 MB, fonts 10 MB). | 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 415: SVG, or a type the library does not take. | 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: `file` 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. | ### PUT /v1/media/upload/{uploadId} Upload bytes to a minted upload URL Upload the raw file bytes for a previously created upload slot. Send the file as the request body and set the Content-Type header to match the file's type. After uploading, finalize the upload to create the media asset record. **Notes.** - This stores the bytes for a bulk import; the import itself creates the media rows. Auth: Bearer token. Permission: `media.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `uploadId` | path | string | yes | An `upl_…` id from POST /v1/import/upload-urls. | Request body (`application/octet-stream`): ```bash curl -X PUT "https://api.brixsignage.com/v1/media/upload/{uploadId}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.uploadId` | string | | | `data.bytes` | 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: No mint with this id 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 413: Over 500 MB. | 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 415: SVG. | 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 body or a malformed 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 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/media/upload/multipart/{id} Get a multipart upload's progress Return the state of an in-progress multipart upload, including the file's name, size, kind, part size, and every part already received, each with its part number, ETag, and size. Use this to resume an interrupted upload from the last completed part instead of starting over. Returns 404 if the upload belongs to another workspace, has already finished, or has expired. Auth: Bearer token. Permission: `media.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | The upload's media id, from `POST /v1/media/upload/multipart/create`. | ```bash curl "https://api.brixsignage.com/v1/media/upload/multipart/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.name` | string | | | `data.contentType` | string | | | `data.kind` | string | | | `data.sizeBytes` | integer | | | `data.partSize` | integer | | | `data.masterOf` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.parts` | array of object | Parts already stored: resume from the first missing one. | | `data.parts[].partNumber` | integer | | | `data.parts[].etag` | string | | | `data.parts[].size` | 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: No such upload. | 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/media/upload/multipart/{id}/abort Cancel a multipart upload Cancel an in-progress multipart upload and discard any parts already received. Safe to call more than once. **Notes.** - Answers `aborted: true` also when there was nothing to cancel. Auth: Bearer token. Permission: `media.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | The upload's media id, from `POST /v1/media/upload/multipart/create`. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/upload/multipart/{id}/abort" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.aborted` | 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. | ### POST /v1/media/upload/multipart/{id}/complete Complete a multipart upload Finalize a multipart upload by sending the list of uploaded parts, each with partNumber and etag. If the list is left empty, the server completes the upload using the parts it already has on record, so a client resuming an interrupted upload does not need to know every part's ETag. This creates the media asset record; video files are automatically queued for processing. **Notes.** - For a master upload (`masterOf`) the body is `{ id, masterKey, masterBytes }`; otherwise it is the new media row. Auth: Bearer token. Permission: `media.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | The upload's media id, from `POST /v1/media/upload/multipart/create`. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `parts` | array of object | yes | | | `parts[].partNumber` | integer | yes | | | `parts[].etag` | string | yes | | ```bash curl -X POST "https://api.brixsignage.com/v1/media/upload/multipart/{id}/complete" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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 upload. | 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: A part is missing or does not match. | 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: No parts. | 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. | ### PUT /v1/media/upload/multipart/{id}/part/{partNumber} Upload one part of a multipart upload Upload one part of a file for an in-progress multipart upload, identified by its part number. Returns the part's ETag, which is needed to complete the upload. Returns 404 if the upload belongs to another workspace. Auth: Bearer token. Permission: `media.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | The upload's media id, from `POST /v1/media/upload/multipart/create`. | | `partNumber` | path | string | yes | 1-based part number. | Request body (`application/octet-stream`): ```bash curl -X PUT "https://api.brixsignage.com/v1/media/upload/multipart/{id}/part/{partNumber}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.partNumber` | integer | | | `data.etag` | string | Send it back in `complete`. | 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 upload. | 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 413: The part is larger than `partSize`. | 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: Bad part number or empty part. | 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/media/upload/multipart/create Start a multipart upload Begin a multipart upload for large files, such as multi-gigabyte videos, that are too large for a single request. Validates the file kind and checks its size against the limit allowed for that kind. Returns an upload id and the part size to use for subsequent part uploads. Auth: Bearer token. Permission: `media.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `contentType` | string | no | The file's media type; decides the kind. | | `sizeBytes` | integer | yes | | | `folderId` | string \| null | no | | | `nodeId` | string \| null | no | | | `alt` | string \| null | no | | | `decorative` | boolean | no | | | `loopDurationMs` | number \| null | no | | | `durationSec` | number \| null | no | | | `width` | integer \| null | no | | | `height` | integer \| null | no | | | `codec` | string \| null | no | | | `codecString` | string \| null | no | | | `fps` | number \| null | no | | | `masterOf` | string \| null | no | An existing video's id: upload a high-resolution master for it instead of a new asset. | | `importSourceId` | string \| null | no | Your own id for the file. If a live asset already has it, the answer is 200 with `exists: true`. | ```bash curl -X POST "https://api.brixsignage.com/v1/media/upload/multipart/create" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success: `importSourceId` matched a live asset: `{ id, exists: true }`, nothing started. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object \| object | | 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 413: `sizeBytes` is over the limit for the kind. | 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 415: SVG. | 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 file type, or `sizeBytes` 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. | ### POST /v1/media/upload/package Upload an HTML5 package Upload a zipped HTML5 package as a media asset, using multipart form data with fields file (required), and optional name, nodeId, and folderId. The archive is validated, expanded into storage, and its entry page is identified automatically. Auth: Bearer token. Permission: `media.create`. Request body (`multipart/form-data`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `file` | string (binary) | yes | The .zip. It must hold an entry page (`index.html`). | | `name` | string | no | Display name; default the file name. | | `nodeId` | string | no | Home location; default the caller's own. | | `folderId` | string | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/media/upload/package" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Media asset id. | | `data.spaceId` | string | Workspace id. | | `data.nodeId` | string \| null | Home location; null = workspace library root. | | `data.folderId` | string \| null | | | `data.name` | string | | | `data.kind` | "package" | | | `data.url` | string | Where the bytes live: a `/v1/media//file` path, an `https` URL, `r2://…`, or (for a web link) the page address. | | `data.bytes` | integer | Stored size in bytes (0 for links and apps). | | `data.checksum` | string | SHA-256 of the .zip. | | `data.active` | boolean | False when archived. | | `data.packageEntry` | string | The entry page found in the archive, e.g. `index.html`. | | `data.packageFiles` | integer | Files in the archive. | | `data.packageBytes` | integer | Expanded size of the archive, in bytes. | | `data.state` | "ready" | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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 folder or 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 413: The .zip, or its expanded contents, is over the limit. | 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: `file` is missing, the .zip cannot be read, or a file in it is refused (unsafe path, type not allowed, no entry page). | 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. | ## Media folders Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/media-folders List media folders Return the media library's folder tree for this workspace, including each folder's id, name, parent folder, and location. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Page size. Omit to get every row; pass it to page by `cursor`. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | | `count` | query | "1" | no | With `limit`: also return `total`. | | `usableAt` | query | string | no | Location id: only folders usable at that location. | ```bash curl "https://api.brixsignage.com/v1/media-folders" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of MediaFolder | | | `data[].id` | string | Media folder id. | | `data[].spaceId` | string | | | `data[].parentId` | string \| null | Parent folder; null = top level. | | `data[].name` | string | | | `data[].nodeId` | string \| null | Home location; null = workspace root. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | | | `nextCursor` | string \| null | Present when `?limit` was passed. Send it back as `?cursor=` for the next page; null on the last page. | | `total` | integer | Total matching rows, when the route computes it. | 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/media-folders Create a media folder Create a folder in the media library. Requires name; parentId and nodeId are optional. Omitting parentId places the folder at the root. **Notes.** - The 201 body is the row as written, not re-read: columns the create does not set are absent rather than null. GET returns every column. Auth: Bearer token. Permission: `media.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `parentId` | string \| null | no | | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | ```bash curl -X POST "https://api.brixsignage.com/v1/media-folders" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Media folder id. | | `data.spaceId` | string | | | `data.parentId` | string \| null | | | `data.name` | string | | | `data.nodeId` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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 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 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/media-folders/{id} Get a media folder Return one media folder. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media folder id. | ```bash curl "https://api.brixsignage.com/v1/media-folders/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | MediaFolder | A folder in the media library. | | `data.id` | string | Media folder id. | | `data.spaceId` | string | | | `data.parentId` | string \| null | Parent folder; null = top level. | | `data.name` | string | | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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. | ### PATCH /v1/media-folders/{id} Update a media folder Rename a media folder or move it to a different parent folder or location. Accepts optional name, parentId, and nodeId. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media folder id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `parentId` | string \| null | no | | | `nodeId` | string \| null | no | | | `baseUpdatedAt` | string | no | The `updatedAt` your edit is based on; 409 with the current row if it moved. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/media-folders/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | MediaFolder | A folder in the media library. | | `data.id` | string | Media folder id. | | `data.spaceId` | string | | | `data.parentId` | string \| null | Parent folder; null = top level. | | `data.name` | string | | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | 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 404: No folder 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: `conflict`: changed since `baseUpdatedAt`; the body carries `current`. | 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/media-folders/{id} Delete a media folder Move a media folder to the recycle bin; it can be restored later. Assets inside the folder are not moved or deleted and keep their existing folder reference. Auth: Bearer token. Permission: `media.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media folder id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/media-folders/{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 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/media-folders/{id}/restore Restore a deleted media folder Bring back a deleted media folder so it reappears in the folder tree. Assets inside it keep whatever delete state they already had. Auth: Bearer token. Permission: `media.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Media folder id. | ```bash curl -X POST "https://api.brixsignage.com/v1/media-folders/{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 folder 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: `not_deleted`. | 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. | ## Media sync Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/media-sync List synced folders Return the workspace's synced folders, including the remote path, the destination folder in Brix, the connected account, sync status, the last error, the last check time, and the number of files synced. Only folders connected at locations where the caller holds the integration view permission are returned. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/media-sync" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | string | | | `data[].provider` | string | `onedrive` or `sharepoint`. | | `data[].remotePath` | string | | | `data[].folderId` | string | The library folder the files land in. | | `data[].folderName` | string \| null | | | `data[].accountLabel` | string \| null | | | `data[].accountConnected` | boolean | False when the connected account was removed: syncing stops until it is connected again. | | `data[].status` | "ok" \| "error" \| "paused" | | | `data[].lastError` | string \| null | | | `data[].lastSyncAt` | string \| null | | | `data[].itemsSynced` | integer | | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | 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. | ### DELETE /v1/media-sync/{id} Stop syncing a folder Stop following a remote folder. The sync source is removed, but files already imported are not affected. This action is recorded in the activity log. **Notes.** - The files already synced stay in the library. Auth: Bearer token. Permission: `integration.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Synced folder id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/media-sync/{id}" \ -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 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/media-sync/{id}/sync Sync a folder now Trigger an immediate sync of one connected folder, bounded to a fixed number of files for the run, and return a summary of what happened. This action is recorded in the activity log. **Notes.** - A failed run still answers 200: read `error`. Auth: Bearer token. Permission: `integration.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Synced folder id. | ```bash curl -X POST "https://api.brixsignage.com/v1/media-sync/{id}/sync" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.imported` | integer | | | `data.updated` | integer | | | `data.removed` | integer | | | `data.skipped` | integer | | | `data.failed` | integer | | | `data.caughtUp` | boolean | False when more changes remain for the next run. | | `data.error` | string \| null | Why the run stopped, in words; null when it did not. | 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 synced folder 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 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. | ## Org nodes Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/org-nodes List locations Returns the workspace's location tree, for example districts, regions, sites, and departments, with each location's path and member count. Every location in the workspace tree the caller may see (not paginated). Build the tree from `parentId`. Auth: Bearer token. Permission: `org-unit.view`. ```bash curl "https://api.brixsignage.com/v1/org-nodes" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of Location | | | `data[].id` | string | Location (org node) id. | | `data[].name` | string | | | `data[].parentId` | string \| null | Parent location; null for the workspace root. | | `data[].externalId` | string \| null | Your own location code (store number, region code). Unique in the workspace. | | `data[].timezone` | string \| null | IANA zone screens here inherit, e.g. `America/Chicago`. | | `data[].tier` | integer | Depth: 1 = the workspace root. | | `data[].isSpace` | boolean | True for a workspace (Space) boundary: the root, or a franchise child workspace. | | `data[].screenCount` | integer | Screens placed directly at this location (not its children). | | `data[].members` | array of object | People granted a role at this location. | | `data[].members[].userId` | string | | | `data[].members[].userName` | string | | | `data[].members[].roleId` | string | | | `data[].members[].roleName` | string | | | `data[].members[].roleColor` | string \| null | | | `data[].members[].source` | string \| null | `sso:` when an identity provider granted this; null when granted in Brix. | | `data[].featureOverrides` | any | Owner-set feature overrides, as stored (normally an array). | | `data[].approval` | ApprovalPolicy | The location's content-approval policy, as stored. New locations inherit `{required:false, inheritFromParent:true, …}`. | | `data[].approval.required` | boolean | | | `data[].approval.inheritFromParent` | boolean | | | `data[].approval.escalateUpTiers` | boolean | | | `data[].approval.allowSharedExemptions` | boolean | | | `data[].approval.approvers` | array of string | User ids named as approvers. | | `data[].billing` | any | Owner-set billing metadata as stored, or null. | | `data[].prefs` | LocationPrefs | Inheritable per-location settings (nearest ancestor wins). | | `data[].prefs.location` | object \| null | Physical place screens here inherit (drives weather and other location-aware apps). | | `data[].prefs.language` | string \| null | BCP 47 tag screens here inherit, e.g. `de-DE`. | | `data[].prefs.defaultContent` | object \| null | What screens here play when they have no content of their own. | ```json { "data": [ { "id": "on_4d5e6f7a8b9c0d1e", "name": "Chicago Loop", "parentId": "space_1a2b3c4d5e6f7a8b", "externalId": "STORE-0142", "timezone": "America/Chicago", "tier": 2, "isSpace": false, "screenCount": 6, "members": [ { "userId": "usr_5e6f7a8b9c0d1e2f", "userName": "Sam Rivera", "roleId": "role_0a1b2c3d4e5f6a7b", "roleName": "Store manager", "roleColor": null, "source": null } ], "featureOverrides": [], "approval": { "required": false, "inheritFromParent": true, "escalateUpTiers": false, "allowSharedExemptions": false, "approvers": [] }, "billing": null, "prefs": { "location": { "label": "Chicago Loop", "lat": 41.8837, "lng": -87.6289 }, "language": null, "defaultContent": 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/org-nodes Create a location Creates a new location under a parent location. Provide `name`, `parentId`, and an optional `kind`. To create many locations at once, use the bulk-create operation instead. Auth: Bearer token. Permission: `org-unit.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Location name. | | `parentId` | string | no | Parent location; omit to place it directly under the workspace root. | | `externalId` | string | no | Your own location code; must be unique in the workspace. | | `timezone` | string | no | IANA zone, e.g. `Europe/London`. | | `approval` | ApprovalPolicy | no | The location's content-approval policy, as stored. New locations inherit `{required:false, inheritFromParent:true, …}`. | | `approval.required` | boolean | no | | | `approval.inheritFromParent` | boolean | no | | | `approval.escalateUpTiers` | boolean | no | | | `approval.allowSharedExemptions` | boolean | no | | | `approval.approvers` | array of string | no | User ids named as approvers. | | `isSpace` | boolean | no | Owner-only: create a child workspace (franchise). | | `billingOwnerNodeId` | string | no | With `isSpace`: the ancestor workspace that pays. | | `screenLimit` | integer | no | Owner-only, advisory. | | `tier` | integer | no | Owner-only. Defaults to 2. | | `billing` | any | no | Owner-only. | | `featureOverrides` | any | no | Owner-only. | ```bash curl -X POST "https://api.brixsignage.com/v1/org-nodes" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Chicago Loop","parentId":"on_0a1b2c3d4e5f6a7b","externalId":"STORE-0142","timezone":"America/Chicago"}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Location | A location in the workspace tree (an org node). | | `data.id` | string | Location (org node) id. | | `data.name` | string | | | `data.parentId` | string \| null | Parent location; null for the workspace root. | | `data.externalId` | string \| null | Your own location code (store number, region code). Unique in the workspace. | | `data.timezone` | string \| null | IANA zone screens here inherit, e.g. `America/Chicago`. | | `data.tier` | integer | Depth: 1 = the workspace root. | | `data.isSpace` | boolean | True for a workspace (Space) boundary: the root, or a franchise child workspace. | | `data.screenCount` | integer | Screens placed directly at this location (not its children). | | `data.members` | array of object | People granted a role at this location. | | `data.members[].userId` | string | | | `data.members[].userName` | string | | | `data.members[].roleId` | string | | | `data.members[].roleName` | string | | | `data.members[].roleColor` | string \| null | | | `data.members[].source` | string \| null | `sso:` when an identity provider granted this; null when granted in Brix. | | `data.featureOverrides` | any | Owner-set feature overrides, as stored (normally an array). | | `data.approval` | ApprovalPolicy | The location's content-approval policy, as stored. New locations inherit `{required:false, inheritFromParent:true, …}`. | | `data.approval.required` | boolean | | | `data.approval.inheritFromParent` | boolean | | | `data.approval.escalateUpTiers` | boolean | | | `data.approval.allowSharedExemptions` | boolean | | | `data.approval.approvers` | array of string | User ids named as approvers. | | `data.billing` | any | Owner-set billing metadata as stored, or null. | | `data.prefs` | LocationPrefs | Inheritable per-location settings (nearest ancestor wins). | | `data.prefs.location` | object \| null | Physical place screens here inherit (drives weather and other location-aware apps). | | `data.prefs.language` | string \| null | BCP 47 tag screens here inherit, e.g. `de-DE`. | | `data.prefs.defaultContent` | object \| null | What screens here play when they have no content of their own. | 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: An owner-only field was set (`isSpace`, `billing`, `featureOverrides`, `tier`, `screenLimit`, or a non-inheriting approval policy). | 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: `location_id_taken`: another location already uses this `externalId`. | 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` missing, invalid `externalId`, or `parentId` not found. | 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/org-nodes/{id} Get a location Returns one location, including its path, parent, and settings. Auth: Bearer token. Permission: `org-unit.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Location (org node) id. | ```bash curl "https://api.brixsignage.com/v1/org-nodes/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Location | A location in the workspace tree (an org node). | | `data.id` | string | Location (org node) id. | | `data.name` | string | | | `data.parentId` | string \| null | Parent location; null for the workspace root. | | `data.externalId` | string \| null | Your own location code (store number, region code). Unique in the workspace. | | `data.timezone` | string \| null | IANA zone screens here inherit, e.g. `America/Chicago`. | | `data.tier` | integer | Depth: 1 = the workspace root. | | `data.isSpace` | boolean | True for a workspace (Space) boundary: the root, or a franchise child workspace. | | `data.screenCount` | integer | Screens placed directly at this location (not its children). | | `data.members` | array of object | People granted a role at this location. | | `data.members[].userId` | string | | | `data.members[].userName` | string | | | `data.members[].roleId` | string | | | `data.members[].roleName` | string | | | `data.members[].roleColor` | string \| null | | | `data.members[].source` | string \| null | `sso:` when an identity provider granted this; null when granted in Brix. | | `data.featureOverrides` | any | Owner-set feature overrides, as stored (normally an array). | | `data.approval` | ApprovalPolicy | The location's content-approval policy, as stored. New locations inherit `{required:false, inheritFromParent:true, …}`. | | `data.approval.required` | boolean | | | `data.approval.inheritFromParent` | boolean | | | `data.approval.escalateUpTiers` | boolean | | | `data.approval.allowSharedExemptions` | boolean | | | `data.approval.approvers` | array of string | User ids named as approvers. | | `data.billing` | any | Owner-set billing metadata as stored, or null. | | `data.prefs` | LocationPrefs | Inheritable per-location settings (nearest ancestor wins). | | `data.prefs.location` | object \| null | Physical place screens here inherit (drives weather and other location-aware apps). | | `data.prefs.language` | string \| null | BCP 47 tag screens here inherit, e.g. `de-DE`. | | `data.prefs.defaultContent` | object \| null | What screens here play when they have no content of their own. | 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 location 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/org-nodes/{id} Update a location Updates a location's name, parent location, approval configuration, or preferences. Moving a location to a new parent updates the path of every location beneath it. Rename, re-code, move, or change inheritable settings. Only the fields sent change. Auth: Bearer token. Permission: `org-unit.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Location (org node) id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `parentId` | string \| null | no | Move under another location (null = the workspace root). | | `externalId` | string \| null | no | | | `timezone` | string \| null | no | IANA zone, or null/empty to inherit. | | `tier` | integer | no | | | `prefs` | object | no | Per-key: a value sets it, null clears it back to inherit, absent keeps it. | | `prefs.location` | object \| null | no | | | `prefs.language` | string \| null | no | BCP 47 tag, or null to inherit. | | `prefs.defaultContent` | object \| null | no | | | `approval` | ApprovalPolicy | no | The location's content-approval policy, as stored. New locations inherit `{required:false, inheritFromParent:true, …}`. | | `approval.required` | boolean | no | | | `approval.inheritFromParent` | boolean | no | | | `approval.escalateUpTiers` | boolean | no | | | `approval.allowSharedExemptions` | boolean | no | | | `approval.approvers` | array of string | no | User ids named as approvers. | | `approvalBase` | ApprovalPolicy | no | The location's content-approval policy, as stored. New locations inherit `{required:false, inheritFromParent:true, …}`. | | `approvalBase.required` | boolean | no | | | `approvalBase.inheritFromParent` | boolean | no | | | `approvalBase.escalateUpTiers` | boolean | no | | | `approvalBase.allowSharedExemptions` | boolean | no | | | `approvalBase.approvers` | array of string | no | User ids named as approvers. | | `billing` | any | no | Owner-only. | | `featureOverrides` | any | no | Owner-only. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/org-nodes/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Location | A location in the workspace tree (an org node). | | `data.id` | string | Location (org node) id. | | `data.name` | string | | | `data.parentId` | string \| null | Parent location; null for the workspace root. | | `data.externalId` | string \| null | Your own location code (store number, region code). Unique in the workspace. | | `data.timezone` | string \| null | IANA zone screens here inherit, e.g. `America/Chicago`. | | `data.tier` | integer | Depth: 1 = the workspace root. | | `data.isSpace` | boolean | True for a workspace (Space) boundary: the root, or a franchise child workspace. | | `data.screenCount` | integer | Screens placed directly at this location (not its children). | | `data.members` | array of object | People granted a role at this location. | | `data.members[].userId` | string | | | `data.members[].userName` | string | | | `data.members[].roleId` | string | | | `data.members[].roleName` | string | | | `data.members[].roleColor` | string \| null | | | `data.members[].source` | string \| null | `sso:` when an identity provider granted this; null when granted in Brix. | | `data.featureOverrides` | any | Owner-set feature overrides, as stored (normally an array). | | `data.approval` | ApprovalPolicy | The location's content-approval policy, as stored. New locations inherit `{required:false, inheritFromParent:true, …}`. | | `data.approval.required` | boolean | | | `data.approval.inheritFromParent` | boolean | | | `data.approval.escalateUpTiers` | boolean | | | `data.approval.allowSharedExemptions` | boolean | | | `data.approval.approvers` | array of string | User ids named as approvers. | | `data.billing` | any | Owner-set billing metadata as stored, or null. | | `data.prefs` | LocationPrefs | Inheritable per-location settings (nearest ancestor wins). | | `data.prefs.location` | object \| null | Physical place screens here inherit (drives weather and other location-aware apps). | | `data.prefs.language` | string \| null | BCP 47 tag screens here inherit, e.g. `de-DE`. | | `data.prefs.defaultContent` | object \| null | What screens here play when they have no content of their own. | 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: Owner-only field, or a move you lack permission for at the destination. | 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 location 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 409: `externalId` already used, or `approvalBase` no longer matches. | 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 field (a malformed `prefs`, moving a location under itself, unplayable default 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. | ### DELETE /v1/org-nodes/{id} Delete location Soft-deletes a location. Everything at that location, including screens, content and folders, is transferred to its parent location first, so nothing is left orphaned. Role assignments at the location are removed, so the same rule as removing a role assignment applies: only an account owner can remove an owner's assignment. The root location and any location with active child locations cannot be deleted. **Notes.** - Screens and content move to the parent. The people placed here lose their role at this location. Auth: Bearer token. Permission: `org-unit.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Location (org node) id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/org-nodes/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.deleted` | true | | | `data.transferredTo` | string | The parent location that received this location's screens and content. | 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: You do not hold `org-unit.delete` there, or a person placed there outranks you (`owner_required`, `outranked`). | 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 location 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 409: `protected` (the workspace root), `has_children` (move or delete the child locations first), or `last_owner`. | 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 502: A franchise workspace's own subscription could not be ended; nothing was deleted. | 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/org-nodes/{id}/members List location members Returns the people directly assigned a role at this specific location, including the id of each role assignment. This id is needed to remove a membership, and is not included in the general roster listing. **Notes.** - Only the roles granted at this location, not those inherited from a parent. Auth: Bearer token. Permission: `user.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Location (org node) id. | ```bash curl "https://api.brixsignage.com/v1/org-nodes/{id}/members" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | string | Membership id. | | `data[].userId` | string | | | `data[].userName` | string \| null | | | `data[].roleId` | string | | | `data[].roleName` | string \| null | | | `data[].roleColor` | 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: You do not hold `user.view` at this 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 location 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/org-nodes/{id}/members Assign role at location Grants a person a role at this location. Provide `userId` and `roleId`. You cannot grant a role carrying permissions you do not hold yourself at that location. **Notes.** - The 201 body is the membership row as written. Auth: Bearer token. Permission: `user.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Location (org node) id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `userId` | string | yes | | | `roleId` | string | yes | | ```bash curl -X POST "https://api.brixsignage.com/v1/org-nodes/{id}/members" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Membership id. | | `data.spaceId` | string | | | `data.nodeId` | string | | | `data.userId` | string | | | `data.roleId` | string | | | `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 role holds permissions you do not hold, or you cannot grant them at this 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 location, person or role 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: `userId` or `roleId` 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. | ### DELETE /v1/org-nodes/{id}/members/{memberId} Remove role assignment Removes one person's role assignment at this location. Only an account owner can remove the assignment of a person who is an owner at this location, and you cannot remove an assignment that carries a permission you do not hold here. An API key is never an owner. Refuses the request if it would leave the workspace without any account owner. Auth: Bearer token. Permission: `user.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Location (org node) id. | | `memberId` | path | string | yes | Membership id (from the member list). | ```bash curl -X DELETE "https://api.brixsignage.com/v1/org-nodes/{id}/members/{memberId}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.removed` | 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: You do not hold `user.edit` here, or the person outranks you (`owner_required`, `outranked`). | 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 location or membership. | 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: `last_owner`: this is the last owner of the 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/org-nodes/bulk Bulk-create locations Creates up to 200 locations in a single call, for example to paste in a full location list at fleet setup. Send `parentId` and either a list of `names` or a list of `units`, each with a name and an optional time zone, town, Location ID and parent Location ID, so one list can hold regions and the stores under them. Empty or repeated names, and Location IDs already in use, are skipped, so the same list can safely be submitted again. Requires the create-location permission at the parent location. **Notes.** - Idempotent: sending the same list again creates nothing new and lists every row in `skipped`. Auth: Bearer token. Permission: `org-unit.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `parentId` | string | yes | The location the new ones go under (unless a row names its own parent). | | `names` | array of string | no | Location names. Use this or `units`. | | `units` | array of object | no | One row per location (up to 200). Use this or `names`. | | `units[].name` | string | yes | | | `units[].timezone` | string \| null | no | IANA zone. | | `units[].location` | object \| null | no | A town or city (with coordinates, or looked up from `label`). | | `units[].externalId` | string | no | Your own location code. | | `units[].parentExternalId` | string | no | The location code of this row's parent: an earlier row, or an existing location. | ```bash curl -X POST "https://api.brixsignage.com/v1/org-nodes/bulk" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"parentId":"on_0a1b2c3d4e5f6a7b","units":[{"name":"Chicago Loop","timezone":"America/Chicago","externalId":"STORE-0142"}]}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.created` | array of Location | | | `data.created[].id` | string | Location (org node) id. | | `data.created[].name` | string | | | `data.created[].parentId` | string \| null | Parent location; null for the workspace root. | | `data.created[].externalId` | string \| null | Your own location code (store number, region code). Unique in the workspace. | | `data.created[].timezone` | string \| null | IANA zone screens here inherit, e.g. `America/Chicago`. | | `data.created[].tier` | integer | Depth: 1 = the workspace root. | | `data.created[].isSpace` | boolean | True for a workspace (Space) boundary: the root, or a franchise child workspace. | | `data.created[].screenCount` | integer | Screens placed directly at this location (not its children). | | `data.created[].members` | array of object | People granted a role at this location. | | `data.created[].members[].userId` | string | | | `data.created[].members[].userName` | string | | | `data.created[].members[].roleId` | string | | | `data.created[].members[].roleName` | string | | | `data.created[].members[].roleColor` | string \| null | | | `data.created[].members[].source` | string \| null | `sso:` when an identity provider granted this; null when granted in Brix. | | `data.created[].featureOverrides` | any | Owner-set feature overrides, as stored (normally an array). | | `data.created[].approval` | ApprovalPolicy | The location's content-approval policy, as stored. New locations inherit `{required:false, inheritFromParent:true, …}`. | | `data.created[].approval.required` | boolean | | | `data.created[].approval.inheritFromParent` | boolean | | | `data.created[].approval.escalateUpTiers` | boolean | | | `data.created[].approval.allowSharedExemptions` | boolean | | | `data.created[].approval.approvers` | array of string | User ids named as approvers. | | `data.created[].billing` | any | Owner-set billing metadata as stored, or null. | | `data.created[].prefs` | LocationPrefs | Inheritable per-location settings (nearest ancestor wins). | | `data.created[].prefs.location` | object \| null | Physical place screens here inherit (drives weather and other location-aware apps). | | `data.created[].prefs.language` | string \| null | BCP 47 tag screens here inherit, e.g. `de-DE`. | | `data.created[].prefs.defaultContent` | object \| null | What screens here play when they have no content of their own. | | `data.skipped` | array of string | Names skipped: empty, repeated, or already a location there (or a Location ID already in use). | | `data.locationsSkipped` | array of string | Names created without a town or city, because the one given could not be found. | 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: You do not hold `org-unit.create` at the parent 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 422: `parentId` missing or not found, no rows, more than 200 rows, an invalid Location ID or time zone, or a parent that is a separate 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. | ## Playlists Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/playlists List playlists List every playlist in the workspace, including its items and a summary of its dayparting rules. Every playlist you can see, each with its items. Not paginated. Auth: Bearer token. Permission: `playlist.view`. ```bash curl "https://api.brixsignage.com/v1/playlists" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of Playlist | | | `data[].id` | string | Playlist id. | | `data[].spaceId` | string | | | `data[].name` | string | | | `data[].description` | string | | | `data[].shuffle` | boolean | | | `data[].fullscreen` | boolean | | | `data[].startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data[].expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data[].approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data[].nodeId` | string \| null | Home location; null = workspace root. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | | | `data[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data[].approvedSnapshot` | string \| null | Internal: the approved version, as stored JSON text. | | `data[].importSourceId` | string \| null | | | `data[].recalledAt` | string \| null | | | `data[].recalledBy` | string \| null | | | `data[].usedByScreenCount` | integer | | | `data[].usedByScreens` | array of object | Up to 12 screens playing it. | | `data[].usedByScreens[].id` | string | | | `data[].usedByScreens[].name` | string | | | `data[].allocations` | array of PlaylistAllocation | | | `data[].allocations[].id` | string | | | `data[].allocations[].label` | string | | | `data[].allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data[].allocations[].ownerId` | string | | | `data[].allocations[].ownerName` | string | | | `data[].allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data[].allocations[].value` | integer | | | `data[].allocations[].endValue` | integer | Absent (not null) when unset. | | `data[].allocations[].colorClass` | string | | | `data[].allocations[].fillSpaceId` | string \| null | | | `data[].allocations[].fillPlaylistId` | string \| null | | | `data[].allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data[].allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data[].allocations[].fillerRefId` | string \| null | | | `data[].allocations[].requiresApproval` | boolean | | | `data[].items` | array of PlaylistItem | | | `data[].items[].id` | string | Playlist item id. | | `data[].items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data[].items[].refId` | string | The media / app instance / layout / playlist id. | | `data[].items[].name` | string | | | `data[].items[].thumbnailUrl` | string | Empty string when there is none. | | `data[].items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data[].items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data[].items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data[].items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data[].items[].loopCount` | integer | | | `data[].items[].loopDurationMs` | integer \| null | | | `data[].items[].mediaDurationSec` | number \| null | | | `data[].items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data[].items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data[].items[].assetStartsAt` | string \| null | | | `data[].items[].assetExpiresAt` | string \| null | | | `data[].items[].assetActive` | boolean \| null | | | `data[].items[].fullscreen` | boolean | | | `data[].items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data[].items[].allocationId` | string \| null | | | `data[].items[].position` | integer | | | `data[].sharedIn` | true | Present when the playlist is outside your locations and was shared in. | 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/playlists Create a playlist with a `name` and an optional `nodeId` and other fields. Safe to retry with an idempotency key, to protect against duplicate creation from a double-click. Creates an empty playlist. Add items with POST /v1/playlists/{id}/items. Send an `Idempotency-Key` header to make a retry safe. **Notes.** - The create response is the new row plus empty `items` and `allocations`; it omits `fit`, `usedByScreenCount`, `usedByScreens` and the other columns that GET returns. Auth: Bearer token. Permission: `playlist.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `description` | string | no | | | `shuffle` | boolean | no | | | `fullscreen` | boolean | no | | | `startsAt` | string | no | YYYY-MM-DD or ISO-8601. | | `expiresAt` | string | no | YYYY-MM-DD or ISO-8601. | | `nodeId` | string | no | Home location; defaults to your own. | ```bash curl -X POST "https://api.brixsignage.com/v1/playlists" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Lunch menu","description":"Weekday 11:00-14:00","shuffle":false}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | PlaylistCreated | A new, empty playlist. | | `data.id` | string | Playlist id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string | | | `data.shuffle` | boolean | | | `data.fullscreen` | boolean | | | `data.startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data.expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.items` | array of PlaylistItem | | | `data.items[].id` | string | Playlist item id. | | `data.items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data.items[].refId` | string | The media / app instance / layout / playlist id. | | `data.items[].name` | string | | | `data.items[].thumbnailUrl` | string | Empty string when there is none. | | `data.items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data.items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data.items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data.items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data.items[].loopCount` | integer | | | `data.items[].loopDurationMs` | integer \| null | | | `data.items[].mediaDurationSec` | number \| null | | | `data.items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data.items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.items[].assetStartsAt` | string \| null | | | `data.items[].assetExpiresAt` | string \| null | | | `data.items[].assetActive` | boolean \| null | | | `data.items[].fullscreen` | boolean | | | `data.items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data.items[].allocationId` | string \| null | | | `data.items[].position` | integer | | | `data.allocations` | array of PlaylistAllocation | | | `data.allocations[].id` | string | | | `data.allocations[].label` | string | | | `data.allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data.allocations[].ownerId` | string | | | `data.allocations[].ownerName` | string | | | `data.allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data.allocations[].value` | integer | | | `data.allocations[].endValue` | integer | Absent (not null) when unset. | | `data.allocations[].colorClass` | string | | | `data.allocations[].fillSpaceId` | string \| null | | | `data.allocations[].fillPlaylistId` | string \| null | | | `data.allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data.allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data.allocations[].fillerRefId` | string \| null | | | `data.allocations[].requiresApproval` | boolean | | ```json { "data": { "id": "pl_4d5e6f7a8b9c0d1e", "spaceId": "space_1a2b3c4d5e6f7a8b", "name": "Lunch menu", "description": "Weekday 11:00-14:00", "shuffle": false, "fullscreen": false, "startsAt": null, "expiresAt": null, "approvalState": "approved", "nodeId": null, "createdAt": "2026-09-28T09:00:00.000Z", "updatedAt": "2026-09-28T09:00:00.000Z", "deletedAt": null, "items": [], "allocations": [] } } ``` 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: Missing name, an unparsable date, or an unknown 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 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/playlists/{id} Get a playlist Retrieve one playlist with its ordered items, including each item's reference kind and id, duration, and rules. Auth: Bearer token. Permission: `playlist.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | ```bash curl "https://api.brixsignage.com/v1/playlists/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | PlaylistDetail | | | `data.id` | string | Playlist id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string | | | `data.shuffle` | boolean | | | `data.fullscreen` | boolean | | | `data.startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data.expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.approvedSnapshot` | string \| null | Internal: the approved version, as stored JSON text. | | `data.importSourceId` | string \| null | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.usedByScreenCount` | integer | | | `data.usedByScreens` | array of object | Up to 12 screens playing it. | | `data.usedByScreens[].id` | string | | | `data.usedByScreens[].name` | string | | | `data.allocations` | array of PlaylistAllocation | | | `data.allocations[].id` | string | | | `data.allocations[].label` | string | | | `data.allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data.allocations[].ownerId` | string | | | `data.allocations[].ownerName` | string | | | `data.allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data.allocations[].value` | integer | | | `data.allocations[].endValue` | integer | Absent (not null) when unset. | | `data.allocations[].colorClass` | string | | | `data.allocations[].fillSpaceId` | string \| null | | | `data.allocations[].fillPlaylistId` | string \| null | | | `data.allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data.allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data.allocations[].fillerRefId` | string \| null | | | `data.allocations[].requiresApproval` | boolean | | | `data.items` | array of PlaylistItem | | | `data.items[].id` | string | Playlist item id. | | `data.items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data.items[].refId` | string | The media / app instance / layout / playlist id. | | `data.items[].name` | string | | | `data.items[].thumbnailUrl` | string | Empty string when there is none. | | `data.items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data.items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data.items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data.items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data.items[].loopCount` | integer | | | `data.items[].loopDurationMs` | integer \| null | | | `data.items[].mediaDurationSec` | number \| null | | | `data.items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data.items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.items[].assetStartsAt` | string \| null | | | `data.items[].assetExpiresAt` | string \| null | | | `data.items[].assetActive` | boolean \| null | | | `data.items[].fullscreen` | boolean | | | `data.items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data.items[].allocationId` | string \| null | | | `data.items[].position` | integer | | | `data.sharedIn` | true | Present when the playlist is outside your locations and was shared in. | | `data.requiresApproval` | boolean | The home location requires approval before content airs. | | `data.resolvedItemCount` | integer | Item count with nested playlists expanded. | | `data.resolvedDurationSec` | number | Runtime in seconds with nested playlists expanded. | 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 playlist 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/playlists/{id} Update a playlist Edit a playlist's own fields, such as name, node, shuffle, and transition settings. Items are managed through the separate /items routes. Send only the fields to change. A playback change to an approved playlist returns it to `draft` where approval is required. **Notes.** - Fields of the wrong type are ignored rather than refused (for example `name: 5`). Auth: Bearer token. Permission: `playlist.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `description` | string | no | | | `shuffle` | boolean | no | | | `fullscreen` | boolean | no | | | `fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | no | | | `startsAt` | string \| null | no | | | `expiresAt` | string \| null | no | | | `nodeId` | string \| null | no | Move to another location (needs playlist.edit there). | | `baseUpdatedAt` | string | no | The `updatedAt` your edit is based on; a stale value answers 409 with `current`. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/playlists/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Playlist | A playlist with its ordered items. | | `data.id` | string | Playlist id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string | | | `data.shuffle` | boolean | | | `data.fullscreen` | boolean | | | `data.startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data.expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.approvedSnapshot` | string \| null | Internal: the approved version, as stored JSON text. | | `data.importSourceId` | string \| null | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.usedByScreenCount` | integer | | | `data.usedByScreens` | array of object | Up to 12 screens playing it. | | `data.usedByScreens[].id` | string | | | `data.usedByScreens[].name` | string | | | `data.allocations` | array of PlaylistAllocation | | | `data.allocations[].id` | string | | | `data.allocations[].label` | string | | | `data.allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data.allocations[].ownerId` | string | | | `data.allocations[].ownerName` | string | | | `data.allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data.allocations[].value` | integer | | | `data.allocations[].endValue` | integer | Absent (not null) when unset. | | `data.allocations[].colorClass` | string | | | `data.allocations[].fillSpaceId` | string \| null | | | `data.allocations[].fillPlaylistId` | string \| null | | | `data.allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data.allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data.allocations[].fillerRefId` | string \| null | | | `data.allocations[].requiresApproval` | boolean | | | `data.items` | array of PlaylistItem | | | `data.items[].id` | string | Playlist item id. | | `data.items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data.items[].refId` | string | The media / app instance / layout / playlist id. | | `data.items[].name` | string | | | `data.items[].thumbnailUrl` | string | Empty string when there is none. | | `data.items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data.items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data.items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data.items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data.items[].loopCount` | integer | | | `data.items[].loopDurationMs` | integer \| null | | | `data.items[].mediaDurationSec` | number \| null | | | `data.items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data.items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.items[].assetStartsAt` | string \| null | | | `data.items[].assetExpiresAt` | string \| null | | | `data.items[].assetActive` | boolean \| null | | | `data.items[].fullscreen` | boolean | | | `data.items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data.items[].allocationId` | string \| null | | | `data.items[].position` | integer | | | `data.sharedIn` | true | Present when the playlist is outside your locations and was shared in. | 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: Moving it to a location where you lack playlist.edit. | 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 playlist 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 409: `conflict`: `baseUpdatedAt` is stale; the body carries `current`. | 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: An unparsable date or an unknown 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 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/playlists/{id} Delete a playlist to the recycle bin. Screens assigned to it fall back to their default content. Fails with 409 if the playlist is shared into other spaces, unless the deletion is forced. Moves the playlist to the recycle bin (restorable for 30 days). Auth: Bearer token. Permission: `playlist.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | | `force` | query | "true" | no | Delete even when it is shared into other places. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/playlists/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.deleted` | true | | | `data.sharesRemoved` | 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: No such playlist 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 409: `content_shared`: the item is shared into other places; `shareCount`, `crossSpaceShares`, `contentShares` say where. Repeat with `?force=true` to delete it and those shares. | 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. | ### PUT /v1/playlists/{id}/allocations Set playlist allocations Set a playlist's allocations: shares of its airtime given to a person, a group or a location (a percent, every nth play, or a time of day). This replaces the whole set; allocations you leave out are removed. **Notes.** - An out-of-range `value` is 400 `validation_error`; every other validation failure is 422. - Rows with an unknown `ownerKind` or `kind` are dropped without an error. - A missing or malformed body is treated as an empty list, which removes every allocation. - The returned playlist row (`updatedAt`, `approvalState`) is the one read before this edit; an approved playlist is returned to `draft` in storage by this call. Re-read with GET to see it. Auth: Bearer token. Permission: `playlist.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `allocations` | array of object | yes | The whole set, at most 200. Allocations left out are removed. | | `allocations[].id` | string | no | Keep this id: one of the playlist's current allocations, or a new id of 6-64 URL-safe characters. Otherwise a new id is made. | | `allocations[].label` | string | no | | | `allocations[].ownerKind` | "user" \| "group" \| "org-unit" | yes | Who gets the share. `org-unit` = a location (`ownerId` is its node id). | | `allocations[].ownerId` | string | yes | | | `allocations[].ownerName` | string | no | | | `allocations[].kind` | "percent" \| "every-nth" \| "daypart" | yes | | | `allocations[].value` | integer | yes | `percent` 0-100, `every-nth` 1-100, `daypart` 0-1439 (the minute of the day it opens). | | `allocations[].endValue` | number | no | `daypart`: the minute of the day it closes. | | `allocations[].colorClass` | string | no | | | `allocations[].fillSpaceId` | string \| null | no | A child workspace that fills the share; null = this workspace. | | `allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | no | What plays while the share is not filled. Default `collapse`. | | `allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" | no | With `filler`: the kind of the filler content. | | `allocations[].fillerRefId` | string | no | | | `allocations[].requiresApproval` | boolean | no | What the recipient puts in the share must be approved first. | ```bash curl -X PUT "https://api.brixsignage.com/v1/playlists/{id}/allocations" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Playlist | A playlist with its ordered items. | | `data.id` | string | Playlist id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string | | | `data.shuffle` | boolean | | | `data.fullscreen` | boolean | | | `data.startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data.expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.approvedSnapshot` | string \| null | Internal: the approved version, as stored JSON text. | | `data.importSourceId` | string \| null | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.usedByScreenCount` | integer | | | `data.usedByScreens` | array of object | Up to 12 screens playing it. | | `data.usedByScreens[].id` | string | | | `data.usedByScreens[].name` | string | | | `data.allocations` | array of PlaylistAllocation | | | `data.allocations[].id` | string | | | `data.allocations[].label` | string | | | `data.allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data.allocations[].ownerId` | string | | | `data.allocations[].ownerName` | string | | | `data.allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data.allocations[].value` | integer | | | `data.allocations[].endValue` | integer | Absent (not null) when unset. | | `data.allocations[].colorClass` | string | | | `data.allocations[].fillSpaceId` | string \| null | | | `data.allocations[].fillPlaylistId` | string \| null | | | `data.allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data.allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data.allocations[].fillerRefId` | string \| null | | | `data.allocations[].requiresApproval` | boolean | | | `data.items` | array of PlaylistItem | | | `data.items[].id` | string | Playlist item id. | | `data.items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data.items[].refId` | string | The media / app instance / layout / playlist id. | | `data.items[].name` | string | | | `data.items[].thumbnailUrl` | string | Empty string when there is none. | | `data.items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data.items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data.items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data.items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data.items[].loopCount` | integer | | | `data.items[].loopDurationMs` | integer \| null | | | `data.items[].mediaDurationSec` | number \| null | | | `data.items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data.items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.items[].assetStartsAt` | string \| null | | | `data.items[].assetExpiresAt` | string \| null | | | `data.items[].assetActive` | boolean \| null | | | `data.items[].fullscreen` | boolean | | | `data.items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data.items[].allocationId` | string \| null | | | `data.items[].position` | integer | | | `data.sharedIn` | true | Present when the playlist is outside your locations and was shared in. | Response 400: `validation_error`: a `value` outside its kind's range or not a whole number. | 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 playlist 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: More than 200 allocations, a location that is not in this workspace (`invalid_node`), or percent shares that would give away more than 99% on some screen. | 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/playlists/{id}/duplicate Duplicate a playlist Create a deep copy of a playlist, including its settings, share-of-voice allocations (fill assignments reset), and ordered items, named " copy" and unique within the space. The copy starts as a draft with no assignment. Requires permission to create playlists at the source playlist's home node. Auth: Bearer token. Permission: `playlist.create`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | ```bash curl -X POST "https://api.brixsignage.com/v1/playlists/{id}/duplicate" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Playlist | A playlist with its ordered items. | | `data.id` | string | Playlist id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string | | | `data.shuffle` | boolean | | | `data.fullscreen` | boolean | | | `data.startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data.expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.approvedSnapshot` | string \| null | Internal: the approved version, as stored JSON text. | | `data.importSourceId` | string \| null | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.usedByScreenCount` | integer | | | `data.usedByScreens` | array of object | Up to 12 screens playing it. | | `data.usedByScreens[].id` | string | | | `data.usedByScreens[].name` | string | | | `data.allocations` | array of PlaylistAllocation | | | `data.allocations[].id` | string | | | `data.allocations[].label` | string | | | `data.allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data.allocations[].ownerId` | string | | | `data.allocations[].ownerName` | string | | | `data.allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data.allocations[].value` | integer | | | `data.allocations[].endValue` | integer | Absent (not null) when unset. | | `data.allocations[].colorClass` | string | | | `data.allocations[].fillSpaceId` | string \| null | | | `data.allocations[].fillPlaylistId` | string \| null | | | `data.allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data.allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data.allocations[].fillerRefId` | string \| null | | | `data.allocations[].requiresApproval` | boolean | | | `data.items` | array of PlaylistItem | | | `data.items[].id` | string | Playlist item id. | | `data.items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data.items[].refId` | string | The media / app instance / layout / playlist id. | | `data.items[].name` | string | | | `data.items[].thumbnailUrl` | string | Empty string when there is none. | | `data.items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data.items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data.items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data.items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data.items[].loopCount` | integer | | | `data.items[].loopDurationMs` | integer \| null | | | `data.items[].mediaDurationSec` | number \| null | | | `data.items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data.items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.items[].assetStartsAt` | string \| null | | | `data.items[].assetExpiresAt` | string \| null | | | `data.items[].assetActive` | boolean \| null | | | `data.items[].fullscreen` | boolean | | | `data.items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data.items[].allocationId` | string \| null | | | `data.items[].position` | integer | | | `data.sharedIn` | true | Present when the playlist is outside your locations and was shared in. | 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 playlist 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/playlists/{id}/items Add an item to a playlist Append one item to a playlist with `refKind`, `refId`, and an optional `durationSec` and other fields. Rejects references from another space, and rejects any nesting that would create a loop, such as a playlist referencing a layout zone that contains the same playlist, with a clear error rather than failing silently. Appends one item. A video or audio file defaults to playing its full length, an animated image to one loop, anything else to 10 seconds. Returns the whole playlist. **Notes.** - `position` is accepted and ignored. - The returned `approvalState` is the value from before the edit; an approved playlist is returned to `draft` in storage by this call. Re-read with GET to see it. Auth: Bearer token. Permission: `playlist.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `refKind` | "media" \| "app" \| "layout" \| "playlist" | yes | | | `refId` | string | yes | | | `durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | no | | | `durationSeconds` | integer | no | | | `loopCount` | number | no | | | `fit` | string | no | contain, cover, fill or blur-fill; anything else means inherit. | | `fullscreen` | boolean | no | | | `position` | number | no | Accepted but ignored: the item is always appended. Reorder with PUT /v1/playlists/{id}/items. | | `allocationId` | string | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/playlists/{id}/items" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Playlist | A playlist with its ordered items. | | `data.id` | string | Playlist id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string | | | `data.shuffle` | boolean | | | `data.fullscreen` | boolean | | | `data.startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data.expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.approvedSnapshot` | string \| null | Internal: the approved version, as stored JSON text. | | `data.importSourceId` | string \| null | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.usedByScreenCount` | integer | | | `data.usedByScreens` | array of object | Up to 12 screens playing it. | | `data.usedByScreens[].id` | string | | | `data.usedByScreens[].name` | string | | | `data.allocations` | array of PlaylistAllocation | | | `data.allocations[].id` | string | | | `data.allocations[].label` | string | | | `data.allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data.allocations[].ownerId` | string | | | `data.allocations[].ownerName` | string | | | `data.allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data.allocations[].value` | integer | | | `data.allocations[].endValue` | integer | Absent (not null) when unset. | | `data.allocations[].colorClass` | string | | | `data.allocations[].fillSpaceId` | string \| null | | | `data.allocations[].fillPlaylistId` | string \| null | | | `data.allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data.allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data.allocations[].fillerRefId` | string \| null | | | `data.allocations[].requiresApproval` | boolean | | | `data.items` | array of PlaylistItem | | | `data.items[].id` | string | Playlist item id. | | `data.items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data.items[].refId` | string | The media / app instance / layout / playlist id. | | `data.items[].name` | string | | | `data.items[].thumbnailUrl` | string | Empty string when there is none. | | `data.items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data.items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data.items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data.items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data.items[].loopCount` | integer | | | `data.items[].loopDurationMs` | integer \| null | | | `data.items[].mediaDurationSec` | number \| null | | | `data.items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data.items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.items[].assetStartsAt` | string \| null | | | `data.items[].assetExpiresAt` | string \| null | | | `data.items[].assetActive` | boolean \| null | | | `data.items[].fullscreen` | boolean | | | `data.items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data.items[].allocationId` | string \| null | | | `data.items[].position` | integer | | | `data.sharedIn` | true | Present when the playlist is outside your locations and was shared in. | 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 available at the playlist'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 playlist, or the referenced 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 422: Invalid body, a playlist loop, a layout with unbound zones, or an unknown allocation. | 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. | ### PUT /v1/playlists/{id}/items Reorder a playlist's items Replace a playlist's entire item list in one call. Send the full array of items in the order you want; the same reference and loop validation applies as when adding a single item. Sets the order of the items: `itemIds[0]` plays first. Unknown ids are skipped. It does not add or remove items. Returns the whole playlist. **Notes.** - Items left out of `itemIds` keep their old position number, so two items can share a position. Send every item id. - A missing or malformed body is treated as an empty list, not refused. Auth: Bearer token. Permission: `playlist.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `itemIds` | array of string | yes | Item ids in the new order. | ```bash curl -X PUT "https://api.brixsignage.com/v1/playlists/{id}/items" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"itemIds":["pli_7a8b9c0d1e2f3a4b","pli_1e2f3a4b5c6d7e8f"]}' ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Playlist | A playlist with its ordered items. | | `data.id` | string | Playlist id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string | | | `data.shuffle` | boolean | | | `data.fullscreen` | boolean | | | `data.startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data.expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.approvedSnapshot` | string \| null | Internal: the approved version, as stored JSON text. | | `data.importSourceId` | string \| null | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.usedByScreenCount` | integer | | | `data.usedByScreens` | array of object | Up to 12 screens playing it. | | `data.usedByScreens[].id` | string | | | `data.usedByScreens[].name` | string | | | `data.allocations` | array of PlaylistAllocation | | | `data.allocations[].id` | string | | | `data.allocations[].label` | string | | | `data.allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data.allocations[].ownerId` | string | | | `data.allocations[].ownerName` | string | | | `data.allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data.allocations[].value` | integer | | | `data.allocations[].endValue` | integer | Absent (not null) when unset. | | `data.allocations[].colorClass` | string | | | `data.allocations[].fillSpaceId` | string \| null | | | `data.allocations[].fillPlaylistId` | string \| null | | | `data.allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data.allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data.allocations[].fillerRefId` | string \| null | | | `data.allocations[].requiresApproval` | boolean | | | `data.items` | array of PlaylistItem | | | `data.items[].id` | string | Playlist item id. | | `data.items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data.items[].refId` | string | The media / app instance / layout / playlist id. | | `data.items[].name` | string | | | `data.items[].thumbnailUrl` | string | Empty string when there is none. | | `data.items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data.items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data.items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data.items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data.items[].loopCount` | integer | | | `data.items[].loopDurationMs` | integer \| null | | | `data.items[].mediaDurationSec` | number \| null | | | `data.items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data.items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.items[].assetStartsAt` | string \| null | | | `data.items[].assetExpiresAt` | string \| null | | | `data.items[].assetActive` | boolean \| null | | | `data.items[].fullscreen` | boolean | | | `data.items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data.items[].allocationId` | string \| null | | | `data.items[].position` | integer | | | `data.sharedIn` | true | Present when the playlist is outside your locations and was shared in. | 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 playlist 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/playlists/{id}/items/{itemId} Update a playlist item Edit one playlist item's duration, loop count, fit, full-screen setting, screen tag rules or allocation. `refKind` and `refId` cannot be changed on an existing item; delete it and add a new item to point at different content. **Notes.** - An unknown `itemId` is not refused: nothing changes and the answer is 200 with the playlist. - `targetTags` and `excludeTags` are stored but not returned on the items. - Fields of the wrong type are ignored rather than refused. - The returned playlist row (`updatedAt`, `approvalState`) is the one read before this edit; an approved playlist is returned to `draft` in storage by this call. Re-read with GET to see it. Auth: Bearer token. Permission: `playlist.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | | `itemId` | path | string | yes | Playlist item id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | no | | | `durationSeconds` | number | no | Dwell for `fixed`, rounded and clamped to 1 second through 24 hours. | | `loopCount` | number | no | Plays per turn for `loop`; rounded. | | `fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | no | The item's own fit; `null` = follow the file's fit. | | `fullscreen` | boolean | no | Take over the whole screen, over any layout. | | `targetTags` | array of string \| null | no | Play only on screens with one of these tags. `null` or `[]` clears the rule. | | `excludeTags` | array of string \| null | no | Never play on screens with one of these tags. `null` or `[]` clears the rule. | | `allocationId` | string \| null | no | Put the item in one of this playlist's allocations; `null` = the base rotation. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/playlists/{id}/items/{itemId}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Playlist | A playlist with its ordered items. | | `data.id` | string | Playlist id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string | | | `data.shuffle` | boolean | | | `data.fullscreen` | boolean | | | `data.startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data.expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.approvedSnapshot` | string \| null | Internal: the approved version, as stored JSON text. | | `data.importSourceId` | string \| null | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.usedByScreenCount` | integer | | | `data.usedByScreens` | array of object | Up to 12 screens playing it. | | `data.usedByScreens[].id` | string | | | `data.usedByScreens[].name` | string | | | `data.allocations` | array of PlaylistAllocation | | | `data.allocations[].id` | string | | | `data.allocations[].label` | string | | | `data.allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data.allocations[].ownerId` | string | | | `data.allocations[].ownerName` | string | | | `data.allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data.allocations[].value` | integer | | | `data.allocations[].endValue` | integer | Absent (not null) when unset. | | `data.allocations[].colorClass` | string | | | `data.allocations[].fillSpaceId` | string \| null | | | `data.allocations[].fillPlaylistId` | string \| null | | | `data.allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data.allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data.allocations[].fillerRefId` | string \| null | | | `data.allocations[].requiresApproval` | boolean | | | `data.items` | array of PlaylistItem | | | `data.items[].id` | string | Playlist item id. | | `data.items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data.items[].refId` | string | The media / app instance / layout / playlist id. | | `data.items[].name` | string | | | `data.items[].thumbnailUrl` | string | Empty string when there is none. | | `data.items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data.items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data.items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data.items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data.items[].loopCount` | integer | | | `data.items[].loopDurationMs` | integer \| null | | | `data.items[].mediaDurationSec` | number \| null | | | `data.items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data.items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.items[].assetStartsAt` | string \| null | | | `data.items[].assetExpiresAt` | string \| null | | | `data.items[].assetActive` | boolean \| null | | | `data.items[].fullscreen` | boolean | | | `data.items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data.items[].allocationId` | string \| null | | | `data.items[].position` | integer | | | `data.sharedIn` | true | Present when the playlist is outside your locations and was shared in. | 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 playlist 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: `allocationId` is not one of this playlist's allocations, or nothing to update. | 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/playlists/{id}/items/{itemId} Remove a playlist item Remove one item from a playlist. The remaining items keep their existing order. Removes the item and closes the gap in the positions. Returns the whole playlist. **Notes.** - An unknown `itemId` is not refused: nothing is removed and the answer is 200 with the playlist. - The returned playlist row (`updatedAt`, `approvalState`) is the one read before this edit; an approved playlist is returned to `draft` in storage by this call. Re-read with GET to see it. Auth: Bearer token. Permission: `playlist.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | | `itemId` | path | string | yes | Playlist item id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/playlists/{id}/items/{itemId}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Playlist | A playlist with its ordered items. | | `data.id` | string | Playlist id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string | | | `data.shuffle` | boolean | | | `data.fullscreen` | boolean | | | `data.startsAt` | string \| null | Plays only from this date (YYYY-MM-DD) or time, stored as sent. | | `data.expiresAt` | string \| null | Stops after this date (YYYY-MM-DD) or time, stored as sent. | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | Only `approved` content airs where the location requires approval. | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.approvedSnapshot` | string \| null | Internal: the approved version, as stored JSON text. | | `data.importSourceId` | string \| null | | | `data.recalledAt` | string \| null | | | `data.recalledBy` | string \| null | | | `data.usedByScreenCount` | integer | | | `data.usedByScreens` | array of object | Up to 12 screens playing it. | | `data.usedByScreens[].id` | string | | | `data.usedByScreens[].name` | string | | | `data.allocations` | array of PlaylistAllocation | | | `data.allocations[].id` | string | | | `data.allocations[].label` | string | | | `data.allocations[].ownerKind` | "user" \| "group" \| "org-unit" | | | `data.allocations[].ownerId` | string | | | `data.allocations[].ownerName` | string | | | `data.allocations[].kind` | "percent" \| "every-nth" \| "daypart" | | | `data.allocations[].value` | integer | | | `data.allocations[].endValue` | integer | Absent (not null) when unset. | | `data.allocations[].colorClass` | string | | | `data.allocations[].fillSpaceId` | string \| null | | | `data.allocations[].fillPlaylistId` | string \| null | | | `data.allocations[].unfilledBehavior` | "collapse" \| "filler" \| "holding" | | | `data.allocations[].fillerRefKind` | "media" \| "playlist" \| "app" \| "layout" \| null | | | `data.allocations[].fillerRefId` | string \| null | | | `data.allocations[].requiresApproval` | boolean | | | `data.items` | array of PlaylistItem | | | `data.items[].id` | string | Playlist item id. | | `data.items[].refKind` | "media" \| "app" \| "layout" \| "playlist" | | | `data.items[].refId` | string | The media / app instance / layout / playlist id. | | `data.items[].name` | string | | | `data.items[].thumbnailUrl` | string | Empty string when there is none. | | `data.items[].kind` | string | The media kind for media items; `app`, `layout` or `playlist` otherwise. | | `data.items[].nestedItemCount` | integer \| null | Nested playlists only: its top-level item count. | | `data.items[].durationMode` | "fixed" \| "full" \| "live" \| "loop" \| "manual" | | | `data.items[].durationSeconds` | integer | Dwell in seconds for `fixed`. | | `data.items[].loopCount` | integer | | | `data.items[].loopDurationMs` | integer \| null | | | `data.items[].mediaDurationSec` | number \| null | | | `data.items[].fit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | The item's own fit; null = inherit. | | `data.items[].assetFit` | "contain" \| "cover" \| "fill" \| "blur-fill" \| null | | | `data.items[].assetStartsAt` | string \| null | | | `data.items[].assetExpiresAt` | string \| null | | | `data.items[].assetActive` | boolean \| null | | | `data.items[].fullscreen` | boolean | | | `data.items[].withheld` | "expired" \| "archived" \| "not-yet" \| null | Why a media item is not airing now, or null. | | `data.items[].allocationId` | string \| null | | | `data.items[].position` | integer | | | `data.sharedIn` | true | Present when the playlist is outside your locations and was shared in. | 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 playlist 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/playlists/{id}/restore Restore a deleted playlist so its items and ordering return to the playlist library. Auth: Bearer token. Permission: `playlist.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | ```bash curl -X POST "https://api.brixsignage.com/v1/playlists/{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 such playlist in this workspace, or it was purged. | 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: `not_deleted`: the playlist is not in the recycle bin. | 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/playlists/{id}/share-readiness Get playlist share readiness For a playlist's airtime reserved for other teams, list who at each location can fill their share, and the narrowest role that would let them do so. **Notes.** - `requiredPermissions` is absent when `locations` is empty. Auth: Bearer token. Permission: `playlist.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Playlist id. | ```bash curl "https://api.brixsignage.com/v1/playlists/{id}/share-readiness" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.locations` | array of object | One per location that holds a percent share of this playlist. | | `data.locations[].nodeId` | string | | | `data.locations[].nodeName` | string | | | `data.locations[].canFill` | boolean | Somebody at the location can fill the share now. | | `data.locations[].fillerCount` | integer | | | `data.locations[].candidates` | array of object | People at the location who cannot fill it yet. | | `data.locations[].candidates[].userId` | string | | | `data.locations[].candidates[].name` | string | | | `data.locations[].candidates[].email` | string | | | `data.locations[].roleId` | string \| null | The narrowest existing role that would let them fill it. | | `data.locations[].roleName` | string \| null | | | `data.locations[].mayGrant` | boolean | The caller may give that role at that location. | | `data.requiredPermissions` | array of string | The permissions filling a share needs. Absent when no location holds a share. | 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 playlist 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. | ## Power policies Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/power-policies List power policies List the power policies configured for your workspace. Each policy defines an open and close window for the screens using it. **Notes.** - Needs the permission at the workspace root: a location-scoped key is refused. Auth: Bearer token. Permission: `screen.view`. ```bash curl "https://api.brixsignage.com/v1/power-policies" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of PowerPolicy | | | `data[].id` | string | Power policy id. | | `data[].spaceId` | string | | | `data[].name` | string | | | `data[].description` | string \| null | | | `data[].windows` | WeeklyWindows | Open window per weekday (`mon` … `sun`). A day that is absent is closed. | | `data[].windows.mon` | OpenWindow | | | `data[].windows.mon.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data[].windows.mon.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data[].windows.mon.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.mon.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.mon.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data[].windows.tue` | OpenWindow | | | `data[].windows.tue.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data[].windows.tue.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data[].windows.tue.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.tue.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.tue.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data[].windows.wed` | OpenWindow | | | `data[].windows.wed.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data[].windows.wed.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data[].windows.wed.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.wed.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.wed.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data[].windows.thu` | OpenWindow | | | `data[].windows.thu.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data[].windows.thu.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data[].windows.thu.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.thu.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.thu.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data[].windows.fri` | OpenWindow | | | `data[].windows.fri.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data[].windows.fri.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data[].windows.fri.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.fri.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.fri.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data[].windows.sat` | OpenWindow | | | `data[].windows.sat.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data[].windows.sat.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data[].windows.sat.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.sat.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.sat.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data[].windows.sun` | OpenWindow | | | `data[].windows.sun.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data[].windows.sun.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data[].windows.sun.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.sun.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data[].windows.sun.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data[].rebootHourLocal` | integer \| null | Hour (0–23, local time) the device restarts each day; null = no scheduled restart. | | `data[].timezone` | string \| null | IANA time zone the windows use; null = each screen's own time zone. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | Always null on these reads. | | `data[].screenCount` | integer | Screens that use this policy. | ```json { "data": [ { "id": "ppol_1a2b3c4d5e6f7a8b", "spaceId": "space_1a2b3c4d5e6f7a8b", "name": "Store hours", "description": "Open 8 to 22", "windows": { "mon": { "start": "08:00", "end": "22:00" }, "tue": { "start": "08:00", "end": "22:00", "volume": 40 } }, "rebootHourLocal": 4, "timezone": "Europe/London", "createdAt": "2026-09-28T09:00:00.000Z", "updatedAt": "2026-09-28T09:00:00.000Z", "deletedAt": null, "screenCount": 12 } ] } ``` 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/power-policies Create a power policy Create a named power policy that defines when screens using it should power on and off. **Notes.** - Needs the permission at the workspace root: a location-scoped key is refused. Auth: Bearer token. Permission: `screen.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Default on create: `Untitled policy`. | | `description` | string \| null | no | | | `windows` | object | no | Open window per weekday. `null` or an absent day is closed. A malformed day is dropped (closed), not refused. | | `windows.mon` | OpenWindow \| null | no | | | `windows.tue` | OpenWindow \| null | no | | | `windows.wed` | OpenWindow \| null | no | | | `windows.thu` | OpenWindow \| null | no | | | `windows.fri` | OpenWindow \| null | no | | | `windows.sat` | OpenWindow \| null | no | | | `windows.sun` | OpenWindow \| null | no | | | `rebootHourLocal` | number \| null | no | 0–23; rounded and clamped. Null (or a value that is not a number) turns the restart off. | | `timezone` | string \| null | no | IANA time zone (`Europe/London`). An unknown zone is refused with 422. | ```bash curl -X POST "https://api.brixsignage.com/v1/power-policies" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | PowerPolicy | | | `data.id` | string | Power policy id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string \| null | | | `data.windows` | WeeklyWindows | Open window per weekday (`mon` … `sun`). A day that is absent is closed. | | `data.windows.mon` | OpenWindow | | | `data.windows.mon.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.mon.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.mon.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.mon.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.mon.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.tue` | OpenWindow | | | `data.windows.tue.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.tue.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.tue.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.tue.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.tue.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.wed` | OpenWindow | | | `data.windows.wed.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.wed.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.wed.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.wed.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.wed.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.thu` | OpenWindow | | | `data.windows.thu.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.thu.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.thu.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.thu.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.thu.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.fri` | OpenWindow | | | `data.windows.fri.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.fri.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.fri.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.fri.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.fri.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.sat` | OpenWindow | | | `data.windows.sat.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.sat.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.sat.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sat.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sat.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.sun` | OpenWindow | | | `data.windows.sun.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.sun.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.sun.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sun.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sun.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.rebootHourLocal` | integer \| null | Hour (0–23, local time) the device restarts each day; null = no scheduled restart. | | `data.timezone` | string \| null | IANA time zone the windows use; null = each screen's own time zone. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads. | | `data.screenCount` | integer | Screens that use this policy. | 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 `timezone`. | 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/power-policies/{id} Get a power policy Get one power policy by its ID. **Notes.** - Needs the permission at the workspace root: a location-scoped key is refused. Auth: Bearer token. Permission: `screen.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Power policy id. | ```bash curl "https://api.brixsignage.com/v1/power-policies/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | PowerPolicy | | | `data.id` | string | Power policy id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string \| null | | | `data.windows` | WeeklyWindows | Open window per weekday (`mon` … `sun`). A day that is absent is closed. | | `data.windows.mon` | OpenWindow | | | `data.windows.mon.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.mon.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.mon.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.mon.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.mon.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.tue` | OpenWindow | | | `data.windows.tue.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.tue.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.tue.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.tue.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.tue.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.wed` | OpenWindow | | | `data.windows.wed.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.wed.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.wed.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.wed.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.wed.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.thu` | OpenWindow | | | `data.windows.thu.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.thu.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.thu.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.thu.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.thu.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.fri` | OpenWindow | | | `data.windows.fri.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.fri.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.fri.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.fri.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.fri.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.sat` | OpenWindow | | | `data.windows.sat.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.sat.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.sat.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sat.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sat.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.sun` | OpenWindow | | | `data.windows.sun.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.sun.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.sun.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sun.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sun.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.rebootHourLocal` | integer \| null | Hour (0–23, local time) the device restarts each day; null = no scheduled restart. | | `data.timezone` | string \| null | IANA time zone the windows use; null = each screen's own time zone. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads. | | `data.screenCount` | integer | Screens that use this policy. | 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 power policy 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/power-policies/{id} Update a power policy's open and close windows, reboot hour, time zone, or name. **Notes.** - `screenCount` in this response is always 0; read the policy to get the real count. - An empty `name` keeps the current name. - Needs the permission at the workspace root: a location-scoped key is refused. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Power policy id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Default on create: `Untitled policy`. | | `description` | string \| null | no | | | `windows` | object | no | Open window per weekday. `null` or an absent day is closed. A malformed day is dropped (closed), not refused. | | `windows.mon` | OpenWindow \| null | no | | | `windows.tue` | OpenWindow \| null | no | | | `windows.wed` | OpenWindow \| null | no | | | `windows.thu` | OpenWindow \| null | no | | | `windows.fri` | OpenWindow \| null | no | | | `windows.sat` | OpenWindow \| null | no | | | `windows.sun` | OpenWindow \| null | no | | | `rebootHourLocal` | number \| null | no | 0–23; rounded and clamped. Null (or a value that is not a number) turns the restart off. | | `timezone` | string \| null | no | IANA time zone (`Europe/London`). An unknown zone is refused with 422. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/power-policies/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | PowerPolicy | | | `data.id` | string | Power policy id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.description` | string \| null | | | `data.windows` | WeeklyWindows | Open window per weekday (`mon` … `sun`). A day that is absent is closed. | | `data.windows.mon` | OpenWindow | | | `data.windows.mon.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.mon.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.mon.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.mon.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.mon.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.tue` | OpenWindow | | | `data.windows.tue.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.tue.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.tue.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.tue.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.tue.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.wed` | OpenWindow | | | `data.windows.wed.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.wed.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.wed.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.wed.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.wed.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.thu` | OpenWindow | | | `data.windows.thu.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.thu.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.thu.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.thu.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.thu.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.fri` | OpenWindow | | | `data.windows.fri.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.fri.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.fri.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.fri.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.fri.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.sat` | OpenWindow | | | `data.windows.sat.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.sat.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.sat.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sat.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sat.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.windows.sun` | OpenWindow | | | `data.windows.sun.start` | string | Opening time, `HH:MM` 24-hour, local time. | | `data.windows.sun.end` | string | Closing time, `HH:MM`. An `end` at or before `start` runs past midnight. | | `data.windows.sun.volume` | integer \| null | Volume 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sun.brightness` | integer \| null | Backlight 0–100 while open; absent or null keeps the screen's own. | | `data.windows.sun.muted` | boolean \| null | Mute (true) or unmute (false) while open; absent or null keeps the screen's own. | | `data.rebootHourLocal` | integer \| null | Hour (0–23, local time) the device restarts each day; null = no scheduled restart. | | `data.timezone` | string \| null | IANA time zone the windows use; null = each screen's own time zone. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads. | | `data.screenCount` | integer | Screens that use this policy. | 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 power policy 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 `timezone`. | 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/power-policies/{id}/delete Delete a power policy. Screens that use it go back to their own operating hours. A deleted policy cannot be restored. **Notes.** - A POST to `/delete`, not `DELETE`. Screens that use the policy keep the link and use their own operating hours until they get another policy. There is no restore route. - Needs the permission at the workspace root: a location-scoped key is refused. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Power policy id. | ```bash curl -X POST "https://api.brixsignage.com/v1/power-policies/{id}/delete" \ -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 power policy 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. | ## Proof Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/proof Get a proof-of-play report Get proof-of-play totals for a date range, broken down by content item and by screen, including digital-out-of-home verification fields. Use the `from` and `to` query parameters to set the date range. Counts what played, where and for how long, over a time window. Only screens the caller can see are counted. Auth: Bearer token. Permission: `playback-log.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `from` | query | string | no | Window start (ISO-8601). Default: the start of the retention window. | | `to` | query | string | no | Window end (ISO-8601). Default: now. | | `screenId` | query | string | no | Only this screen. | ```bash curl "https://api.brixsignage.com/v1/proof" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ProofOfPlayReport | | | `data.totals` | object | | | `data.totals.plays` | integer | Successful plays (neither skipped nor failed). | | `data.totals.skipped` | integer | | | `data.totals.failed` | integer | | | `data.totals.events` | integer | Every playback-end event: plays + skipped + failed. | | `data.totals.uniqueContent` | integer | | | `data.totals.screensReporting` | integer | | | `data.byContent` | array of object | Per content, most successful plays first. | | `data.byContent[].contentId` | string | `unknown` when the player did not report one. | | `data.byContent[].contentName` | string | Falls back to the id when no name was reported. | | `data.byContent[].contentKind` | string | `media`, `app`, `creative`, …; `media` for days read from the daily rollup. | | `data.byContent[].screenName` | string \| null | Set only when exactly one screen played it. | | `data.byContent[].played` | integer | | | `data.byContent[].skipped` | integer | | | `data.byContent[].failed` | integer | | | `data.byContent[].screenCount` | integer | | | `data.byContent[].totalDurationSec` | integer | | | `data.byContent[].firstAt` | string | ISO-8601 timestamp (UTC). | | `data.byContent[].lastAt` | string | ISO-8601 timestamp (UTC). | | `data.screens` | array of object | Screens that reported in the window, by name. | | `data.screens[].id` | string | | | `data.screens[].name` | string | | | `data.from` | string | ISO-8601 timestamp (UTC). | | `data.to` | string | ISO-8601 timestamp (UTC). | | `data.retentionDays` | integer | How far back raw play events exist; older days come from the daily rollup. | | `data.truncated` | boolean | Always false: the whole window is counted. | | `data.fromRollup` | boolean | Part of the window was read from the daily rollup. | 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/proof/events List proof-of-play events List individual playback events, one per play, for a date range. The number of events returned is capped. For totals rolled up by content item and screen, use GET /v1/proof instead. Auth: Bearer token. Permission: `playback-log.view`. ```bash curl "https://api.brixsignage.com/v1/proof/events" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Recall Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/recall List recalled content Return everything currently held off screens, including its kind, name, when it was recalled, and who recalled it. Since a recall is reversible, this list is the record of what is being withheld and why, so nothing stays off screens indefinitely without anyone knowing. An item is listed only where the caller can view that kind of content at its location. Who recalled it is returned in full only where the caller also holds the user view permission; otherwise only its kind, for example user or staff. Auth: Bearer token. Permission: `screen.view`. ```bash curl "https://api.brixsignage.com/v1/recall" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/recall/creative/{id} Recall a creative from all screens Immediately remove a creative from every screen it appears on, without deleting it or changing any playlist. This applies regardless of any approval settings at the affected locations. The action is reversible, and the underlying rotation is left untouched. Auth: Bearer token. Permission: `creative.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/recall/creative/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/recall/creative/{id}/restore Restore a recalled creative Put a recalled creative back on the screens it was removed from. Nothing was changed by the recall, so playback resumes exactly as it was before. Auth: Bearer token. Permission: `creative.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/recall/creative/{id}/restore" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/recall/media/{id} Recall a media file from all screens Immediately remove a media file from every screen it appears on, without deleting it or changing any playlist. This applies regardless of any approval settings at the affected locations. The action is reversible, and the underlying rotation is left untouched. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/recall/media/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/recall/media/{id}/restore Restore a recalled media file Put a recalled media file back on the screens it was removed from. Nothing was changed by the recall, so playback resumes exactly as it was before. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/recall/media/{id}/restore" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/recall/playlist/{id} Recall a playlist from all screens Immediately remove a playlist from every screen it appears on, without deleting it or changing any other playlist. This applies regardless of any approval settings at the affected locations. The action is reversible, and the underlying rotation is left untouched. Auth: Bearer token. Permission: `playlist.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/recall/playlist/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/recall/playlist/{id}/restore Restore a recalled playlist Put a recalled playlist back on the screens it was removed from. Nothing was changed by the recall, so playback resumes exactly as it was before. Auth: Bearer token. Permission: `playlist.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/recall/playlist/{id}/restore" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/recall/schedule/{id} Recall a schedule from all screens Immediately remove a schedule from every screen it appears on, without deleting it or changing any playlist. This applies regardless of any approval settings at the affected locations. The action is reversible, and the underlying rotation is left untouched. Auth: Bearer token. Permission: `schedule.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/recall/schedule/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/recall/schedule/{id}/restore Restore a recalled schedule Put a recalled schedule back on the screens it was removed from. Nothing was changed by the recall, so playback resumes exactly as it was before. Auth: Bearer token. Permission: `schedule.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/recall/schedule/{id}/restore" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Recycle bin Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/recycle-bin List deleted items List every deleted item across your workspace, including media, playlists, schedules, layouts, creatives, apps, and data sources. Each entry includes the deletion time and size, so items can be restored or permanently erased within the 30-day recovery window. Deleted screens are not included; screens have their own recovery endpoints. Auth: Bearer token. ```bash curl "https://api.brixsignage.com/v1/recycle-bin" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/recycle-bin/{kind}/{id}/purge Permanently delete an item Permanently erase one deleted item before the 30-day recovery window would otherwise remove it. For media, this also deletes the stored file. This action cannot be undone. Auth: Bearer token. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `kind` | path | string | yes | Identifier for kind. | | `id` | path | string | yes | Identifier for id. | ```bash curl -X POST "https://api.brixsignage.com/v1/recycle-bin/{kind}/{id}/purge" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Roles Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/roles List roles Returns the workspace's roles, both built-in and custom, with the permissions each one carries. **Notes.** - Not paginated. Auth: Bearer token. Permission: `permission-group.view`. ```bash curl "https://api.brixsignage.com/v1/roles" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of Role | | | `data[].id` | string | | | `data[].name` | string | | | `data[].color` | string \| null | Display style for the role chip. | | `data[].description` | string | Empty string when not set. | | `data[].builtIn` | boolean | A role that ships with the product. It cannot be deleted, and its permissions, features and scope cannot be changed. | | `data[].permissions` | "all" \| array of string | `resource.action` permissions, or `"all"` (every permission, now and later). | | `data[].features` | "all" \| array of string | Feature keys the role unlocks, or `"all"`. | | `data[].scope` | object \| object | Where the role applies: the whole workspace, or only the listed locations. | | `data[].character` | string \| null | A decorative label. Not used for access. | | `data[].memberCount` | integer | Role assignments that use this role (one per person per location). | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | 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/roles Create role Creates a custom role. Provide `name` and `permissions`, a list of `resource.action` strings or `"all"`. You cannot create a role that holds permissions you do not hold yourself. **Notes.** - `permissions`, `features` and `scope` are stored as sent: the handler does not check their shape, so a malformed value is stored and read back as it was written. - `builtIn` in the body is ignored. Auth: Bearer token. Permission: `permission-group.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Required on create. | | `color` | string | no | | | `description` | string | no | | | `permissions` | "all" \| array of string | no | You must hold every permission you grant, workspace-wide. Default `[]`. | | `features` | "all" \| array of string | no | Default `[]`. | | `scope` | object \| object | no | Default `{ "kind": "workspace" }`. | | `character` | string \| null | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/roles" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Store manager","permissions":["screen.view","screen.cast","media.view","media.create"]}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Role | A named set of permissions assigned to people at locations. | | `data.id` | string | | | `data.name` | string | | | `data.color` | string \| null | Display style for the role chip. | | `data.description` | string | Empty string when not set. | | `data.builtIn` | boolean | A role that ships with the product. It cannot be deleted, and its permissions, features and scope cannot be changed. | | `data.permissions` | "all" \| array of string | `resource.action` permissions, or `"all"` (every permission, now and later). | | `data.features` | "all" \| array of string | Feature keys the role unlocks, or `"all"`. | | `data.scope` | object \| object | Where the role applies: the whole workspace, or only the listed locations. | | `data.character` | string \| null | A decorative label. Not used for access. | | `data.memberCount` | integer | Role assignments that use this role (one per person per location). | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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: `permissions` holds a permission you do not hold workspace-wide. | 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` 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/roles/{id} Get role Returns one role with its full list of permissions. Auth: Bearer token. Permission: `permission-group.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Role id. | ```bash curl "https://api.brixsignage.com/v1/roles/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Role | A named set of permissions assigned to people at locations. | | `data.id` | string | | | `data.name` | string | | | `data.color` | string \| null | Display style for the role chip. | | `data.description` | string | Empty string when not set. | | `data.builtIn` | boolean | A role that ships with the product. It cannot be deleted, and its permissions, features and scope cannot be changed. | | `data.permissions` | "all" \| array of string | `resource.action` permissions, or `"all"` (every permission, now and later). | | `data.features` | "all" \| array of string | Feature keys the role unlocks, or `"all"`. | | `data.scope` | object \| object | Where the role applies: the whole workspace, or only the listed locations. | | `data.character` | string \| null | A decorative label. Not used for access. | | `data.memberCount` | integer | Role assignments that use this role (one per person per location). | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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 such role in this workspace (or it is deleted). | 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/roles/{id} Update role Updates a role. The same rule as creating a role applies: you cannot add or remove a permission you do not hold yourself. Only a signed-in account owner can take full ownership away from a role that people hold; an API key cannot. On a built-in role, only the name, color and description change; its permissions, features and scope stay fixed. **Notes.** - `permissions`, `features` and `scope` are stored as sent: the handler does not check their shape, so a malformed value is stored and read back as it was written. - `builtIn` in the body is ignored. - On a built-in role, `permissions`, `features` and `scope` are ignored without an error; only `name`, `color`, `description` and `character` change. Auth: Bearer token. Permission: `permission-group.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Role id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Required on create. | | `color` | string | no | | | `description` | string | no | | | `permissions` | "all" \| array of string | no | You must hold every permission you grant, workspace-wide. Default `[]`. | | `features` | "all" \| array of string | no | Default `[]`. | | `scope` | object \| object | no | Default `{ "kind": "workspace" }`. | | `character` | string \| null | no | | ```bash curl -X PATCH "https://api.brixsignage.com/v1/roles/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Role | A named set of permissions assigned to people at locations. | | `data.id` | string | | | `data.name` | string | | | `data.color` | string \| null | Display style for the role chip. | | `data.description` | string | Empty string when not set. | | `data.builtIn` | boolean | A role that ships with the product. It cannot be deleted, and its permissions, features and scope cannot be changed. | | `data.permissions` | "all" \| array of string | `resource.action` permissions, or `"all"` (every permission, now and later). | | `data.features` | "all" \| array of string | Feature keys the role unlocks, or `"all"`. | | `data.scope` | object \| object | Where the role applies: the whole workspace, or only the listed locations. | | `data.character` | string \| null | A decorative label. Not used for access. | | `data.memberCount` | integer | Role assignments that use this role (one per person per location). | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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: You would add or remove a permission you do not hold workspace-wide, or change `scope` of a role whose permissions you do not hold workspace-wide; `owner_required`: the change takes full ownership away from a role people hold, and the caller is not a signed-in owner (an API key never is). | 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 role 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 409: `last_owner_role`: this is the only role with full ownership. | 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/roles/{id} Delete role Soft-deletes a custom role. The role can later be restored. Refuses the request while anyone is still assigned to the role. Auth: Bearer token. Permission: `permission-group.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Role id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/roles/{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 role 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 409: `protected` (a built-in role), `last_owner_role`, or `in_use` (people still hold the role). | 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/roles/{id}/restore Restore deleted role Restores a previously deleted custom role. Requires the same permission needed to delete a role. **Notes.** - Answers the whole restored role, not the `{ id, restored }` acknowledgement other restores use. Auth: Bearer token. Permission: `permission-group.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Role id. | ```bash curl -X POST "https://api.brixsignage.com/v1/roles/{id}/restore" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Role | A named set of permissions assigned to people at locations. | | `data.id` | string | | | `data.name` | string | | | `data.color` | string \| null | Display style for the role chip. | | `data.description` | string | Empty string when not set. | | `data.builtIn` | boolean | A role that ships with the product. It cannot be deleted, and its permissions, features and scope cannot be changed. | | `data.permissions` | "all" \| array of string | `resource.action` permissions, or `"all"` (every permission, now and later). | | `data.features` | "all" \| array of string | Feature keys the role unlocks, or `"all"`. | | `data.scope` | object \| object | Where the role applies: the whole workspace, or only the listed locations. | | `data.character` | string \| null | A decorative label. Not used for access. | | `data.memberCount` | integer | Role assignments that use this role (one per person per location). | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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 such role 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 409: `not_deleted`: the role is not deleted. | 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. | ## Schedules Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/schedules List schedules List the workspace's schedules, including their dayparting blocks. Auth: Bearer token. Permission: `schedule.view`. ```bash curl "https://api.brixsignage.com/v1/schedules" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of Schedule | | | `data[].id` | string | Schedule id. | | `data[].spaceId` | string | | | `data[].nodeId` | string \| null | Home location (null = workspace root). | | `data[].name` | string | | | `data[].fallbackName` | string | Label of the content played between blocks. | | `data[].fallbackMode` | "content" \| "off" | Between blocks: play the fallback content, or turn the screens off. | | `data[].fallbackContentKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" \| null | | | `data[].fallbackContentId` | string \| null | | | `data[].playsSolely` | boolean | | | `data[].timeBasis` | "device" \| "cms" | Whose clock the block times use: each screen's local time, or the workspace's. | | `data[].startsAt` | string \| null | `YYYY-MM-DD` or ISO-8601; null = no start bound. | | `data[].expiresAt` | string \| null | | | `data[].approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | | | `data[].recalledAt` | string \| null | Set while the schedule is recalled (withheld from every screen). | | `data[].recalledBy` | string \| null | | | `data[].approvedSnapshot` | string \| null | JSON of the blocks as last approved (approval workflow). | | `data[].importSourceId` | string \| null | | | `data[].usedByScreenCount` | integer | Screens assigned this schedule. | | `data[].fallbackContentRef` | object \| null | | | `data[].fallbackThumbnailUrl` | string \| null | | | `data[].blocks` | array of ScheduleBlock | | | `data[].blocks[].id` | string | Schedule block id. | | `data[].blocks[].label` | string | | | `data[].blocks[].daysOfWeek` | array of integer | 0 = Sunday … 6 = Saturday. Empty = every day. | | `data[].blocks[].startTime` | string | 24-hour `HH:MM`. | | `data[].blocks[].endTime` | string | 24-hour `HH:MM`; `00:00` = midnight. An end before the start wraps overnight. | | `data[].blocks[].priority` | integer | 0–1000; higher wins where blocks overlap. | | `data[].blocks[].startDate` | string \| null | `YYYY-MM-DD`, or null for no start bound. | | `data[].blocks[].endDate` | string \| null | | | `data[].blocks[].repeatEveryWeeks` | integer | 1–52. | | `data[].blocks[].refKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | | | `data[].blocks[].refId` | string | | | `data[].blocks[].screensOff` | boolean | The block turns the screens off instead of playing `ref`. | | `data[].blocks[].contentName` | string | Current name of the content, `Screens off`, or `(missing content)` when the ref no longer resolves. | | `data[].blocks[].thumbnailUrl` | string \| null | | | `data[].blocks[].position` | 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/schedules Create a schedule with a `name` and an optional `nodeId` and other fields. Safe to retry with an idempotency key. Creates an empty schedule. Add its dayparts with POST /v1/schedules/{id}/blocks, then assign it to screens. **Notes.** - Create returns the stored row plus `blocks: []`, not the composed schedule the other schedule routes return: no `usedByScreenCount`, `fallbackContentRef`, `fallbackThumbnailUrl`, `recalledAt`, `approvedSnapshot` or `importSourceId`. Auth: Bearer token. Permission: `schedule.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Required. | | `nodeId` | string | no | Home location. Default: the caller's own location. | | `fallbackName` | string | no | | | `fallbackContentKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | no | | | `fallbackContentId` | string | no | | | `fallbackMode` | "content" \| "off" | no | | | `playsSolely` | boolean | no | | | `timeBasis` | "device" \| "cms" | no | | | `startsAt` | string \| null | no | `YYYY-MM-DD` or ISO-8601. | | `expiresAt` | string \| null | no | `YYYY-MM-DD` or ISO-8601. | ```bash curl -X POST "https://api.brixsignage.com/v1/schedules" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Weekday dayparts","fallbackContentKind":"playlist","fallbackContentId":"pl_2a3b4c5d6e7f8a9b","startsAt":"2026-10-01"}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Schedule id. | | `data.spaceId` | string | | | `data.nodeId` | string \| null | Home location (null = workspace root). | | `data.name` | string | | | `data.fallbackName` | string | Label of the content played between blocks. | | `data.fallbackMode` | "content" \| "off" | Between blocks: play the fallback content, or turn the screens off. | | `data.fallbackContentKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" \| null | | | `data.fallbackContentId` | string \| null | | | `data.playsSolely` | boolean | | | `data.timeBasis` | "device" \| "cms" | Whose clock the block times use: each screen's local time, or the workspace's. | | `data.startsAt` | string \| null | `YYYY-MM-DD` or ISO-8601; null = no start bound. | | `data.expiresAt` | string \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.blocks` | array of ScheduleBlock | Always empty on create. | | `data.blocks[].id` | string | Schedule block id. | | `data.blocks[].label` | string | | | `data.blocks[].daysOfWeek` | array of integer | 0 = Sunday … 6 = Saturday. Empty = every day. | | `data.blocks[].startTime` | string | 24-hour `HH:MM`. | | `data.blocks[].endTime` | string | 24-hour `HH:MM`; `00:00` = midnight. An end before the start wraps overnight. | | `data.blocks[].priority` | integer | 0–1000; higher wins where blocks overlap. | | `data.blocks[].startDate` | string \| null | `YYYY-MM-DD`, or null for no start bound. | | `data.blocks[].endDate` | string \| null | | | `data.blocks[].repeatEveryWeeks` | integer | 1–52. | | `data.blocks[].refKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | | | `data.blocks[].refId` | string | | | `data.blocks[].screensOff` | boolean | The block turns the screens off instead of playing `ref`. | | `data.blocks[].contentName` | string | Current name of the content, `Screens off`, or `(missing content)` when the ref no longer resolves. | | `data.blocks[].thumbnailUrl` | string \| null | | | `data.blocks[].position` | integer | | ```json { "data": { "id": "sch_6f7a8b9c0d1e2f3a", "spaceId": "space_1a2b3c4d5e6f7a8b", "nodeId": null, "name": "Weekday dayparts", "fallbackName": "Default Loop", "fallbackContentKind": "playlist", "fallbackContentId": "pl_2a3b4c5d6e7f8a9b", "fallbackMode": "content", "playsSolely": false, "timeBasis": "device", "startsAt": "2026-10-01", "expiresAt": null, "approvalState": "draft", "createdAt": "2026-09-28T09:00:00.000Z", "updatedAt": "2026-09-28T09:00:00.000Z", "deletedAt": null, "blocks": [] } } ``` 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 fallback content is not shared to the schedule'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: The fallback content or location does not exist 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: `name` missing, or an unparseable `startsAt` / `expiresAt`. | 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/schedules/{id} Get a schedule Retrieve one schedule, including its ordered blocks, with each block's time window, recurrence, content reference, and priority. Auth: Bearer token. Permission: `schedule.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Schedule id. | ```bash curl "https://api.brixsignage.com/v1/schedules/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Schedule id. | | `data.spaceId` | string | | | `data.nodeId` | string \| null | Home location (null = workspace root). | | `data.name` | string | | | `data.fallbackName` | string | Label of the content played between blocks. | | `data.fallbackMode` | "content" \| "off" | Between blocks: play the fallback content, or turn the screens off. | | `data.fallbackContentKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" \| null | | | `data.fallbackContentId` | string \| null | | | `data.playsSolely` | boolean | | | `data.timeBasis` | "device" \| "cms" | Whose clock the block times use: each screen's local time, or the workspace's. | | `data.startsAt` | string \| null | `YYYY-MM-DD` or ISO-8601; null = no start bound. | | `data.expiresAt` | string \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.recalledAt` | string \| null | Set while the schedule is recalled (withheld from every screen). | | `data.recalledBy` | string \| null | | | `data.approvedSnapshot` | string \| null | JSON of the blocks as last approved (approval workflow). | | `data.importSourceId` | string \| null | | | `data.usedByScreenCount` | integer | Screens assigned this schedule. | | `data.fallbackContentRef` | object \| null | | | `data.fallbackThumbnailUrl` | string \| null | | | `data.blocks` | array of ScheduleBlock | | | `data.blocks[].id` | string | Schedule block id. | | `data.blocks[].label` | string | | | `data.blocks[].daysOfWeek` | array of integer | 0 = Sunday … 6 = Saturday. Empty = every day. | | `data.blocks[].startTime` | string | 24-hour `HH:MM`. | | `data.blocks[].endTime` | string | 24-hour `HH:MM`; `00:00` = midnight. An end before the start wraps overnight. | | `data.blocks[].priority` | integer | 0–1000; higher wins where blocks overlap. | | `data.blocks[].startDate` | string \| null | `YYYY-MM-DD`, or null for no start bound. | | `data.blocks[].endDate` | string \| null | | | `data.blocks[].repeatEveryWeeks` | integer | 1–52. | | `data.blocks[].refKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | | | `data.blocks[].refId` | string | | | `data.blocks[].screensOff` | boolean | The block turns the screens off instead of playing `ref`. | | `data.blocks[].contentName` | string | Current name of the content, `Screens off`, or `(missing content)` when the ref no longer resolves. | | `data.blocks[].thumbnailUrl` | string \| null | | | `data.blocks[].position` | integer | | | `data.requiresApproval` | boolean | The schedule's location requires approval before edits air. | 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 schedule 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/schedules/{id} Update a schedule Edit a schedule's own fields, such as name, node, timezone, and default behaviour. Blocks are managed through the separate /blocks routes. Partial update of schedule-level fields. A playback change to an approved schedule returns it to `draft`. Auth: Bearer token. Permission: `schedule.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Schedule id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Required. | | `nodeId` | string \| null | no | Move to another location (needs schedule.edit there too). | | `fallbackName` | string | no | | | `fallbackContentKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" \| null | no | | | `fallbackContentId` | string \| null | no | | | `fallbackMode` | "content" \| "off" | no | | | `playsSolely` | boolean | no | | | `timeBasis` | "device" \| "cms" | no | | | `startsAt` | string \| null | no | `YYYY-MM-DD` or ISO-8601. | | `expiresAt` | string \| null | no | `YYYY-MM-DD` or ISO-8601. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/schedules/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Schedule | A schedule with its timed blocks. | | `data.id` | string | Schedule id. | | `data.spaceId` | string | | | `data.nodeId` | string \| null | Home location (null = workspace root). | | `data.name` | string | | | `data.fallbackName` | string | Label of the content played between blocks. | | `data.fallbackMode` | "content" \| "off" | Between blocks: play the fallback content, or turn the screens off. | | `data.fallbackContentKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" \| null | | | `data.fallbackContentId` | string \| null | | | `data.playsSolely` | boolean | | | `data.timeBasis` | "device" \| "cms" | Whose clock the block times use: each screen's local time, or the workspace's. | | `data.startsAt` | string \| null | `YYYY-MM-DD` or ISO-8601; null = no start bound. | | `data.expiresAt` | string \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.recalledAt` | string \| null | Set while the schedule is recalled (withheld from every screen). | | `data.recalledBy` | string \| null | | | `data.approvedSnapshot` | string \| null | JSON of the blocks as last approved (approval workflow). | | `data.importSourceId` | string \| null | | | `data.usedByScreenCount` | integer | Screens assigned this schedule. | | `data.fallbackContentRef` | object \| null | | | `data.fallbackThumbnailUrl` | string \| null | | | `data.blocks` | array of ScheduleBlock | | | `data.blocks[].id` | string | Schedule block id. | | `data.blocks[].label` | string | | | `data.blocks[].daysOfWeek` | array of integer | 0 = Sunday … 6 = Saturday. Empty = every day. | | `data.blocks[].startTime` | string | 24-hour `HH:MM`. | | `data.blocks[].endTime` | string | 24-hour `HH:MM`; `00:00` = midnight. An end before the start wraps overnight. | | `data.blocks[].priority` | integer | 0–1000; higher wins where blocks overlap. | | `data.blocks[].startDate` | string \| null | `YYYY-MM-DD`, or null for no start bound. | | `data.blocks[].endDate` | string \| null | | | `data.blocks[].repeatEveryWeeks` | integer | 1–52. | | `data.blocks[].refKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | | | `data.blocks[].refId` | string | | | `data.blocks[].screensOff` | boolean | The block turns the screens off instead of playing `ref`. | | `data.blocks[].contentName` | string | Current name of the content, `Screens off`, or `(missing content)` when the ref no longer resolves. | | `data.blocks[].thumbnailUrl` | string \| null | | | `data.blocks[].position` | 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: Moving to a location you cannot edit, or fallback content not shared there. | 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 schedule, fallback content or 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 422: Unparseable `startsAt` / `expiresAt`. | 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/schedules/{id} Delete a schedule to the recycle bin. Screens using it fall back to their assigned content. Soft-deletes into the recycle bin. Screens on it fall back to their other content. Auth: Bearer token. Permission: `schedule.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Schedule id. | | `force` | query | "true" | no | Delete even when shared; the shares are removed too. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/schedules/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.deleted` | true | | | `data.sharesRemoved` | 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: No such schedule. | 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: `content_shared`: the schedule is shared; the body carries `shareCount`. Retry with `?force=true`. | 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/schedules/{id}/blocks Add a block to a schedule Add one dayparting block to a schedule with `refKind`, `refId`, `days`, `startTime`, `endTime`, and an optional `priority` and other fields. **Notes.** - The returned schedule is composed from the row read BEFORE the edit, so `approvalState` / `updatedAt` can lag the edit by one read (the blocks are current). Auth: Bearer token. Permission: `schedule.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Schedule id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `refKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | yes | Required on create. | | `refId` | string | yes | Required on create. | | `label` | string | no | Default "Block". | | `daysOfWeek` | array of integer | no | 0 = Sunday. Default: every day. | | `startTime` | string | no | 24-hour `HH:MM`. Default `09:00`. | | `endTime` | string | no | 24-hour `HH:MM`; `24:00` is accepted as midnight. Default `17:00`. | | `priority` | integer | no | Clamped to 0–1000. | | `startDate` | string \| null | no | `YYYY-MM-DD`. Any other value is stored as null. | | `endDate` | string \| null | no | | | `repeatEveryWeeks` | integer | no | Clamped to 1–52. | | `screensOff` | boolean | no | | ```bash curl -X POST "https://api.brixsignage.com/v1/schedules/{id}/blocks" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Schedule | A schedule with its timed blocks. | | `data.id` | string | Schedule id. | | `data.spaceId` | string | | | `data.nodeId` | string \| null | Home location (null = workspace root). | | `data.name` | string | | | `data.fallbackName` | string | Label of the content played between blocks. | | `data.fallbackMode` | "content" \| "off" | Between blocks: play the fallback content, or turn the screens off. | | `data.fallbackContentKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" \| null | | | `data.fallbackContentId` | string \| null | | | `data.playsSolely` | boolean | | | `data.timeBasis` | "device" \| "cms" | Whose clock the block times use: each screen's local time, or the workspace's. | | `data.startsAt` | string \| null | `YYYY-MM-DD` or ISO-8601; null = no start bound. | | `data.expiresAt` | string \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.recalledAt` | string \| null | Set while the schedule is recalled (withheld from every screen). | | `data.recalledBy` | string \| null | | | `data.approvedSnapshot` | string \| null | JSON of the blocks as last approved (approval workflow). | | `data.importSourceId` | string \| null | | | `data.usedByScreenCount` | integer | Screens assigned this schedule. | | `data.fallbackContentRef` | object \| null | | | `data.fallbackThumbnailUrl` | string \| null | | | `data.blocks` | array of ScheduleBlock | | | `data.blocks[].id` | string | Schedule block id. | | `data.blocks[].label` | string | | | `data.blocks[].daysOfWeek` | array of integer | 0 = Sunday … 6 = Saturday. Empty = every day. | | `data.blocks[].startTime` | string | 24-hour `HH:MM`. | | `data.blocks[].endTime` | string | 24-hour `HH:MM`; `00:00` = midnight. An end before the start wraps overnight. | | `data.blocks[].priority` | integer | 0–1000; higher wins where blocks overlap. | | `data.blocks[].startDate` | string \| null | `YYYY-MM-DD`, or null for no start bound. | | `data.blocks[].endDate` | string \| null | | | `data.blocks[].repeatEveryWeeks` | integer | 1–52. | | `data.blocks[].refKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | | | `data.blocks[].refId` | string | | | `data.blocks[].screensOff` | boolean | The block turns the screens off instead of playing `ref`. | | `data.blocks[].contentName` | string | Current name of the content, `Screens off`, or `(missing content)` when the ref no longer resolves. | | `data.blocks[].thumbnailUrl` | string \| null | | | `data.blocks[].position` | 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: `not_shared`: the content is not shared to the schedule'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 schedule, or the content does not exist 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: Missing `refKind`/`refId`, or a malformed time. | 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/schedules/{id}/blocks/{blockId} Update a schedule block Edit one dayparting block's time window, days, priority, or content reference. Partial: omitted fields keep their stored value. **Notes.** - The returned schedule is composed from the row read BEFORE the edit, so `approvalState` / `updatedAt` can lag the edit by one read (the blocks are current). Auth: Bearer token. Permission: `schedule.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Schedule id. | | `blockId` | path | string | yes | Block id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `refKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | no | Required on create. | | `refId` | string | no | Required on create. | | `label` | string | no | Default "Block". | | `daysOfWeek` | array of integer | no | 0 = Sunday. Default: every day. | | `startTime` | string | no | 24-hour `HH:MM`. Default `09:00`. | | `endTime` | string | no | 24-hour `HH:MM`; `24:00` is accepted as midnight. Default `17:00`. | | `priority` | integer | no | Clamped to 0–1000. | | `startDate` | string \| null | no | `YYYY-MM-DD`. Any other value is stored as null. | | `endDate` | string \| null | no | | | `repeatEveryWeeks` | integer | no | Clamped to 1–52. | | `screensOff` | boolean | no | | ```bash curl -X PATCH "https://api.brixsignage.com/v1/schedules/{id}/blocks/{blockId}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Schedule | A schedule with its timed blocks. | | `data.id` | string | Schedule id. | | `data.spaceId` | string | | | `data.nodeId` | string \| null | Home location (null = workspace root). | | `data.name` | string | | | `data.fallbackName` | string | Label of the content played between blocks. | | `data.fallbackMode` | "content" \| "off" | Between blocks: play the fallback content, or turn the screens off. | | `data.fallbackContentKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" \| null | | | `data.fallbackContentId` | string \| null | | | `data.playsSolely` | boolean | | | `data.timeBasis` | "device" \| "cms" | Whose clock the block times use: each screen's local time, or the workspace's. | | `data.startsAt` | string \| null | `YYYY-MM-DD` or ISO-8601; null = no start bound. | | `data.expiresAt` | string \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.recalledAt` | string \| null | Set while the schedule is recalled (withheld from every screen). | | `data.recalledBy` | string \| null | | | `data.approvedSnapshot` | string \| null | JSON of the blocks as last approved (approval workflow). | | `data.importSourceId` | string \| null | | | `data.usedByScreenCount` | integer | Screens assigned this schedule. | | `data.fallbackContentRef` | object \| null | | | `data.fallbackThumbnailUrl` | string \| null | | | `data.blocks` | array of ScheduleBlock | | | `data.blocks[].id` | string | Schedule block id. | | `data.blocks[].label` | string | | | `data.blocks[].daysOfWeek` | array of integer | 0 = Sunday … 6 = Saturday. Empty = every day. | | `data.blocks[].startTime` | string | 24-hour `HH:MM`. | | `data.blocks[].endTime` | string | 24-hour `HH:MM`; `00:00` = midnight. An end before the start wraps overnight. | | `data.blocks[].priority` | integer | 0–1000; higher wins where blocks overlap. | | `data.blocks[].startDate` | string \| null | `YYYY-MM-DD`, or null for no start bound. | | `data.blocks[].endDate` | string \| null | | | `data.blocks[].repeatEveryWeeks` | integer | 1–52. | | `data.blocks[].refKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | | | `data.blocks[].refId` | string | | | `data.blocks[].screensOff` | boolean | The block turns the screens off instead of playing `ref`. | | `data.blocks[].contentName` | string | Current name of the content, `Screens off`, or `(missing content)` when the ref no longer resolves. | | `data.blocks[].thumbnailUrl` | string \| null | | | `data.blocks[].position` | 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: `not_shared`: the content is not shared to the schedule'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 schedule or block. | 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: A malformed time. | 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/schedules/{id}/blocks/{blockId} Remove a schedule block Remove one dayparting block from a schedule. Removes the block (a hard delete of the child row) and re-packs positions. **Notes.** - The returned schedule is composed from the row read BEFORE the edit, so `approvalState` / `updatedAt` can lag the edit by one read (the blocks are current). - An unknown `blockId` is not an error: the route answers 200 with the unchanged schedule. Auth: Bearer token. Permission: `schedule.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Schedule id. | | `blockId` | path | string | yes | Block id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/schedules/{id}/blocks/{blockId}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | Schedule | A schedule with its timed blocks. | | `data.id` | string | Schedule id. | | `data.spaceId` | string | | | `data.nodeId` | string \| null | Home location (null = workspace root). | | `data.name` | string | | | `data.fallbackName` | string | Label of the content played between blocks. | | `data.fallbackMode` | "content" \| "off" | Between blocks: play the fallback content, or turn the screens off. | | `data.fallbackContentKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" \| null | | | `data.fallbackContentId` | string \| null | | | `data.playsSolely` | boolean | | | `data.timeBasis` | "device" \| "cms" | Whose clock the block times use: each screen's local time, or the workspace's. | | `data.startsAt` | string \| null | `YYYY-MM-DD` or ISO-8601; null = no start bound. | | `data.expiresAt` | string \| null | | | `data.approvalState` | "draft" \| "pending" \| "approved" \| "rejected" | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | | | `data.recalledAt` | string \| null | Set while the schedule is recalled (withheld from every screen). | | `data.recalledBy` | string \| null | | | `data.approvedSnapshot` | string \| null | JSON of the blocks as last approved (approval workflow). | | `data.importSourceId` | string \| null | | | `data.usedByScreenCount` | integer | Screens assigned this schedule. | | `data.fallbackContentRef` | object \| null | | | `data.fallbackThumbnailUrl` | string \| null | | | `data.blocks` | array of ScheduleBlock | | | `data.blocks[].id` | string | Schedule block id. | | `data.blocks[].label` | string | | | `data.blocks[].daysOfWeek` | array of integer | 0 = Sunday … 6 = Saturday. Empty = every day. | | `data.blocks[].startTime` | string | 24-hour `HH:MM`. | | `data.blocks[].endTime` | string | 24-hour `HH:MM`; `00:00` = midnight. An end before the start wraps overnight. | | `data.blocks[].priority` | integer | 0–1000; higher wins where blocks overlap. | | `data.blocks[].startDate` | string \| null | `YYYY-MM-DD`, or null for no start bound. | | `data.blocks[].endDate` | string \| null | | | `data.blocks[].repeatEveryWeeks` | integer | 1–52. | | `data.blocks[].refKind` | "media" \| "playlist" \| "app" \| "layout" \| "creative" \| "signage" | | | `data.blocks[].refId` | string | | | `data.blocks[].screensOff` | boolean | The block turns the screens off instead of playing `ref`. | | `data.blocks[].contentName` | string | Current name of the content, `Screens off`, or `(missing content)` when the ref no longer resolves. | | `data.blocks[].thumbnailUrl` | string \| null | | | `data.blocks[].position` | 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: No such schedule. | 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/schedules/{id}/restore Restore a deleted schedule so its block layout returns to the schedule library. **Notes.** - Also restores the cross-workspace shares that were removed when the schedule was deleted. Auth: Bearer token. Permission: `schedule.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Schedule id. | ```bash curl -X POST "https://api.brixsignage.com/v1/schedules/{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 such schedule 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 409: `not_deleted`: the schedule is not in the recycle bin. | 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. | ## Screen groups Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/screen-groups List screen groups List the screen groups in your workspace. Each group includes the number of screens in it and the IDs of the screens you have access to see. Auth: Bearer token. Permission: `screen.view`. ```bash curl "https://api.brixsignage.com/v1/screen-groups" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of ScreenGroup | | | `data[].id` | string | Screen group id. | | `data[].name` | string | | | `data[].description` | string \| null | | | `data[].screenCount` | integer | Members the caller can see. | | `data[].screenIds` | array of string | Member screen ids the caller can see, oldest membership first. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | 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/screen-groups Create a screen group Create a named group of screens. Optionally pass `screenIds` to add screens to the group at creation time. Auth: Bearer token. Permission: `screen.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Unique in the workspace (case-insensitive). Trimmed; longer names are cut at 120. | | `description` | string | no | Trimmed; cut at 500 characters. | | `screenIds` | array of string | no | Seed members. Ids you cannot edit are skipped. | ```bash curl -X POST "https://api.brixsignage.com/v1/screen-groups" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ScreenGroup | A named, saved set of screens — the unit for bulk commands and casts. | | `data.id` | string | Screen group id. | | `data.name` | string | | | `data.description` | string \| null | | | `data.screenCount` | integer | Members the caller can see. | | `data.screenIds` | array of string | Member screen ids the caller can see, oldest membership first. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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 409: `name_taken`: a group with this name exists. | 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` missing, or `screenIds` empty / over 5000. | 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/screen-groups/{id} Get a screen group Get one screen group and the screens in it. Requesting a group that belongs to a different workspace returns a not-found error. Auth: Bearer token. Permission: `screen.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Screen group id. | ```bash curl "https://api.brixsignage.com/v1/screen-groups/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ScreenGroup | A named, saved set of screens — the unit for bulk commands and casts. | | `data.id` | string | Screen group id. | | `data.name` | string | | | `data.description` | string \| null | | | `data.screenCount` | integer | Members the caller can see. | | `data.screenIds` | array of string | Member screen ids the caller can see, oldest membership first. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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 such 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 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/screen-groups/{id} Rename a screen group Update a screen group's name or description. A name already used by another group in your workspace is refused with a `name_taken` conflict error. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Screen group id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `description` | string \| null | no | | ```bash curl -X PATCH "https://api.brixsignage.com/v1/screen-groups/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ScreenGroup | A named, saved set of screens — the unit for bulk commands and casts. | | `data.id` | string | Screen group id. | | `data.name` | string | | | `data.description` | string \| null | | | `data.screenCount` | integer | Members the caller can see. | | `data.screenIds` | array of string | Member screen ids the caller can see, oldest membership first. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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 group 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: `name_taken`. | 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 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. | ### DELETE /v1/screen-groups/{id} Delete a screen group. The screens in the group are not affected, and the group's membership is kept so it can be fully restored with POST /v1/screen-groups/:id/restore. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Screen group id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/screen-groups/{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 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/screen-groups/{id}/restore Restore a deleted screen group along with its original screen membership. A group that is not currently deleted is refused with a conflict error. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Screen group id. | ```bash curl -X POST "https://api.brixsignage.com/v1/screen-groups/{id}/restore" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | ScreenGroup | A named, saved set of screens — the unit for bulk commands and casts. | | `data.id` | string | Screen group id. | | `data.name` | string | | | `data.description` | string \| null | | | `data.screenCount` | integer | Members the caller can see. | | `data.screenIds` | array of string | Member screen ids the caller can see, oldest membership first. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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 group 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: `not_deleted`, or a live group has the same name. | 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/screen-groups/{id}/screens Add screens to a group. Adding a screen already in the group has no additional effect. A screen from another workspace, a deleted screen, or a screen outside your organization scope is skipped and counted in the response, but not identified individually. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Screen group id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `screenIds` | array of string | yes | Screen ids (1–5000). Ids you cannot edit, or that do not exist, are skipped. | ```bash curl -X POST "https://api.brixsignage.com/v1/screen-groups/{id}/screens" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.added` | integer | Screens newly added (existing members are not counted). | | `data.skipped` | integer | Requested ids that do not exist or that the caller cannot edit. | | `data.screenIds` | array of string | The screens newly added. | 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 group. | 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: `screenIds` empty or over 5000. | 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/screen-groups/{id}/screens/{screenId} Remove a screen from a group Remove one screen from a group. Returns a not-found error if the screen is not currently in the group. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Screen group id. | | `screenId` | path | string | yes | Screen id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/screen-groups/{id}/screens/{screenId}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.removed` | integer | 1 when the screen was a member, else 0. | | `data.screenIds` | array of string | The remaining members the caller can see. | 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/screen-groups/{id}/screens/remove Remove screens from a group Remove one or more screens from a group. This only removes the screens from the group; the screens themselves are not affected. Removing a screen that is not in the group has no effect. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Screen group id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `screenIds` | array of string | yes | Screen ids (1–5000). Ids you cannot edit, or that do not exist, are skipped. | ```bash curl -X POST "https://api.brixsignage.com/v1/screen-groups/{id}/screens/remove" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.removed` | integer | | | `data.skipped` | integer | Requested ids that do not exist or that the caller cannot edit. | | `data.screenIds` | array of string | The screens removed. | 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 group. | 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: `screenIds` empty or over 5000. | 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. | ## 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. | ## Screenshots Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/screenshots List screenshots List screen captures uploaded by your screens. Filter by a specific screen using the `screenId` query parameter. The image bytes for each screenshot are retrieved separately from GET /v1/screenshots/:id/file. Auth: Bearer token. Permission: `screen.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `screenId` | query | string | no | Only this screen's captures. | ```bash curl "https://api.brixsignage.com/v1/screenshots" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].id` | string | | | `data[].screenId` | string | | | `data[].screenName` | string | | | `data[].url` | string | Download path (GET /v1/screenshots/{id}/file). | | `data[].capturedAt` | string | ISO-8601 timestamp (UTC). | | `data[].confirmedAt` | string \| null | | | `data[].width` | integer \| null | | | `data[].height` | integer \| null | | | `data[].sourceWidth` | integer \| null | | | `data[].sourceHeight` | integer \| null | | | `data[].sizeBytes` | integer | | | `data[].capturedBy` | "agent" \| "projection" \| "accessibility" \| "webview" \| null | | | `data[].belowNative` | boolean | Present and true when the capture is smaller than the panel's native resolution. | 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/screenshots/{id}/file Download a screenshot Download the image bytes of one screen capture. Auth: Bearer token. Permission: `screen.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Screenshot id. | ```bash curl "https://api.brixsignage.com/v1/screenshots/{id}/file" \ -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 screenshot with this id, or its file is gone. | 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. | ## Search Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/search Search media, playlists, screens and creatives Search across media, playlists, screens, and creatives using the `q` query parameter. Supports bare terms, prefix matching with `*`, the operators AND, OR, and NOT, and column filters such as `name:dashboard`. Results are scoped to the current space. Full-text search by name. Each list holds only kinds the caller may view (the others come back empty), best match first. A query under 2 characters returns empty lists. **Notes.** - The route is gated by `media.view` (the route-registry description says `screen.view`): a key without `media.view` is refused even if it could see screens. - A failing index query is swallowed and returns an empty list for that kind, so an empty result is not proof of no match. Auth: Bearer token. Permission: `media.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `q` | query | string | yes | Search text (2+ characters). The last word matches as a prefix. | | `limit` | query | integer | no | Hits per kind (default 10, max 20). | ```bash curl "https://api.brixsignage.com/v1/search" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.media` | array of object | | | `data.media[].id` | string | | | `data.media[].snippet` | string | Matched text with `…` around the hit. | | `data.playlists` | array of object | | | `data.playlists[].id` | string | | | `data.playlists[].snippet` | string | Matched text with `…` around the hit. | | `data.screens` | array of object | | | `data.screens[].id` | string | | | `data.screens[].snippet` | string | Matched text with `…` around the hit. | | `data.creatives` | array of object | | | `data.creatives[].id` | string | | | `data.creatives[].snippet` | string | Matched text with `…` around the hit. | 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. | ## Serial templates Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/serial-templates List serial command templates List saved RS232 serial command templates for your workspace, such as profiles for turning a display on or switching its input. Each template includes the exact bytes each command sends. **Notes.** - Not paginated. Only templates at locations where the key has `screen.view` are listed. Auth: Bearer token. Permission: `screen.view`. ```bash curl "https://api.brixsignage.com/v1/serial-templates" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of SerialTemplate | | | `data[].id` | string | Serial template id. | | `data[].spaceId` | string | | | `data[].name` | string | | | `data[].model` | string \| null | Free-text panel model (`Sony Bravia FW-series`). | | `data[].nodeId` | string \| null | Home location; null = workspace root. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].items` | array of object | | | `data[].items[].id` | string | Command id. Kept across edits, so saved buttons that name it keep working. | | `data[].items[].name` | string | | | `data[].items[].value` | string | The command: literal text (`ascii`) or hex digits (`hex`). | | `data[].items[].encoding` | "ascii" \| "hex" | | | `data[].items[].eol` | "none" \| "cr" \| "lf" \| "crlf" | Line ending added after the value. | | `data[].items[].hexPreview` | string | The exact bytes sent, as spaced hex (`2A 53 0A`). | 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/serial-templates Create a serial command template Create an RS232 command template. Provide a `name`, an optional `model`, an `items` array of commands each with `name`, `value`, `encoding` (`ascii` or `hex`), and `eol`, and an optional `nodeId`. Auth: Bearer token. Permission: `screen.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `model` | string \| null | no | | | `nodeId` | string \| null | no | Home location; null = workspace root. | | `items` | array of object | no | The commands, at most 200. On update the list replaces the stored one. | | `items[].id` | string | no | Keep an existing command's id; omit to get a new one. | | `items[].name` | string | yes | | | `items[].value` | string | yes | At most 512 characters, and it must give at least one byte. | | `items[].encoding` | "ascii" \| "hex" | no | Default `ascii`. An unknown value is read as `ascii`. | | `items[].eol` | "none" \| "cr" \| "lf" \| "crlf" | no | Default `none`. An unknown value is read as `none`. | ```bash curl -X POST "https://api.brixsignage.com/v1/serial-templates" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Sony Bravia","model":"FW-55BZ35","items":[{"name":"Power on","value":"*SCPOWR0000000000000001","encoding":"ascii","eol":"lf"}]}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | SerialTemplate | A saved set of RS232 commands for one panel model. | | `data.id` | string | Serial template id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.model` | string \| null | Free-text panel model (`Sony Bravia FW-series`). | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.items` | array of object | | | `data.items[].id` | string | Command id. Kept across edits, so saved buttons that name it keep working. | | `data.items[].name` | string | | | `data.items[].value` | string | The command: literal text (`ascii`) or hex digits (`hex`). | | `data.items[].encoding` | "ascii" \| "hex" | | | `data.items[].eol` | "none" \| "cr" \| "lf" \| "crlf" | Line ending added after the value. | | `data.items[].hexPreview` | string | The exact bytes sent, as spaced hex (`2A 53 0A`). | ```json { "data": { "id": "sertpl_2b3c4d5e6f7a8b9c", "spaceId": "space_1a2b3c4d5e6f7a8b", "name": "Sony Bravia", "model": "FW-55BZ35", "nodeId": null, "createdAt": "2026-09-28T09:00:00.000Z", "updatedAt": "2026-09-28T09:00:00.000Z", "items": [ { "id": "sti_3c4d5e6f7a8b9c0d", "name": "Power on", "value": "*SCPOWR0000000000000001", "encoding": "ascii", "eol": "lf", "hexPreview": "2A 53 43 50 4F 57 52 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 31 0A" } ] } } ``` 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: `name` is missing, `invalid_node` (the location is not in this workspace), or a command is not valid (no name, no value, too long, no bytes, a duplicate id, over 200 commands); `message` names 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/serial-templates/{id} Get a serial command template Get one RS232 command template, including a preview of the exact bytes each command will send to the device. Auth: Bearer token. Permission: `screen.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Serial template id. | ```bash curl "https://api.brixsignage.com/v1/serial-templates/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | SerialTemplate | A saved set of RS232 commands for one panel model. | | `data.id` | string | Serial template id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.model` | string \| null | Free-text panel model (`Sony Bravia FW-series`). | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.items` | array of object | | | `data.items[].id` | string | Command id. Kept across edits, so saved buttons that name it keep working. | | `data.items[].name` | string | | | `data.items[].value` | string | The command: literal text (`ascii`) or hex digits (`hex`). | | `data.items[].encoding` | "ascii" \| "hex" | | | `data.items[].eol` | "none" \| "cr" \| "lf" \| "crlf" | Line ending added after the value. | | `data.items[].hexPreview` | string | The exact bytes sent, as spaced hex (`2A 53 0A`). | 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 serial template 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/serial-templates/{id} Update a serial command template Update an RS232 command template's name, model, organization node, or list of commands. Existing command IDs are preserved, so anything referencing a specific command continues to work. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Serial template id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Required on create. Cut to 120 characters. | | `model` | string \| null | no | | | `nodeId` | string \| null | no | Home location; null = workspace root. | | `items` | array of object | no | The commands, at most 200. On update the list replaces the stored one. | | `items[].id` | string | no | Keep an existing command's id; omit to get a new one. | | `items[].name` | string | yes | | | `items[].value` | string | yes | At most 512 characters, and it must give at least one byte. | | `items[].encoding` | "ascii" \| "hex" | no | Default `ascii`. An unknown value is read as `ascii`. | | `items[].eol` | "none" \| "cr" \| "lf" \| "crlf" | no | Default `none`. An unknown value is read as `none`. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/serial-templates/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | SerialTemplate | A saved set of RS232 commands for one panel model. | | `data.id` | string | Serial template id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.model` | string \| null | Free-text panel model (`Sony Bravia FW-series`). | | `data.nodeId` | string \| null | Home location; null = workspace root. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.items` | array of object | | | `data.items[].id` | string | Command id. Kept across edits, so saved buttons that name it keep working. | | `data.items[].name` | string | | | `data.items[].value` | string | The command: literal text (`ascii`) or hex digits (`hex`). | | `data.items[].encoding` | "ascii" \| "hex" | | | `data.items[].eol` | "none" \| "cr" \| "lf" \| "crlf" | Line ending added after the value. | | `data.items[].hexPreview` | string | The exact bytes sent, as spaced hex (`2A 53 0A`). | 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: No `screen.edit` at the destination 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 serial template 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: An empty `name`, `invalid_node` (the destination is not in this workspace), or a command is not valid; `message` names 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. | ### DELETE /v1/serial-templates/{id} Delete a serial command template Delete an RS232 command template. Commands already queued using this template are unaffected, because they carry their own fully expanded bytes rather than a reference to the template. **Notes.** - Answers `{ data: { ok: true } }`, not the `{ id, deleted: true }` most other deletes return. There is no restore route. Auth: Bearer token. Permission: `screen.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Serial template id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/serial-templates/{id}" \ -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 such serial template 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. | ## Shared with Me Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/shared-with-me List airtime shared with me List airtime slices that other teams have reserved and made available for the caller's team to fill. Auth: Bearer token. ```bash curl "https://api.brixsignage.com/v1/shared-with-me" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/shared-with-me/{allocationId}/fill Fill a shared airtime slice Create a new playlist and attach it as the fill for an airtime slice reserved for the caller's team. Auth: Bearer token. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `allocationId` | path | string | yes | Identifier for allocationId. | ```bash curl -X POST "https://api.brixsignage.com/v1/shared-with-me/{allocationId}/fill" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ### PUT /v1/shared-with-me/{allocationId}/fill Set a shared airtime slice's fill Attach an existing playlist as the fill for an airtime slice reserved for the caller's team. Send `playlistId` as `null` to remove it. Auth: Bearer token. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `allocationId` | path | string | yes | Identifier for allocationId. | ```bash curl -X PUT "https://api.brixsignage.com/v1/shared-with-me/{allocationId}/fill" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Shares Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/shares/{kind}/{id} Get a content item's shares List the share entries for one content item, showing which nodes, users, and roles it is shared with, and at what level. Requires view, edit, or share permission on the item at its home node. Auth: Bearer token. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `kind` | path | string | yes | Identifier for kind. | | `id` | path | string | yes | Identifier for id. | ```bash curl "https://api.brixsignage.com/v1/shares/{kind}/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ### PUT /v1/shares/{kind}/{id} Replace a content item's shares Replace the entire share set for one content item. Each entry targets exactly one of a node, a user, or a role. Requires the item's own share permission at its home node. Auth: Bearer token. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `kind` | path | string | yes | Identifier for kind. | | `id` | path | string | yes | Identifier for id. | ```bash curl -X PUT "https://api.brixsignage.com/v1/shares/{kind}/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/shares/directory List shareable people and roles List the people and roles a share can target, including id, display name, and, for roles, colour and member count. Available to anyone holding a share permission on any content, rather than requiring full user directory access. People are listed only from the caller's own workspace: a franchise child workspace sees its own people, never the parent's or another child workspace's. Email is returned only for people the caller can see with the user view permission; otherwise it is null. A role's member count counts only the listed people. Auth: Bearer token. Permission: `user.view`. ```bash curl "https://api.brixsignage.com/v1/shares/directory" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Signage master templates Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/signage-master-templates List master signage templates Return the published catalog of signage templates curated by Brix. This catalog is shared across all workspaces rather than scoped to one, and is the only source of the masterId values required when creating a signage template. Drafts and deleted entries are not included. **Notes.** - Not paginated: the whole published catalog comes back, sorted by `sortOrder` then name. Auth: Bearer token. Permission: `creative.view`. ```bash curl "https://api.brixsignage.com/v1/signage-master-templates" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of SignageMasterTemplate | | | `data[].id` | string | | | `data[].name` | string | | | `data[].description` | string \| null | | | `data[].industry` | string \| null | | | `data[].archetype` | string \| null | | | `data[].sortOrder` | integer | | | `data[].format` | "engine" \| "creative" | `engine`: `config` is the template. `creative`: `creative` is a finished design to create a creative from. | | `data[].config` | object | The template configuration: `archetype`, `style`, `palette`, `orientation`, `content`, and the `schemaVersion` the API stamps on every write. | | `data[].creative` | any \| null | `creative` format: the design (the body `POST /v1/creatives` takes). Null otherwise. | | `data[].updatedAt` | 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. | ## Signage templates Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/signage-templates List signage templates Return the signage template instances in this workspace. Auth: Bearer token. Permission: `creative.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | integer | no | Page size. Omit to get every row; pass it to page by `cursor`. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | | `count` | query | "1" | no | With `limit`: also return `total`, the number of matching rows. | | `usableAt` | query | string | no | Location id: only rows usable at that location (homed there, at the workspace root, or shared to it). | ```bash curl "https://api.brixsignage.com/v1/signage-templates" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of SignageTemplate | | | `data[].id` | string | Signage template id. | | `data[].spaceId` | string | | | `data[].name` | string | | | `data[].config` | object | The template configuration: `archetype`, `style`, `palette`, `orientation`, `content`, and the `schemaVersion` the API stamps on every write. | | `data[].nodeId` | string \| null | | | `data[].masterId` | string \| null | The master template it was copied from, if any. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | string | ISO-8601 timestamp (UTC). | | `data[].deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | | `nextCursor` | string \| null | Present when `?limit` was passed. Send it back as `?cursor=` for the next page; null on the last page. | | `total` | integer | Total matching rows, when the route computes it. | 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/signage-templates Create a signage template instance from a configuration object containing archetype, style, palette, orientation, and content. **Notes.** - The 201 body is the row as written, not re-read from the database, so columns the create does not set (for example `lastSnapshotAt`) are absent rather than null. `GET` returns every column. Auth: Bearer token. Permission: `creative.create`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `config` | object \| string | no | A configuration object, or the same as a JSON string. Default `{}`. | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | | `masterId` | string \| null | no | An id from `GET /v1/signage-master-templates`. | ```bash curl -X POST "https://api.brixsignage.com/v1/signage-templates" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Signage template id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.config` | object | The template configuration: `archetype`, `style`, `palette`, `orientation`, `content`, and the `schemaVersion` the API stamps on every write. | | `data.nodeId` | string \| null | | | `data.masterId` | string \| null | | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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: Missing name, invalid JSON, or a location outside 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/signage-templates/{id} Get a signage template Return one signage template instance. Auth: Bearer token. Permission: `creative.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Signage template id. | ```bash curl "https://api.brixsignage.com/v1/signage-templates/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | SignageTemplate | A signage template instance in this workspace. | | `data.id` | string | Signage template id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.config` | object | The template configuration: `archetype`, `style`, `palette`, `orientation`, `content`, and the `schemaVersion` the API stamps on every write. | | `data.nodeId` | string \| null | | | `data.masterId` | string \| null | The master template it was copied from, if any. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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 signage template 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/signage-templates/{id} Update a signage template Edit a signage template instance's configuration or name. Screens currently playing it receive an updated manifest reflecting the change. **Notes.** - When the PATCH does not send `config`, the response carries the stored config as it is: an old row is not brought to the current `schemaVersion` here (GET does that). Auth: Bearer token. Permission: `creative.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Signage template id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | | | `config` | object \| string | no | A configuration object, or the same as a JSON string. Default `{}`. | | `nodeId` | string \| null | no | Home location. Default: the caller's own location. | | `masterId` | string \| null | no | An id from `GET /v1/signage-master-templates`. | | `baseUpdatedAt` | string | no | Optimistic concurrency: the `updatedAt` you read. A stale value is refused with 409 `conflict` and the `current` row. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/signage-templates/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | SignageTemplate | A signage template instance in this workspace. | | `data.id` | string | Signage template id. | | `data.spaceId` | string | | | `data.name` | string | | | `data.config` | object | The template configuration: `archetype`, `style`, `palette`, `orientation`, `content`, and the `schemaVersion` the API stamps on every write. | | `data.nodeId` | string \| null | | | `data.masterId` | string \| null | The master template it was copied from, if any. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.deletedAt` | string \| null | Always null on these reads: deleted rows are not listed. | 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: Moving it to a location where you lack creative.edit. | 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 signage template 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 409: `conflict`: the row changed since `baseUpdatedAt`; the body carries `current`. | 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 JSON. | 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/signage-templates/{id} Delete a signage template Move a signage template instance to the recycle bin. Auth: Bearer token. Permission: `creative.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Signage template id. | | `force` | query | "true" | no | Delete even when it is shared into other places; the shares go with it. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/signage-templates/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.deleted` | true | | | `data.sharesRemoved` | integer | Shares removed with it. | 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 signage template 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 409: `content_shared`: it is shared into other places; `shareCount`, `crossSpaceShares`, `contentShares` say where. Repeat with `?force=true` to delete it and those shares. | 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/signage-templates/{id}/restore Restore a signage template Bring back a deleted signage template instance from the recycle bin. Auth: Bearer token. Permission: `creative.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Signage template id. | ```bash curl -X POST "https://api.brixsignage.com/v1/signage-templates/{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 such signage template in this workspace, or it was purged. | 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: `not_deleted`: it is not in the recycle bin. | 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. | ## Social accounts Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/social-accounts List connected social accounts List the social accounts connected to your workspace. Use the provider query parameter, for example provider=youtube, to filter by provider. Each result includes the display name and connection status for the account, but never the stored credentials. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/social-accounts" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/social-accounts Connect a social account Create a social account connection by submitting its credentials directly. Credentials are encrypted before they are stored. For most providers, use the provider's OAuth start endpoint instead, since it handles sign-in and consent on your behalf. Auth: Bearer token. Permission: `integration.create`. ```bash curl -X POST "https://api.brixsignage.com/v1/social-accounts" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/social-accounts/{id} Rename or move a social account Update a connected social account by its ID. You can change its display name or move it to a different location in your organization. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/social-accounts/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/social-accounts/{id} Disconnect a social account Delete a connected social account by its ID. This permanently removes the stored credentials and stops any apps that use the account from fetching new data. Auth: Bearer token. Permission: `integration.delete`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Identifier for id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/social-accounts/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/social-accounts/preview-token Get a preview token for social apps Get a short-lived token used to preview a live social app before it is published to a screen. Pass this token as the dt query parameter when calling social feed endpoints from a preview. The token is issued only for your own workspace and cannot be requested for another one. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/social-accounts/preview-token" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## SSO connections Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/sso-connections List SSO connections Returns the workspace's enterprise single sign-on connections, with client secrets masked. The key or person must have `settings.view` for the whole workspace. A caller limited to a location or to a franchise workspace gets 403. `lastClaims` holds the claims from the last sign-in: a person's email, name and groups. It is `null` unless the caller also has `user.view` for the whole workspace. Auth: Bearer token. Permission: `settings.view`. ```bash curl "https://api.brixsignage.com/v1/sso-connections" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of SsoConnection | | | `data[].id` | string | | | `data[].displayName` | string | | | `data[].vendor` | string | Free text; `generic-oidc` when not set. | | `data[].issuer` | string | The OpenID Connect issuer URL (`https://`, no trailing slash). | | `data[].clientId` | string | | | `data[].clientSecretPreview` | "••••••••" | Always this mask: the client secret is stored encrypted and is never returned. | | `data[].emailDomains` | string | Comma-separated, lower-case (a leading `@` and a trailing dot are removed). | | `data[].domainsVerifiedAt` | string \| null | When DNS proved the domains; null until verified. An unverified connection is not offered at sign-in. | | `data[].provenAt` | string \| null | When a sign-in first completed with the current issuer, client and domains; null until then. | | `data[].lastSignInAt` | string \| null | The latest sign-in through this connection. Only the list fills it; create and update answer null. | | `data[].jitProvisioning` | boolean | Create a person on their first sign-in. | | `data[].defaultRoleId` | string \| null | The role a new person gets at the workspace root when no rule applies. | | `data[].claimMapping` | SsoClaimMapping \| null | | | `data[].extraScopes` | string \| null | Scopes requested on top of `openid email profile`, space-separated. | | `data[].lastClaims` | object \| null | The claims of the last sign-in (a person's email, name and groups). Null unless the caller also holds `user.view` for the whole workspace. | | `data[].lastClaimsAt` | string \| null | | | `data[].enabled` | boolean | | | `data[].redirectUri` | string | The redirect URI to register in the identity provider. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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/sso-connections Create SSO connection Creates an enterprise single sign-on connection. The client secret is encrypted before it is stored. **Notes.** - A new connection is unverified: prove its domains (`domain-challenge`, then `verify-domains`) before it is offered at sign-in. Auth: Bearer token. Permission: `settings.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `displayName` | string | yes | | | `vendor` | string | no | Free text, e.g. `okta`, `entra`. | | `issuer` | string | yes | Must start with `https://`. A trailing slash is removed. | | `clientId` | string | yes | | | `clientSecret` | string | yes | Encrypted before it is stored; never returned. | | `emailDomains` | string | yes | Comma-separated email domains, e.g. `acme.com,acme.org`. | | `jitProvisioning` | boolean | no | Default true. | | `enabled` | boolean | no | Default true. | | `defaultRoleId` | string \| null | no | You must be able to grant this role for the whole workspace. | | `claimMapping` | SsoClaimMapping \| null | no | You must be able to grant each rule's role where the rule grants it. Null clears it. | | `extraScopes` | string \| null | no | Space- or comma-separated scope tokens (at most 20). Null or empty clears them. | ```bash curl -X POST "https://api.brixsignage.com/v1/sso-connections" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"displayName":"Acme Okta","issuer":"https://acme.okta.com","clientId":"0oa1b2c3d4","clientSecret":"example-client-secret","emailDomains":"acme.com"}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | SsoConnection | An enterprise single sign-on (OpenID Connect) connection. | | `data.id` | string | | | `data.displayName` | string | | | `data.vendor` | string | Free text; `generic-oidc` when not set. | | `data.issuer` | string | The OpenID Connect issuer URL (`https://`, no trailing slash). | | `data.clientId` | string | | | `data.clientSecretPreview` | "••••••••" | Always this mask: the client secret is stored encrypted and is never returned. | | `data.emailDomains` | string | Comma-separated, lower-case (a leading `@` and a trailing dot are removed). | | `data.domainsVerifiedAt` | string \| null | When DNS proved the domains; null until verified. An unverified connection is not offered at sign-in. | | `data.provenAt` | string \| null | When a sign-in first completed with the current issuer, client and domains; null until then. | | `data.lastSignInAt` | string \| null | The latest sign-in through this connection. Only the list fills it; create and update answer null. | | `data.jitProvisioning` | boolean | Create a person on their first sign-in. | | `data.defaultRoleId` | string \| null | The role a new person gets at the workspace root when no rule applies. | | `data.claimMapping` | SsoClaimMapping \| null | | | `data.extraScopes` | string \| null | Scopes requested on top of `openid email profile`, space-separated. | | `data.lastClaims` | object \| null | The claims of the last sign-in (a person's email, name and groups). Null unless the caller also holds `user.view` for the whole workspace. | | `data.lastClaimsAt` | string \| null | | | `data.enabled` | boolean | | | `data.redirectUri` | string | The redirect URI to register in the identity provider. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). Also: `defaultRoleId` or a rule's role holds permissions you cannot grant there. | 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 connection, role or location 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: A required field is missing, `issuer` is not `https://`, an invalid `claimMapping`, or `extraScopes` is not a valid scope 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. | ### PATCH /v1/sso-connections/{id} Update SSO connection Updates an enterprise single sign-on connection. `clientSecret` is optional: when provided, it replaces the stored secret; when omitted, the existing secret is left unchanged. **Notes.** - Changing `emailDomains` clears `domainsVerifiedAt`. Changing `issuer`, `clientId`, `clientSecret` or `emailDomains` clears `provenAt`. Auth: Bearer token. Permission: `settings.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | SSO connection id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `displayName` | string | no | | | `vendor` | string | no | Free text, e.g. `okta`, `entra`. | | `issuer` | string | no | Must start with `https://`. A trailing slash is removed. | | `clientId` | string | no | | | `clientSecret` | string | no | Replaces the stored secret. Omitted or empty keeps it. | | `emailDomains` | string | no | Comma-separated email domains, e.g. `acme.com,acme.org`. | | `jitProvisioning` | boolean | no | Default true. | | `enabled` | boolean | no | Default true. | | `defaultRoleId` | string \| null | no | You must be able to grant this role for the whole workspace. | | `claimMapping` | SsoClaimMapping \| null | no | You must be able to grant each rule's role where the rule grants it. Null clears it. | | `extraScopes` | string \| null | no | Space- or comma-separated scope tokens (at most 20). Null or empty clears them. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/sso-connections/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | SsoConnection | An enterprise single sign-on (OpenID Connect) connection. | | `data.id` | string | | | `data.displayName` | string | | | `data.vendor` | string | Free text; `generic-oidc` when not set. | | `data.issuer` | string | The OpenID Connect issuer URL (`https://`, no trailing slash). | | `data.clientId` | string | | | `data.clientSecretPreview` | "••••••••" | Always this mask: the client secret is stored encrypted and is never returned. | | `data.emailDomains` | string | Comma-separated, lower-case (a leading `@` and a trailing dot are removed). | | `data.domainsVerifiedAt` | string \| null | When DNS proved the domains; null until verified. An unverified connection is not offered at sign-in. | | `data.provenAt` | string \| null | When a sign-in first completed with the current issuer, client and domains; null until then. | | `data.lastSignInAt` | string \| null | The latest sign-in through this connection. Only the list fills it; create and update answer null. | | `data.jitProvisioning` | boolean | Create a person on their first sign-in. | | `data.defaultRoleId` | string \| null | The role a new person gets at the workspace root when no rule applies. | | `data.claimMapping` | SsoClaimMapping \| null | | | `data.extraScopes` | string \| null | Scopes requested on top of `openid email profile`, space-separated. | | `data.lastClaims` | object \| null | The claims of the last sign-in (a person's email, name and groups). Null unless the caller also holds `user.view` for the whole workspace. | | `data.lastClaimsAt` | string \| null | | | `data.enabled` | boolean | | | `data.redirectUri` | string | The redirect URI to register in the identity provider. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). Also: `defaultRoleId` or a rule's role holds permissions you cannot grant there. | 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 connection, role or location 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: A required field is missing, `issuer` is not `https://`, an invalid `claimMapping`, or `extraScopes` is not a valid scope 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. | ### DELETE /v1/sso-connections/{id} Delete SSO connection Soft-deletes an enterprise single sign-on connection. **Notes.** - Answers `{ ok: true }`, not the `{ id, deleted }` shape of other deletes. The connection is also disabled. Auth: Bearer token. Permission: `settings.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | SSO connection id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/sso-connections/{id}" \ -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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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 connection 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/sso-connections/{id}/domain-challenge Get domain verification challenge Returns the DNS TXT records you must publish to prove control of the email domains used by this SSO connection. The key or person must have `settings.view` for the whole workspace. Auth: Bearer token. Permission: `settings.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | SSO connection id. | ```bash curl "https://api.brixsignage.com/v1/sso-connections/{id}/domain-challenge" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.records` | array of object | | | `data.records[].domain` | string | | | `data.records[].name` | string | The record name: `_brix-verify.`. | | `data.records[].type` | "TXT" | | | `data.records[].value` | string | The record value to publish: `brix-domain-verify=`. | | `data.verifiedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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 connection 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/sso-connections/{id}/scim Get SCIM configuration Returns the SCIM base URL for this SSO connection and a preview of its currently live provisioning tokens. Token secrets themselves are never returned. Auth: Bearer token. Permission: `settings.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | SSO connection id. | ```bash curl "https://api.brixsignage.com/v1/sso-connections/{id}/scim" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.baseUrl` | string | The SCIM 2.0 base URL to give the identity provider. | | `data.tokens` | array of object | Live tokens only. | | `data.tokens[].id` | string | | | `data.tokens[].preview` | string | `••••` and the last four characters. | | `data.tokens[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data.tokens[].lastUsedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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 connection 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/sso-connections/{id}/scim/tokens Create SCIM token Creates a new SCIM provisioning token for this SSO connection. The token value is shown exactly once, in this response. A connection can have at most 5 live tokens at a time. This action cannot be taken while impersonating another user. Auth: Bearer token. Permission: `settings.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | SSO connection id. | ```bash curl -X POST "https://api.brixsignage.com/v1/sso-connections/{id}/scim/tokens" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.token` | string | The SCIM bearer token. Shown only here; store it now. | | `data.baseUrl` | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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 connection 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 409: `too_many_tokens`: the connection already has 5 live tokens. | 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/sso-connections/{id}/scim/tokens/{tokenId} Revoke SCIM token Revokes one SCIM provisioning token belonging to this SSO connection. Auth: Bearer token. Permission: `settings.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | SSO connection id. | | `tokenId` | path | string | yes | SCIM token id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/sso-connections/{id}/scim/tokens/{tokenId}" \ -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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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 connection, or no live token 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 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/sso-connections/{id}/test-mapping Test SSO claim mapping Previews the role and location a person would be granted, given a set of example identity-provider claims, by running the same mapping logic used at sign-in. This is read-only and does not sign anyone in or change anything. Auth: Bearer token. Permission: `settings.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | SSO connection id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `claims` | object | yes | Example identity-provider claims. | | `claimMapping` | SsoClaimMapping \| null | no | An unsaved mapping to test; omitted = the saved one. | ```bash curl -X POST "https://api.brixsignage.com/v1/sso-connections/{id}/test-mapping" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.mapped` | boolean | False when there is no mapping: `grants` is then the default role at the root, without names. | | `data.grants` | array of object | | | `data.grants[].nodeId` | string | Location id (the workspace id for the root). | | `data.grants[].roleId` | string | | | `data.grants[].roleName` | string \| null | Absent when `mapped` is false. | | `data.grants[].nodeName` | string \| null | Absent when `mapped` is false. | | `data.matchedRules` | array of integer | Indexes of the rules that matched. | | `data.unmatchedLocations` | array of string | Location values that matched no location. | | `data.ambiguousLocations` | array of string | Location values that matched more than one location. | | `data.usedDefault` | boolean | | | `data.denied` | boolean | True when the sign-in would be refused (`noMatch: deny`). | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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 connection 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: `claims` is not an object, or `claimMapping` is invalid. | 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/sso-connections/{id}/verify-domains Verify SSO domains Verifies an SSO connection's email domains using a DNS TXT record lookup. Verified domains control whether the connection is offered at sign-in and whether cross-workspace sign-in is allowed for that domain. **Notes.** - Answers 200 with `verified: false` when a domain fails; the connection stays unverified. Auth: Bearer token. Permission: `settings.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | SSO connection id. | ```bash curl -X POST "https://api.brixsignage.com/v1/sso-connections/{id}/verify-domains" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.verified` | boolean | True only when every domain proved out. | | `data.results` | array of object | | | `data.results[].domain` | string | | | `data.results[].ok` | boolean | | | `data.results[].reason` | string | Why a domain failed: `claimed_by_another_workspace`, `dns_`, or a lookup error. Absent when the lookup answered. | | `data.verifiedAt` | 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: You do not hold the permission for the whole workspace (a grant at one location, or in a franchise workspace, is not enough). | 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 connection 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: `no_domains`: the connection has no email domains. | 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/sso-connections/test Test SSO discovery Fetches the identity provider's OpenID Connect discovery document and returns its parsed authorization, token, and key endpoints, so the connection can be confirmed as reachable before the setup form is submitted. **Notes.** - Needs `settings.view` held anywhere, unlike the other SSO routes, which need it for the whole workspace. Auth: Bearer token. Permission: `settings.view`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `issuer` | string | yes | Must start with `https://`. | ```bash curl -X POST "https://api.brixsignage.com/v1/sso-connections/test" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.ok` | true | | | `data.issuer` | string | The issuer the discovery document names; absent when it names none. | | `data.authorizationEndpoint` | string | | | `data.tokenEndpoint` | string | | | `data.jwksUri` | 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 422: `issuer` is not `https://`. | 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 502: `discovery_failed` (the provider answered an error), `discovery_incomplete` (an endpoint is missing), or `discovery_threw` (not reachable). | 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. | ## Status Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/status Get platform status Get the current platform status, including overall state, 30-day latency attainment, open and recent published incidents, and a 90-day history strip. This endpoint requires no authentication. If the underlying status data is more than three hours old, the state is reported as `unknown` rather than `operational`. Pass `format=text` for a plain-text response. **Notes.** - Answers 200 during an outage too: the state is in the body. - With `format=text` the body is `text/plain`. Auth: none. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `format` | query | "text" | no | `text` answers a plain-text summary instead of JSON. | ```bash curl "https://api.brixsignage.com/v1/status" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.state` | "operational" \| "degraded" \| "outage" \| "unknown" | | | `data.computedAt` | string \| null | When the status was measured; null when never. | | `data.stale` | boolean | True when the measurement is more than three hours old (the state is then `unknown`). | | `data.latency` | object \| null | | | `data.incidents` | array of StatusIncident | Open incidents. | | `data.incidents[].id` | string | | | `data.incidents[].title` | string | | | `data.incidents[].status` | "investigating" \| "identified" \| "monitoring" \| "resolved" | | | `data.incidents[].impact` | "degraded" \| "outage" \| "none" | | | `data.incidents[].startedAt` | string | ISO-8601 timestamp (UTC). | | `data.incidents[].resolvedAt` | string \| null | | | `data.incidents[].updates` | array of object | Oldest first. | | `data.incidents[].updates[].at` | string | ISO-8601 timestamp (UTC). | | `data.incidents[].updates[].status` | "investigating" \| "identified" \| "monitoring" \| "resolved" | | | `data.incidents[].updates[].message` | string | | | `data.incidents[].screensPlaying` | boolean \| null | Whether screens kept playing during the incident; null when not stated. | | `data.recent` | array of StatusIncident | Recently resolved incidents. | | `data.recent[].id` | string | | | `data.recent[].title` | string | | | `data.recent[].status` | "investigating" \| "identified" \| "monitoring" \| "resolved" | | | `data.recent[].impact` | "degraded" \| "outage" \| "none" | | | `data.recent[].startedAt` | string | ISO-8601 timestamp (UTC). | | `data.recent[].resolvedAt` | string \| null | | | `data.recent[].updates` | array of object | Oldest first. | | `data.recent[].updates[].at` | string | ISO-8601 timestamp (UTC). | | `data.recent[].updates[].status` | "investigating" \| "identified" \| "monitoring" \| "resolved" | | | `data.recent[].updates[].message` | string | | | `data.recent[].screensPlaying` | boolean \| null | Whether screens kept playing during the incident; null when not stated. | | `data.days` | array of object | One entry per day, for the last 90 days. | | `data.days[].date` | string | `YYYY-MM-DD`. | | `data.days[].state` | "operational" \| "degraded" \| "outage" | | 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 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/status/deprecations List API deprecations List every API route scheduled for retirement, when it stops working, and its replacement. This endpoint requires no authentication and is safe to poll from a script. An empty list means no routes are currently scheduled for retirement. Auth: none. ```bash curl "https://api.brixsignage.com/v1/status/deprecations" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.noticeDays` | integer | The minimum notice a retired route gets. | | `data.policy` | string | | | `data.deprecations` | array of object | | | `data.deprecations[].route` | string | `METHOD /path`. | | `data.deprecations[].announced` | string | Date (`YYYY-MM-DD`) it was listed. | | `data.deprecations[].sunset` | string | Date (`YYYY-MM-DD`) it stops working. | | `data.deprecations[].replacement` | string \| null | | | `data.deprecations[].why` | string | | | `data.deprecations[].noticeDays` | integer | Days between `announced` and `sunset`. | 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 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. | ## Uptime Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/uptime Get an uptime report for a date range, with online percentage per screen and per location, downtime windows, and mean time to recovery. Auth: Bearer token. Permission: `screen.view`. ```bash curl "https://api.brixsignage.com/v1/uptime" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Users Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/users List users Returns the workspace roster: each person's role, which locations they can access, their sign-in method, two-factor authentication status, and last login. **Notes.** - Not paginated. Deleted (erased) people are not listed. A caller whose access is limited to some locations sees only the people with access there, and only those locations in `access`. Auth: Bearer token. Permission: `user.view`. ```bash curl "https://api.brixsignage.com/v1/users" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of User | | | `data[].id` | string | | | `data[].name` | string | | | `data[].email` | string | | | `data[].status` | "active" \| "invited" \| "deactivated" | `invited` until the person sets a password or signs in. | | `data[].roleId` | string \| null | The role of the first entry in `access`; null when the person has no access. | | `data[].scope` | string | The location name of the first entry in `access`; `—` when none. | | `data[].loginMethod` | "sso" \| "passkey" \| "password" | How the person signs in. | | `data[].totpEnabled` | boolean | Two-factor authentication with an authenticator app is on. | | `data[].passkeyCount` | integer | | | `data[].lastLoginAt` | string \| null | | | `data[].access` | array of object | Each location the person can access and the role they hold there. Only locations the caller can see are listed. | | `data[].access[].nodeId` | string | Location id. | | `data[].access[].nodeName` | string | Location name; `—` when the location no longer exists. | | `data[].access[].roleId` | string | | | `data[].access[].roleName` | string | Role name; `—` when the role no longer exists. | 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. | ### PATCH /v1/users/{id} Update user Updates a person's profile. Supported fields are `name` and `trainingStep` (which resets a feature walkthrough for that person). Email address and account status cannot be changed through this operation. Requires access to every location the target person belongs to. Auth: Bearer token. Permission: `user.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | User id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | no | Trimmed; must not be empty. | | `trainingStep` | string \| null | no | Walkthrough position (`s1`…`s5`, `l1`…`l6`, `done`); null resets it. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/users/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.email` | string | | | `data.name` | string | | | `data.status` | "active" \| "invited" \| "deactivated" | | | `data.trainingStep` | string \| null | Walkthrough position; null when not started or reset. | 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 person belongs to a location where you do not hold `user.edit`. | 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 person 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: Nothing to update, an empty or over-long `name`, or an unknown `trainingStep`. | 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/users/{id}/deactivate Deactivate user Deactivates a person's account. This revokes all of their active sessions, signing them out everywhere immediately and blocking further sign-in including through SSO, and removes them from every location's approver list. This action is reversible. You cannot deactivate your own account or the last remaining account owner. Only an account owner can do this to an owner, and you cannot do it to a person who holds a permission you do not hold. An API key is never an owner. **Notes.** - Only an owner can deactivate an owner, and nobody can deactivate a person who holds a permission they do not hold. An API key is never an owner, whatever its permissions. Auth: Bearer token. Permission: `user.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | User id. | ```bash curl -X POST "https://api.brixsignage.com/v1/users/{id}/deactivate" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.status` | "deactivated" | | | `data.deactivatedAt` | 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 person belongs to a location where you do not hold `user.edit`; `owner_required`: the person is an owner and you are not an owner there (an API key is never an owner); or `outranked`: the person holds a permission you do not hold where they hold 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 404: No such person 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 409: `cant_deactivate_self`, `already_deactivated`, or `last_owner` (the last owner of the workspace or of a 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 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/users/{id}/erase Erase user under GDPR Permanently erases a deactivated person's personal data to satisfy a right-to-be-forgotten request: it anonymizes their profile and hard-deletes their passkeys, linked identities, sessions, and location role assignments. The account must already be deactivated. You cannot erase your own account or the last remaining account owner. Only an account owner can do this to an owner, and you cannot do it to a person who holds a permission you do not hold. An API key is never an owner. **Notes.** - Irreversible. The same rule as deactivation applies: only an owner can erase an owner, and the caller must hold every permission the person holds. Auth: Bearer token. Permission: `user.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | User id. | ```bash curl -X POST "https://api.brixsignage.com/v1/users/{id}/erase" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.erased` | 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 person belongs to a location where you do not hold `user.edit`; `owner_required`: the person is an owner and you are not an owner there (an API key is never an owner); or `outranked`: the person holds a permission you do not hold where they hold 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 404: No such person in this workspace (an erased person is not found 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 409: `cant_erase_self`, `last_owner`, or `must_deactivate_first`. | 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/users/{id}/reactivate Reactivate user Restores a deactivated person's account to active status. Their previous role assignments are restored along with the access they grant. Any approver-list entries removed at deactivation are not automatically restored. Only an account owner can do this to an owner, and you cannot do it to a person who holds a permission you do not hold. An API key is never an owner. Auth: Bearer token. Permission: `user.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | User id. | ```bash curl -X POST "https://api.brixsignage.com/v1/users/{id}/reactivate" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.status` | "active" | | 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 person belongs to a location where you do not hold `user.edit`; `owner_required`: the person is an owner and you are not an owner there (an API key is never an owner); or `outranked`: the person holds a permission you do not hold where they hold 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 404: No such person 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 409: `not_deactivated`: the person is not deactivated. | 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/users/{id}/sessions List user sessions Returns a person's currently active sign-in sessions: id, creation time, last used time, expiry, device or browser, IP address, and which session belongs to the caller. Revoked and expired sessions are not included. **Notes.** - Needs `user.edit`, not `user.view`: the rows carry IP addresses. Sorted by last use, newest first. Auth: Bearer token. Permission: `user.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | User id. | ```bash curl "https://api.brixsignage.com/v1/users/{id}/sessions" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of UserSession | | | `data[].id` | string | Session id (not the session token, which is never returned). | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].lastUsedAt` | string \| null | | | `data[].expiresAt` | string | ISO-8601 timestamp (UTC). | | `data[].userAgent` | string \| null | The browser or device that signed in. | | `data[].ipAddress` | string \| null | | | `data[].current` | boolean | This is the session making the call (always false for an 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 person belongs to a location where you do not hold `user.edit`. | 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 person 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. | ### DELETE /v1/users/{id}/sessions/{sessionId} Revoke user session Signs one of a person's devices out by revoking that session. The session record itself is kept, not deleted. Calling this on an already-revoked session is safe and reports `revoked: false`. Auth: Bearer token. Permission: `user.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | User id. | | `sessionId` | path | string | yes | Session id from the session list. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/users/{id}/sessions/{sessionId}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.revoked` | boolean | False when the session was already revoked. | 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 person belongs to a location where you do not hold `user.edit`. | 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 person, or no such session for this person. | 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/users/{id}/sessions/revoke-all Revoke all user sessions Signs a person out of every active session without deactivating their account, so they can still sign back in afterward. Returns the number of sessions that were revoked. Auth: Bearer token. Permission: `user.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | User id. | ```bash curl -X POST "https://api.brixsignage.com/v1/users/{id}/sessions/revoke-all" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | User id. | | `data.revoked` | integer | Sessions revoked. | 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 person belongs to a location where you do not hold `user.edit`. | 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 person 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/users/invite Invite user Creates a new person in the workspace, or reuses an existing one, and assigns them a role at the chosen locations. A new person gets an email with a link to set their password. When a signed-in user sends the invite, the email goes out only when that user's own email address is verified. An invite made with an API key sends the email in the workspace's name. You cannot grant a role carrying permissions you do not hold yourself. **Notes.** - Answers 200 (not 201) for a new person too; `created` tells the two apart. - For a signed-in user, the set-password email is sent only when that user's own email address is verified. An invite made with an API key sends the email too, from the workspace (the key's name is not shown). The 30-a-minute limit counts per key for a key. Auth: Bearer token. Permission: `user.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | Stored in lower case. | | `name` | string | no | Used only when the person is new. | | `roleId` | string | yes | | | `nodeIds` | array of string | yes | Locations to grant the role at. You need `user.edit` and every permission of the role at each one. | ```bash curl -X POST "https://api.brixsignage.com/v1/users/invite" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email":"sam@example.com","name":"Sam Rivera","roleId":"role_5e6f7a8b9c0d1e2f","nodeIds":["on_4d5e6f7a8b9c0d1e"]}' ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.email` | string | | | `data.status` | "active" \| "invited" \| "deactivated" | | | `data.created` | boolean | True when a new person was created; false when an existing person got the extra access. | | `data.emailSent` | boolean | A set-password email went out. | | `data.emailBlockedReason` | "inviter-unverified" \| "provider-refused" \| null | Why no email went out for a new person: the calling user's own email address is not verified (never for an API key), or the mail provider refused it. Null when sent, or when the person already existed. | 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 role holds permissions you do not hold, or you lack `user.edit` or the role's permissions at one of the locations. | 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 role or location 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 `email`, or `roleId` / `nodeIds` 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: More than 30 invites 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. | ## Web recorder Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/web-recorder/{sid} Get a web recording session Return the steps recorded so far in an in-progress sign-in recording session. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `sid` | path | string | yes | Identifier for sid. | ```bash curl "https://api.brixsignage.com/v1/web-recorder/{sid}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/web-recorder/{sid}/abort Cancel a web recording session Discard an in-progress sign-in recording session without changing the associated media asset. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `sid` | path | string | yes | Identifier for sid. | ```bash curl -X POST "https://api.brixsignage.com/v1/web-recorder/{sid}/abort" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/web-recorder/{sid}/action Add an action to a web recording session Append one recorded interaction, such as a click, typed text, or a wait, to an open sign-in recording session. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `sid` | path | string | yes | Identifier for sid. | ```bash curl -X POST "https://api.brixsignage.com/v1/web-recorder/{sid}/action" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/web-recorder/{sid}/finalize Finalize a web recording session Save a completed recording to a web asset, using mediaId and optional secrets. The recorded steps are stored on the asset's web configuration, secrets are encrypted and referenced by name, and the session ends. Auth: Bearer token. Permission: `media.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `sid` | path | string | yes | Identifier for sid. | ```bash curl -X POST "https://api.brixsignage.com/v1/web-recorder/{sid}/finalize" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/web-recorder/links/{mediaId}/verify Verify a web recording's sign-in Run the finalized sign-in recording for a web asset and report whether the resulting session looks successfully logged in. This lets a caller confirm the recording works right after capturing it, rather than waiting until the content plays on a screen. Auth: Bearer token. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `mediaId` | path | string | yes | Identifier for mediaId. | ```bash curl -X POST "https://api.brixsignage.com/v1/web-recorder/links/{mediaId}/verify" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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/web-recorder/start Start a web recording session Begin a sign-in recording session for a web asset, given a url. Returns a session id, the url, and the steps recorded so far, which starts empty. Steps are then submitted using the Brix Recorder browser extension. Auth: Bearer token. Permission: `media.edit`. ```bash curl -X POST "https://api.brixsignage.com/v1/web-recorder/start" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 4XX: Client error. 404 rather than 403 for another tenant's resource, so account existence is not leaked. | 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. | ## Webhooks Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/webhooks List webhook endpoints List the workspace's outbound webhook endpoints. The signing secret is never included in this response; it is shown only once, at creation. **Notes.** - Needs the permission at the workspace root: a location-scoped key is refused. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/webhooks" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of WebhookEndpoint | | | `data[].id` | string | Webhook endpoint id. | | `data[].url` | string | Where deliveries are POSTed. | | `data[].description` | string \| null | | | `data[].secretPreview` | string | First 8 characters of the signing secret. | | `data[].events` | array of string | Subscribed event names (see `GET /v1/webhooks/events`). | | `data[].enabled` | boolean | | | `data[].autoDisabledAt` | string \| null | Set when repeated failures switched the endpoint off. | | `data[].autoDisabledReason` | string \| null | | | `data[].consecutiveFailures` | integer | | | `data[].lastDeliveryAt` | string \| null | | | `data[].lastStatus` | integer \| null | HTTP status of the last delivery attempt. | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].updatedAt` | 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/webhooks Create a webhook endpoint Create an endpoint. The response includes the signing secret once; after that, only a preview of it is available, since it is stored encrypted and cannot be retrieved in full. The endpoint URL is checked at creation and again before every delivery, since a hostname that resolved to a public address at save time could later be re-pointed at an internal address. Only `content.recalled` and `content.restored` events are actually sent today, even though the event catalog lists more. The response carries the signing `secret` ONCE. Store it: later reads return only `secretPreview`. **Notes.** - Validation failures are 400 `bad_request`, where most other routes answer 422 `validation_error`. Auth: Bearer token. Permission: `integration.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | yes | An https URL on the public internet (private and loopback hosts are refused). | | `events` | array of "screen.offline" \| "screen.online" \| "content.recalled" \| "content.restored" \| "approval.requested" \| "approval.decided" \| "emergency.started" \| "emergency.cleared" | no | Events to deliver. Default: none. An unknown name is refused. | | `description` | string | no | Label; cut to 200 characters. | ```bash curl -X POST "https://api.brixsignage.com/v1/webhooks" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://hooks.example.com/brix","events":["screen.offline","screen.online"],"description":"Ops alerts"}' ``` Response 201: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | Webhook endpoint id. | | `data.url` | string | Where deliveries are POSTed. | | `data.description` | string \| null | | | `data.secretPreview` | string | First 8 characters of the signing secret. | | `data.events` | array of string | Subscribed event names (see `GET /v1/webhooks/events`). | | `data.enabled` | boolean | | | `data.autoDisabledAt` | string \| null | Set when repeated failures switched the endpoint off. | | `data.autoDisabledReason` | string \| null | | | `data.consecutiveFailures` | integer | | | `data.lastDeliveryAt` | string \| null | | | `data.lastStatus` | integer \| null | HTTP status of the last delivery attempt. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | | `data.secret` | string | The HMAC signing secret (64 hex characters). Shown only in this response. | ```json { "data": { "id": "whe_7a8b9c0d1e2f3a4b", "url": "https://hooks.example.com/brix", "description": "Ops alerts", "secretPreview": "3f9a1c2e", "events": [ "screen.offline", "screen.online" ], "enabled": true, "autoDisabledAt": null, "autoDisabledReason": null, "consecutiveFailures": 0, "lastDeliveryAt": null, "lastStatus": null, "createdAt": "2026-09-28T09:00:00.000Z", "updatedAt": "2026-09-28T09:00:00.000Z", "secret": "3f9a1c2e4b5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f" } } ``` Response 400: `bad_request` (missing url, unknown event) or `unsafe_url`. | 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 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/webhooks/{id} Update a webhook endpoint Change a webhook endpoint's URL, event subscriptions, description, or enabled state. Re-enabling an endpoint clears any automatic disable and resets its failure count, since re-enabling means the receiver has been confirmed fixed. **Notes.** - Needs the permission at the workspace root: a location-scoped key is refused. - Validation failures are 400 `bad_request`, where most other routes answer 422 `validation_error`. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Webhook endpoint id. | Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `url` | string | no | An https URL on the public internet. | | `events` | array of "screen.offline" \| "screen.online" \| "content.recalled" \| "content.restored" \| "approval.requested" \| "approval.decided" \| "emergency.started" \| "emergency.cleared" | no | Replaces the subscription. An unknown name is refused. | | `description` | string | no | Cut to 200 characters. | | `enabled` | boolean | no | `true` also clears an automatic switch-off and the failure count. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/webhooks/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled":true}' ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | WebhookEndpoint | A customer webhook endpoint. The signing secret is never returned after creation. | | `data.id` | string | Webhook endpoint id. | | `data.url` | string | Where deliveries are POSTed. | | `data.description` | string \| null | | | `data.secretPreview` | string | First 8 characters of the signing secret. | | `data.events` | array of string | Subscribed event names (see `GET /v1/webhooks/events`). | | `data.enabled` | boolean | | | `data.autoDisabledAt` | string \| null | Set when repeated failures switched the endpoint off. | | `data.autoDisabledReason` | string \| null | | | `data.consecutiveFailures` | integer | | | `data.lastDeliveryAt` | string \| null | | | `data.lastStatus` | integer \| null | HTTP status of the last delivery attempt. | | `data.createdAt` | string | ISO-8601 timestamp (UTC). | | `data.updatedAt` | string | ISO-8601 timestamp (UTC). | ```json { "data": { "id": "whe_7a8b9c0d1e2f3a4b", "url": "https://hooks.example.com/brix", "description": "Ops alerts", "secretPreview": "3f9a1c2e", "events": [ "screen.offline", "screen.online" ], "enabled": true, "autoDisabledAt": null, "autoDisabledReason": null, "consecutiveFailures": 0, "lastDeliveryAt": null, "lastStatus": null, "createdAt": "2026-09-28T09:00:00.000Z", "updatedAt": "2026-09-28T09:00:00.000Z" } } ``` Response 400: `bad_request` (unknown event) or `unsafe_url`. | 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 endpoint 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. | ### DELETE /v1/webhooks/{id} Delete a webhook endpoint. Any deliveries still pending for it are abandoned rather than retried. **Notes.** - Answers `{ data: { id } }` — without the `deleted: true` most other deletes return. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Webhook endpoint id. | ```bash curl -X DELETE "https://api.brixsignage.com/v1/webhooks/{id}" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | 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 endpoint 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/webhooks/{id}/test Send a test webhook delivery Send a real, signed sample delivery to this endpoint now. It is a genuine delivery rather than a simulation, so it exercises the same signature verification your receiver uses in production. The sample event is `screen.offline`; this call returns 409 if the endpoint is not subscribed to that event. Sends a real, signed `screen.offline` delivery with `data: { test: true, note }` now and records it in the delivery log. A failed send is NOT an HTTP error: the answer is 200 with `ok: false`. **Notes.** - The test event is always `screen.offline`: an endpoint subscribed only to other events gets 409 `not_subscribed`, with a message that says it is subscribed to no event. - Needs the permission at the workspace root: a location-scoped key is refused. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Webhook endpoint id. | ```bash curl -X POST "https://api.brixsignage.com/v1/webhooks/{id}/test" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.deliveryId` | string | Id of the delivery this call made (a new `whd_…` row in the delivery log). | | `data.ok` | boolean | The receiver answered 2xx. | | `data.status` | integer \| null | HTTP status the receiver answered with; null when no answer came. | | `data.error` | string \| null | Why the delivery failed (`http 500`, a timeout, an unsafe address); null on success. | 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 endpoint 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 409: `not_subscribed`: the endpoint is not subscribed to `screen.offline`, or it is switched off. | 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/webhooks/deliveries List webhook deliveries List the webhook delivery log: what was sent, the response received, the number of attempts, and what is still pending. Each entry includes the exact payload that was sent. Results are paginated and can be filtered by endpoint and status. Failed deliveries are retried with increasing delay over roughly 8 to 12 hours before they are given up on. **Notes.** - Always paged: without `?limit` a page has 50 deliveries (not every row, unlike the lists that page only on request), and `nextCursor` is always present. - Needs the permission at the workspace root: a location-scoped key is refused. Auth: Bearer token. Permission: `integration.view`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `limit` | query | string | no | Page size, 1–200. Default 50. | | `cursor` | query | string | no | The `nextCursor` of the previous page. | | `endpointId` | query | string | no | Only this endpoint's deliveries. | | `status` | query | "pending" \| "delivered" \| "failed" \| "abandoned" | no | Only deliveries in this state. Another value is ignored. | ```bash curl "https://api.brixsignage.com/v1/webhooks/deliveries" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of WebhookDelivery | Newest first. | | `data[].id` | string | Webhook delivery id. | | `data[].endpointId` | string | | | `data[].event` | string | | | `data[].status` | "pending" \| "delivered" \| "failed" \| "abandoned" | `failed` is retried later; `abandoned` is not retried again. | | `data[].attempts` | integer | | | `data[].occurredAt` | string | ISO-8601 timestamp (UTC). | | `data[].createdAt` | string | ISO-8601 timestamp (UTC). | | `data[].nextAttemptAt` | string \| null | When the next retry is due; null when none is. | | `data[].deliveredAt` | string \| null | | | `data[].responseStatus` | integer \| null | | | `data[].error` | string \| null | | | `data[].replayOf` | string \| null | Set on a delivery made by a replay: the delivery it resends. | | `data[].payload` | object \| null | The JSON body as it was sent; null if the stored copy cannot be read. | | `nextCursor` | string \| null | Send it back as `?cursor=` for the next page; null on the last page. | 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/webhooks/deliveries/{id}/replay Replay a webhook delivery Resend a delivery as a new delivery with its own id and a `replayOf` field pointing at the original. The original delivery record is left unchanged, so a receiver that deduplicates on delivery id will not silently ignore the resend. Sends the payload again now, as a new delivery. A failed send is NOT an HTTP error: the answer is 200 with `ok: false`. **Notes.** - Needs the permission at the workspace root: a location-scoped key is refused. Auth: Bearer token. Permission: `integration.edit`. Parameters: | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `id` | path | string | yes | Id of the delivery to send again. | ```bash curl -X POST "https://api.brixsignage.com/v1/webhooks/deliveries/{id}/replay" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.deliveryId` | string | Id of the delivery this call made (a new `whd_…` row in the delivery log). | | `data.ok` | boolean | The receiver answered 2xx. | | `data.status` | integer \| null | HTTP status the receiver answered with; null when no answer came. | | `data.error` | string \| null | Why the delivery failed (`http 500`, a timeout, an unsafe address); null on success. | 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 delivery 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/webhooks/events List subscribable webhook events List the catalog of event types that can be subscribed to, each with a label and a description of what it carries. Only `content.recalled` and `content.restored` events are actually sent today; other listed events are not yet emitted. Auth: Bearer token. Permission: `integration.view`. ```bash curl "https://api.brixsignage.com/v1/webhooks/events" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | array of object | | | `data[].event` | "screen.offline" \| "screen.online" \| "content.recalled" \| "content.restored" \| "approval.requested" \| "approval.decided" \| "emergency.started" \| "emergency.cleared" | | | `data[].label` | string | | | `data[].what` | string | What the event means and carries. | 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. | ## Workspace Base URL: `https://api.brixsignage.com`. Send `Authorization: Bearer $BRIX_API_KEY` unless an operation says Auth: none. ### GET /v1/workspace Get the workspace Returns the calling workspace's id and name. Auth: Bearer token. Permission: `screen.view`. ```bash curl "https://api.brixsignage.com/v1/workspace" \ -H "Authorization: Bearer $BRIX_API_KEY" ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | WorkspaceRef | | | `data.id` | string | Workspace id. | | `data.name` | string \| null | Workspace name; null only if the workspace row is missing. | 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. | ### PATCH /v1/workspace Rename workspace Renames the workspace. The name must be non-empty and no more than 120 characters. Requires permission to edit billing for the whole workspace. **Notes.** - Only `name` can be changed here. Auth: Bearer token. Permission: `billing.edit`. Request body (`application/json`): | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | Trimmed; 1–120 characters. | ```bash curl -X PATCH "https://api.brixsignage.com/v1/workspace" \ -H "Authorization: Bearer $BRIX_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Riverside Coffee"}' ``` Response 200: Success. | Field | Type | Description | | --- | --- | --- | | `data` | object | | | `data.id` | string | | | `data.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: You do not hold `billing.edit` for the whole workspace (a grant at one location is not enough). | 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`: `name` missing, empty or over 120 characters. | 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. |