# 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: <perm>`. |
| 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.
