# 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_<region>_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`.
