# 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. |
