Approvals API
Approvals endpoints in the Brix REST API for digital signage: 5 operations (GET, POST). Each lists its path, auth, required permission and a curl example.
View as Markdown · For AI agents: the full docs index is at /llms.txt
Base URL https://api.brixsignage.com. Send Authorization: Bearer $BRIX_API_KEY unless an operation says Auth: none or Auth: no API key. The Permission line names what the key must hold. See Authentication and scopes, Errors and rate limits and Pagination.
GET /v1/approvalsPOST /v1/approvalsGET /v1/approvals/{id}POST /v1/approvals/{id}/decidePOST /v1/approvals/{id}/withdraw
Every 4xx and 5xx response below returns this 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. |
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.
| Parameter | 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). |
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:<id> 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 | |
data[].canDecide | boolean | On the list and single reads: whether the caller may approve or reject the request at its current level (a named approver, or the kind's approve permission there). False once it is decided. |
Response 401: Missing, expired or revoked bearer token.
Response 403: The token lacks the permission this operation needs (see x-brix-permission).
Response 5XX: Server error. The body carries a requestId to quote to support.
POST /v1/approvals
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:<id> for a key).
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. |
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:<id> 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 | |
data.canDecide | boolean | On the list and single reads: whether the caller may approve or reject the request at its current level (a named approver, or the kind's approve permission there). False once it is decided. |
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:<id> 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 | |
data.canDecide | boolean | On the list and single reads: whether the caller may approve or reject the request at its current level (a named approver, or the kind's approve permission there). False once it is decided. |
Response 401: Missing, expired or revoked bearer token.
Response 403: The content is approved, and you do not hold its approve permission (playlist.approve, …) at its location.
Response 404: No such content, or it is at a location where you do not hold screen.view.
Response 422: contentKind, contentId or contentName is missing.
Response 5XX: Server error. The body carries a requestId to quote to support.
GET /v1/approvals/{id}
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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | Approval request id. |
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:<id> 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 | |
data.canDecide | boolean | On the list and single reads: whether the caller may approve or reject the request at its current level (a named approver, or the kind's approve permission there). False once it is decided. |
Response 401: Missing, expired or revoked bearer token.
Response 403: The token lacks the permission this operation needs (see x-brix-permission).
Response 404: No such request, or you cannot view (or approve) that kind of content at its location.
Response 5XX: Server error. The body carries a requestId to quote to support.
POST /v1/approvals/{id}/decide
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.
| Parameter | 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. |
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:<id> 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 | |
data.canDecide | boolean | On the list and single reads: whether the caller may approve or reject the request at its current level (a named approver, or the kind's approve permission there). False once it is decided. |
Response 401: Missing, expired or revoked bearer token.
Response 403: Not eligible to decide this level, or self_approval (another approver exists).
Response 404: No such request.
Response 409: already_decided.
Response 422: Bad decision, or a rejection without decisionNote.
Response 5XX: Server error. The body carries a requestId to quote to support.
POST /v1/approvals/{id}/withdraw
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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | yes | Approval request id. |
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:<id> 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 | |
data.canDecide | boolean | On the list and single reads: whether the caller may approve or reject the request at its current level (a named approver, or the kind's approve permission there). False once it is decided. |
Response 401: Missing, expired or revoked bearer token.
Response 403: You are not the requester, and not an approver for the current level.
Response 404: No such request.
Response 409: already_decided: the request is no longer pending.
Response 5XX: Server error. The body carries a requestId to quote to support.