# impartire API — Reference: Playout channels

Every path is under `https://impartire.com/api/v1`. The machine-readable contract is https://impartire.com/api/v1/openapi.json (OpenAPI 3.1).

#### GET /api/v1/channels

List the org's playout channels

The org's playout channels as the DAM stores them — mode, the service each airs, whether it records and where — by name. Never a stream key, an ingest key or a destination, for any credential. Not the live on-air state: reading never calls the playout engine; that status is served once the orchestration tier owns the channel's convergence loop. Poll with `updated_after` (exclusive, millisecond). For an OAuth token, its person needs membership of the org (no platform-admin bypass); a key reads the whole org. 404 when the org does not have playout, and for an OAuth token whose person is not a member of the org.

Scopes: `channels:read`

Parameters:

- `updated_after` (query, string (date-time)) — Only channels changed strictly after this instant (ISO 8601 with offset), compared to the millisecond.
- `limit` (query, integer) — Page size, default 50. Values above 100 are treated as 100.
- `cursor` (query, string) — next_cursor from the previous page, with the other parameters unchanged.

Response 200 — `ChannelList`

- `data` (array of Channel, required) — By name, then id.
  - `id` (string (uuid), required)
  - `name` (string, required)
  - `mode` ("strict" | "relay", required) — `strict` — a scheduled playlist the playout engine plays out; `relay` — a live feed restreamed to platforms.
  - `service` (object or null, required) — The broadcast service (EPG channel, GET /api/v1/services/{id}) the channel airs; null when none is linked.
    - `id` (string (uuid), required)
    - `name` (string, required)
  - `recording` (object or null, required) — Whether the channel records, and where the recordings go; null when it does not record.
    - `kind` ("live_input", required) — What is recorded: `live_input` — a relay channel's incoming live feed, kept as a lossless master and registered as an asset.
    - `project` (object or null, required) — The project recordings are filed into; null when none is set or it is no longer a live project of the org (they file into the org's Unfiled project).
      - `id` (string (uuid), required)
      - `name` (string or null, required) — Null — for an OAuth token — when its person may not read the project.
  - `created_at` (string (date-time), required)
  - `updated_at` (string (date-time), required)
- `next_cursor` (string or null, required) — Pass as `cursor` to get the next page; null means this is the last page.

Other responses:

- 401 (Problem) — unauthenticated — no key, or an unknown, revoked or expired one.
- 403 (Problem) — insufficient-scope — the key lacks a required scope.
- 404 (Problem) — not-found.
- 422 (Problem) — validation — a bad `updated_after`, `limit` or `cursor` (`errors[].path` names it).
- 429 (Problem) — rate-limited — the key's request budget is spent; wait Retry-After seconds.
- 500 (Problem) — internal.

#### GET /api/v1/channels/{id}

Get one playout channel

One channel, in the list's shape. 404 when the org does not have playout, and for an OAuth token whose person is not a member of the org.

Scopes: `channels:read`

Parameters:

- `id` (path, string (uuid), required) — The channel's id, from GET /api/v1/channels.
- `If-None-Match` (header, string) — An ETag from a previous response; 304 if unchanged.

Response 200 — `Channel`: A playout channel's configuration as the DAM stores it — not its live on-air state, never a stream key or destination.

- `id` (string (uuid), required)
- `name` (string, required)
- `mode` ("strict" | "relay", required) — `strict` — a scheduled playlist the playout engine plays out; `relay` — a live feed restreamed to platforms.
- `service` (object or null, required) — The broadcast service (EPG channel, GET /api/v1/services/{id}) the channel airs; null when none is linked.
  - `id` (string (uuid), required)
  - `name` (string, required)
- `recording` (object or null, required) — Whether the channel records, and where the recordings go; null when it does not record.
  - `kind` ("live_input", required) — What is recorded: `live_input` — a relay channel's incoming live feed, kept as a lossless master and registered as an asset.
  - `project` (object or null, required) — The project recordings are filed into; null when none is set or it is no longer a live project of the org (they file into the org's Unfiled project).
    - `id` (string (uuid), required)
    - `name` (string or null, required) — Null — for an OAuth token — when its person may not read the project.
- `created_at` (string (date-time), required)
- `updated_at` (string (date-time), required)

Other responses:

- 304 — Not modified — the ETag in If-None-Match is current.
- 401 (Problem) — unauthenticated — no key, or an unknown, revoked or expired one.
- 403 (Problem) — insufficient-scope — the key lacks a required scope.
- 404 (Problem) — not-found.
- 429 (Problem) — rate-limited — the key's request budget is spent; wait Retry-After seconds.
- 500 (Problem) — internal.

#### POST /api/v1/channels/{id}/start

Start a playout channel (guarded)

A guarded action (ADR 0081): it puts a service on air. The app's own start runs through the playout engine: the stored configuration is pushed, the channel started, then the on-air graphic's schedule pushed (a graphic that fails leaves the channel on air without it). Only a `strict` channel starts through the engine; a relay channel is 409. **An API key's start happens at once**: 200 with the Channel — as the DAM stores it: the answer carries no engine status (read the audit entry, or the app). **An OAuth token's does not**: 202 with a Confirmation naming the channel and the service; once its person confirms it in impartire it runs as them. Its person must be an org admin (the app's verb; no platform-admin bypass). Audited: a `channel.started` event with the engine's state after the call. 404 when the org does not have playout, and for an OAuth token whose person is not a member of the org.

Scopes: `channels:control`

Parameters:

- `id` (path, string (uuid), required) — The channel's id, from GET /api/v1/channels.
- `Idempotency-Key` (header, string) — Makes a retry safe: the same key and request within 24 hours replays the first response. 1–255 characters.

Response 200 — `Channel`: A playout channel's configuration as the DAM stores it — not its live on-air state, never a stream key or destination.

- `id` (string (uuid), required)
- `name` (string, required)
- `mode` ("strict" | "relay", required) — `strict` — a scheduled playlist the playout engine plays out; `relay` — a live feed restreamed to platforms.
- `service` (object or null, required) — The broadcast service (EPG channel, GET /api/v1/services/{id}) the channel airs; null when none is linked.
  - `id` (string (uuid), required)
  - `name` (string, required)
- `recording` (object or null, required) — Whether the channel records, and where the recordings go; null when it does not record.
  - `kind` ("live_input", required) — What is recorded: `live_input` — a relay channel's incoming live feed, kept as a lossless master and registered as an asset.
  - `project` (object or null, required) — The project recordings are filed into; null when none is set or it is no longer a live project of the org (they file into the org's Unfiled project).
    - `id` (string (uuid), required)
    - `name` (string or null, required) — Null — for an OAuth token — when its person may not read the project.
- `created_at` (string (date-time), required)
- `updated_at` (string (date-time), required)

Response 202 — `Confirmation`: A guarded action an OAuth token asked for, waiting for its person to confirm it in impartire (ADR 0081). A guarded endpoint answers 202 with this and a Location; poll it until `status` is executed, declined or expired.

- `id` (string (uuid), required)
- `status` ("pending" | "confirmed" | "declined" | "expired" | "executed", required) — pending: waiting for the person to confirm or decline it in impartire (at `confirm_url`). confirmed: they confirmed and the action is running — poll again. executed: it ran; `result` holds its answer, or `problem` why it failed. declined: they said no; it will never run. expired: nobody decided within 15 minutes; it will never run (GET /api/v1/confirmations/{id} answers 410 confirmation-expired instead of this body; the person still sees it in impartire).
- `action` (string, required) — The guarded action, e.g. `run.signal`.
- `resource` (object, required)
  - `type` (string, required) — The kind of resource the action is on, e.g. `run`.
  - `id` (string (uuid) or null, required)
  - `name` (string, required) — The resource as the person was shown it when the action was asked for.
- `summary` (string, required) — The action in words, as the person reads it on the confirmation page.
- `client_id` (string, required) — The OAuth client that asked.
- `confirm_url` (string, required) — Where the person confirms or declines it. Show it to them; the API cannot confirm.
- `created_at` (string (date-time), required)
- `expires_at` (string (date-time), required) — 15 minutes after it was asked for. A pending confirmation past it is expired.
- `decided_at` (string (date-time) or null, required) — When the person confirmed or declined it, or when it expired; null while pending.
- `result` (any, required) — When executed and the action succeeded: the action's answer (what the endpoint would have returned with a key). Null otherwise.
- `problem` (Problem or null, required) — When executed and the action failed: the Problem it failed with. Null otherwise.
  - `type` (string, required) — A stable URL naming the problem: https://impartire.com/problems/<code>. Branch on this.
  - `title` (string, required)
  - `status` (integer, required)
  - `detail` (string, required) — What went wrong and what to do about it, written for the caller.
  - `instance` (string, required) — The request path.
  - `request_id` (string, required) — Also sent as the Request-Id header. Quote it when reporting a problem.
  - `errors` (array of object) — On 422 validation: one entry per invalid field.
    - `path` (string, required) — The field or parameter, as a dotted path.
    - `message` (string, required)
  - `edit_seq` (integer) — On 412 precondition-failed: the resource's current edit_seq.
  - `etag` (string) — On 412 precondition-failed for a project, a collection, a playlist or a schedule slot: its current ETag.
  - `item_id` (string) — On 409 already-member: the existing item's id (in the collection or playlist).
  - `schema_version` (integer) — On 409 type-changed: the asset type's current schema_version.
  - `confirmation_id` (string) — On 409 confirmation-pending: the confirmation already waiting for its person (GET /api/v1/confirmations/{id}).
  - `confirm_url` (string) — On 409 confirmation-pending: where the person confirms or declines it, in impartire.

Other responses:

- 401 (Problem) — unauthenticated — no key, or an unknown, revoked or expired one.
- 403 (Problem) — insufficient-scope, or insufficient-permission — the token's person is not an org admin.
- 404 (Problem) — not-found.
- 409 (Problem) — conflict — a relay channel; confirmation-pending; or idempotency-in-flight.
- 422 (Problem) — idempotency-mismatch (an OAuth token may not send Idempotency-Key).
- 429 (Problem) — rate-limited — the key's request budget is spent; wait Retry-After seconds.
- 500 (Problem) — internal.
- 502 (Problem) — playout-unavailable — the engine did not start the channel; nothing changed on air.

#### POST /api/v1/channels/{id}/stop

Stop a playout channel (guarded)

A guarded action (ADR 0081): it takes a service off air. The app's own stop runs through the playout engine. Only a `strict` channel stops through the engine; a relay channel is 409. **An API key's stop happens at once**: 200 with the Channel — as the DAM stores it: the answer carries no engine status (read the audit entry, or the app). **An OAuth token's does not**: 202 with a Confirmation naming the channel and the service; once its person confirms it in impartire it runs as them. Its person must be an org admin (the app's verb; no platform-admin bypass). Audited: a `channel.stopped` event with the engine's state after the call. 404 when the org does not have playout, and for an OAuth token whose person is not a member of the org.

Scopes: `channels:control`

Parameters:

- `id` (path, string (uuid), required) — The channel's id, from GET /api/v1/channels.
- `Idempotency-Key` (header, string) — Makes a retry safe: the same key and request within 24 hours replays the first response. 1–255 characters.

Response 200 — `Channel`: A playout channel's configuration as the DAM stores it — not its live on-air state, never a stream key or destination.

- `id` (string (uuid), required)
- `name` (string, required)
- `mode` ("strict" | "relay", required) — `strict` — a scheduled playlist the playout engine plays out; `relay` — a live feed restreamed to platforms.
- `service` (object or null, required) — The broadcast service (EPG channel, GET /api/v1/services/{id}) the channel airs; null when none is linked.
  - `id` (string (uuid), required)
  - `name` (string, required)
- `recording` (object or null, required) — Whether the channel records, and where the recordings go; null when it does not record.
  - `kind` ("live_input", required) — What is recorded: `live_input` — a relay channel's incoming live feed, kept as a lossless master and registered as an asset.
  - `project` (object or null, required) — The project recordings are filed into; null when none is set or it is no longer a live project of the org (they file into the org's Unfiled project).
    - `id` (string (uuid), required)
    - `name` (string or null, required) — Null — for an OAuth token — when its person may not read the project.
- `created_at` (string (date-time), required)
- `updated_at` (string (date-time), required)

Response 202 — `Confirmation`: A guarded action an OAuth token asked for, waiting for its person to confirm it in impartire (ADR 0081). A guarded endpoint answers 202 with this and a Location; poll it until `status` is executed, declined or expired.

- `id` (string (uuid), required)
- `status` ("pending" | "confirmed" | "declined" | "expired" | "executed", required) — pending: waiting for the person to confirm or decline it in impartire (at `confirm_url`). confirmed: they confirmed and the action is running — poll again. executed: it ran; `result` holds its answer, or `problem` why it failed. declined: they said no; it will never run. expired: nobody decided within 15 minutes; it will never run (GET /api/v1/confirmations/{id} answers 410 confirmation-expired instead of this body; the person still sees it in impartire).
- `action` (string, required) — The guarded action, e.g. `run.signal`.
- `resource` (object, required)
  - `type` (string, required) — The kind of resource the action is on, e.g. `run`.
  - `id` (string (uuid) or null, required)
  - `name` (string, required) — The resource as the person was shown it when the action was asked for.
- `summary` (string, required) — The action in words, as the person reads it on the confirmation page.
- `client_id` (string, required) — The OAuth client that asked.
- `confirm_url` (string, required) — Where the person confirms or declines it. Show it to them; the API cannot confirm.
- `created_at` (string (date-time), required)
- `expires_at` (string (date-time), required) — 15 minutes after it was asked for. A pending confirmation past it is expired.
- `decided_at` (string (date-time) or null, required) — When the person confirmed or declined it, or when it expired; null while pending.
- `result` (any, required) — When executed and the action succeeded: the action's answer (what the endpoint would have returned with a key). Null otherwise.
- `problem` (Problem or null, required) — When executed and the action failed: the Problem it failed with. Null otherwise.
  - `type` (string, required) — A stable URL naming the problem: https://impartire.com/problems/<code>. Branch on this.
  - `title` (string, required)
  - `status` (integer, required)
  - `detail` (string, required) — What went wrong and what to do about it, written for the caller.
  - `instance` (string, required) — The request path.
  - `request_id` (string, required) — Also sent as the Request-Id header. Quote it when reporting a problem.
  - `errors` (array of object) — On 422 validation: one entry per invalid field.
    - `path` (string, required) — The field or parameter, as a dotted path.
    - `message` (string, required)
  - `edit_seq` (integer) — On 412 precondition-failed: the resource's current edit_seq.
  - `etag` (string) — On 412 precondition-failed for a project, a collection, a playlist or a schedule slot: its current ETag.
  - `item_id` (string) — On 409 already-member: the existing item's id (in the collection or playlist).
  - `schema_version` (integer) — On 409 type-changed: the asset type's current schema_version.
  - `confirmation_id` (string) — On 409 confirmation-pending: the confirmation already waiting for its person (GET /api/v1/confirmations/{id}).
  - `confirm_url` (string) — On 409 confirmation-pending: where the person confirms or declines it, in impartire.

Other responses:

- 401 (Problem) — unauthenticated — no key, or an unknown, revoked or expired one.
- 403 (Problem) — insufficient-scope, or insufficient-permission — the token's person is not an org admin.
- 404 (Problem) — not-found.
- 409 (Problem) — conflict — a relay channel; confirmation-pending; or idempotency-in-flight.
- 422 (Problem) — idempotency-mismatch (an OAuth token may not send Idempotency-Key).
- 429 (Problem) — rate-limited — the key's request budget is spent; wait Retry-After seconds.
- 500 (Problem) — internal.
- 502 (Problem) — playout-unavailable — the engine did not stop the channel; nothing changed on air.
