# Alteox Media Server REST API — public reference, generated by scripts/sync-openapi.mjs.
openapi: 3.1.0
info:
  title: Alteox Media Server REST API
  version: "1"
  summary: Control and monitor the Alteox live media server.
  description: |
    Human-readable reference with all rules: docs/API.md. Curl and Python
    recipes: docs/API-EXAMPLES.md.

    - JSON everywhere except `/config` (YAML), `/events` (server-sent
      events), `/health?format=nagios` (plain text) and this document.
    - Unknown request fields are rejected (`400 bad_request`).
    - Every non-2xx response has the `Error` body.
    - Durations are Go duration strings (`"2s"`, `"500ms"`), configured
      bitrates `"6000k"`/`"6M"` or an integer (bit/s); measured values are
      plain numbers (bit/s, seconds).
    - Writes are persisted to the config file (and in a cluster replicated
      through the raft leader) before the response is sent.
  license:
    name: Proprietary
    identifier: LicenseRef-Alteox-Proprietary
servers:
  - url: /api/v1
security:
  - bearerAuth: []
  - basicAuth: []
  - sessionCookie: []
tags:
  - name: identity
    description: Identity and server
  - name: auth
    description: Web UI sessions (cookie login
    logout: null
    revocation): null
  - name: health
    description: Health and readiness for monitoring
  - name: channels
    description: Channel configuration and control
  - name: events
    description: Live updates (server-sent events)
  - name: profiles
    description: Transcode profiles
  - name: templates
    description: Channel templates (shared channel settings with per-channel overrides)
  - name: capabilities
    description: FFmpeg / hardware probe
  - name: sessions
    description: Client sessions
  - name: config
    description: Configuration import/export (YAML)
  - name: cluster
    description: Cluster membership and placement
  - name: gpus
    description: GPU monitoring
    admission control and alarms (WP 6.3): null
  - name: license
    description: "Licensing: status, license key, EULA, deactivation (docs/LICENSING.md, API.md §16)"
  - name: notifications
    description: Operator notifications via Mattermost and e-mail (WP 6.9)
  - name: event-sinks
    description: Event sinks (play_closed
    source_*) and their delivery status: null
paths:
  /healthz:
    servers:
      - url: /
    get:
      tags:
        - health
      operationId: healthz
      summary: Liveness probe (no authentication)
      description: "`200` while the process serves HTTP. Plain text: `ok`, version, uptime, channel count."
      security: []
      responses:
        "200":
          description: Alive.
          content:
            text/plain:
              schema:
                type: string
                pattern: ^ok\n
              example: |
                ok
                version 0.6.0
                uptime 3h2m11s
                channels 6
  /readyz:
    servers:
      - url: /
    get:
      tags:
        - health
      operationId: readyz
      summary: Readiness probe (no authentication)
      description: |
        `200` when the node can do its job: configuration loaded, HTTP (and
        SRT) listeners bound, channels applied and, in cluster mode, joined
        with a known leader and a replicated configuration. `503` with the
        failing checks otherwise, also while shutting down.
      security: []
      responses:
        "200":
          description: Ready.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Readiness"
              example:
                ready: true
                checks:
                  - name: config
                    ok: true
                    detail: loaded
                  - name: http_listener
                    ok: true
                    detail: 0.0.0.0:8080
                  - name: srt_listener
                    ok: true
                    detail: :9000
                  - name: channels
                    ok: true
                    detail: 6 configured
                reasons: []
        "503":
          description: Not ready; `reasons` lists the failing checks.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Readiness"
              example:
                ready: false
                checks:
                  - name: config
                    ok: true
                    detail: loaded
                  - name: cluster
                    ok: false
                    detail: no leader (no quorum)
                reasons:
                  - "cluster: no leader (no quorum)"
  /openapi.yaml:
    get:
      tags:
        - identity
      operationId: getOpenAPI
      summary: This OpenAPI document
      responses:
        "200":
          description: The embedded docs/openapi.yaml.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/yaml:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
  /me:
    get:
      tags:
        - identity
      operationId: getMe
      summary: Who am I
      responses:
        "200":
          description: The authenticated identity.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Identity"
              example:
                user: admin
                method: basic
                role: admin
                allow_extra_args: false
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          $ref: "#/components/responses/Unavailable"
  /auth/login:
    post:
      tags:
        - auth
      operationId: login
      summary: Log in (web UI) and get a session cookie
      description: |
        Exchanges a user/password (`api.users`) or an API key (`api.keys`)
        for the session cookie `alteoxms_session` (opaque, HttpOnly,
        SameSite=Strict, `Secure` on HTTPS, Path=/). Nothing secret has to
        be kept by the browser. No credentials are needed for this call;
        failed attempts are throttled like other logins (`429`). A browser
        request from another origin is rejected (`403`). The response
        carries the CSRF token that cookie-authenticated `POST`, `PUT`,
        `PATCH` and `DELETE` requests must send as `X-CSRF-Token`.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
            example:
              user: admin
              password: s3cret
      responses:
        "200":
          description: Logged in; `Set-Cookie` carries the session.
          headers:
            Set-Cookie:
              schema:
                type: string
              description: "`alteoxms_session=<id>; Path=/; HttpOnly; SameSite=Strict[; Secure]`"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Identity"
              example:
                user: admin
                method: session
                role: admin
                allow_extra_args: false
                csrf_token: q3V0Zm9v…
                expires_at: 2026-10-10T12:00:00.000Z
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/TooManyRequests"
        "503":
          $ref: "#/components/responses/Unavailable"
  /auth/logout:
    post:
      tags:
        - auth
      operationId: logout
      summary: End the current UI session
      description: |
        Deletes the session of the `alteoxms_session` cookie (on every
        cluster node) and clears the cookie. Needs `X-CSRF-Token` while the
        session is valid; without a (valid) session it only clears the
        cookie (`204`).
      security:
        - sessionCookie: []
        - {}
      parameters:
        - name: X-CSRF-Token
          in: header
          required: false
          schema:
            type: string
      responses:
        "204":
          description: Logged out (cookie cleared).
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/Unavailable"
  /auth/sessions:
    delete:
      tags:
        - auth
      operationId: revokeSessions
      summary: Revoke UI sessions (all, or one user's)
      description: |
        Ends the web UI sessions of `user` (a user or API key name), or of
        everyone without `user`. Sessions also end automatically when their
        user/key is removed or gets another role or password.
      parameters:
        - name: user
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: Sessions revoked.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - revoked
                properties:
                  revoked:
                    type: integer
                    minimum: 0
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/Unavailable"
  /server:
    get:
      tags:
        - identity
      operationId: getServer
      summary: Version, uptime, node, CPU/memory and totals
      responses:
        "200":
          description: Server information.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServerInfo"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /health:
    get:
      tags:
        - health
      operationId: getHealth
      summary: Aggregated health for monitoring (JSON or Nagios)
      description: |
        `status` is `critical` when a channel failed, a critical alarm is
        active or the cluster has no leader; `degraded` when a channel is
        degraded (or starting), a warning alarm is active, or a transcoder
        restarted / an input failed over in the last 10 minutes; else `ok`.
        Stopped channels (disabled, or running on another cluster node) are
        counted but are not problems.

        `?format=nagios` returns one Nagios/Icinga plugin line
        (`ALTEOXMS <STATE> - <text> | <perfdata>`). State → exit code →
        HTTP status: `OK` → 0 → `200`, `WARNING` → 1 → `200`, `CRITICAL` →
        2 → `503`, `UNKNOWN` → 3 → `503`; the exit code is also in
        `X-Nagios-Exit-Code`. The JSON form always answers `200`.
      parameters:
        - name: format
          in: query
          schema:
            type: string
            enum:
              - json
              - nagios
            default: json
      responses:
        "200":
          description: Health report (JSON), or a Nagios line with state OK or WARNING.
          headers:
            X-Nagios-Exit-Code:
              $ref: "#/components/headers/NagiosExitCode"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Health"
              example:
                status: degraded
                node: ms-fra-1
                ts: 2026-10-02T14:03:11.250Z
                reasons:
                  - 1 channel degraded
                  - 1 input failover in the last 10 min
                channels:
                  total: 6
                  ok: 4
                  degraded: 1
                  failed: 0
                  stopped: 1
                  sleeping: 0
                problems:
                  - channel: sport1
                    state: running
                    health: degraded
                    reason: "running on backup input 1 (input 0: srt: connection refused)"
                transcoder_restarts_10m: 0
                input_failovers_10m: 1
                alarms_available: false
                alarms: []
                cluster:
                  enabled: false
                  leader: ms-fra-1
            text/plain:
              schema:
                type: string
                pattern: ^ALTEOXMS (OK|WARNING) - [^\n|]* \| [^\n]*\n$
              example: |
                ALTEOXMS WARNING - 4/5 channels ok, 1 stopped; 1 channel degraded; sport1 degraded: running on backup input 1 | channels=6 ok=4 degraded=1 failed=0 stopped=1 transcoder_restarts_10m=0 input_failovers_10m=1 alarms=0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          description: Nagios format with state CRITICAL (or UNKNOWN); or the API is disabled (JSON error).
          headers:
            X-Nagios-Exit-Code:
              $ref: "#/components/headers/NagiosExitCode"
          content:
            text/plain:
              schema:
                type: string
                pattern: ^ALTEOXMS (CRITICAL|UNKNOWN) - [^\n|]* \| [^\n]*\n$
              example: |
                ALTEOXMS CRITICAL - 5/6 channels ok; 1 channel failed; news24 failed: no healthy input (input 0: srt: connection refused) | channels=6 ok=5 degraded=0 failed=1 stopped=0 transcoder_restarts_10m=0 input_failovers_10m=0 alarms=0
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
  /channels:
    get:
      tags:
        - channels
      operationId: listChannels
      summary: Channels with status summary
      parameters:
        - name: label
          in: query
          description: "Label filter, repeatable (all must match): `key=value` or `key` (label present)."
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
          example:
            - team=sport
        - name: template
          in: query
          description: Channels using one of these templates (repeatable); empty = channels without template.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
      responses:
        "200":
          description: Channels sorted by name.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - channels
                properties:
                  channels:
                    type: array
                    items:
                      $ref: "#/components/schemas/ChannelSummary"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags:
        - channels
      operationId: createChannel
      summary: Create a channel (starts immediately unless enabled is false)
      parameters:
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChannelConfigRequest"
            example:
              name: sport1
              inputs:
                - url: srt://encoder-a:9000?mode=caller&latency=200
                - url: udp://239.1.1.1:1234
              failover:
                loss_timeout: 2s
                return_after: 30s
              outputs:
                srt: true
                hls:
                  segment: 4s
                  window: 6
              labels:
                team: sport
      responses:
        "201":
          description: Created; body is the stored configuration.
          headers:
            Location:
              $ref: "#/components/headers/Location"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/LicenseLimit"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "413":
          $ref: "#/components/responses/TooLarge"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /channels/actions:
    post:
      tags:
        - channels
      operationId: bulkChannelAction
      summary: Restart, start or stop many channels
      description: |
        Targets either an explicit `channels` list or a `selector`
        (`name_prefix` and/or `label`, both must match; an empty selector is
        rejected). `start`/`stop` change `enabled` of all targets in **one**
        configuration write (persisted / replicated once); if that write
        fails the request fails as a whole and nothing changed. `restart`
        restarts the running channels on this node (up to 8 in parallel).

        The response is always `200` when the request itself was valid
        (multi-status semantics without HTTP 207): see `results[].status`
        and the `succeeded` / `failed` counts. Names in `channels` that do
        not exist are reported with `code: not_found`. A channel that is
        itself named `actions` keeps working with GET/PUT/PATCH/DELETE on
        this path.
      parameters:
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkActionRequest"
            examples:
              byLabel:
                value:
                  action: restart
                  selector:
                    label: team=sport
              byName:
                value:
                  action: stop
                  channels:
                    - sport1
                    - sport2
      responses:
        "200":
          description: Per-channel results.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkActionResponse"
              example:
                action: restart
                dry_run: false
                matched: 2
                succeeded: 1
                failed: 2
                results:
                  - name: sport1
                    status: ok
                  - name: sport2
                    status: error
                    code: conflict
                    message: channel is not running (disabled, on another node, or its profile is missing)
                  - name: ghost
                    status: error
                    code: not_found
                    message: channel "ghost" not found
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/LicenseLimit"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /channels/{name}:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    get:
      tags:
        - channels
      operationId: getChannel
      summary: Channel configuration
      description: |
        Secrets in URLs are verbatim for admins and redacted (`***`) for
        viewers or with `?redact=true`. A channel with a `template` is
        returned in its stored form: only the fields it sets itself
        (`overrides` lists their paths), plus the read-only `effective`
        channel (template ⊕ channel) the runtime uses.
      parameters:
        - name: redact
          in: query
          schema:
            type: boolean
      responses:
        "200":
          description: The channel configuration.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      tags:
        - channels
      operationId: putChannel
      summary: Replace the channel configuration
      description: |
        Full replacement; `name` may be omitted (renaming is not supported).
        Omitted `enabled`, `labels`, `failover.node_standby` and
        `outputs.hls.ts_segments` keep their stored values. A URL secret of
        exactly `***` keeps the stored value. With `template` the body is
        the channel's stored form: omitted fields are inherited from the
        template, sent fields are stored as overrides; without `template`
        a templated channel keeps its template and stores the fields that
        differ from it; `template: null` detaches it (docs/API.md 4.14).
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChannelConfigRequest"
      responses:
        "200":
          description: Stored configuration.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/LicenseLimit"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
    patch:
      tags:
        - channels
      operationId: patchChannel
      summary: Change parts of the channel configuration (JSON Merge Patch)
      description: |
        RFC 7386 JSON Merge Patch over the configuration as an admin's GET
        returns it: objects merge, `null` removes a field (it takes its
        default; for a templated channel: the template's value again),
        arrays (`inputs`, `outputs.udp`) are replaced as a whole.
        The result is validated exactly like a PUT (same `details`), with
        `If-Match`, `dry_run` and `***` secrets. Content-Type
        `application/merge-patch+json` (or `application/json`).
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: "#/components/schemas/ChannelPatch"
            examples:
              failover:
                value:
                  failover:
                    loss_timeout: 3s
                  labels:
                    tier: gold
              inputs:
                value:
                  inputs:
                    - url: srt://enc-a:9000?mode=caller&passphrase=***
                    - url: udp://239.1.1.9:1234
              disableHLS:
                value:
                  outputs:
                    hls: null
          application/json:
            schema:
              $ref: "#/components/schemas/ChannelPatch"
      responses:
        "200":
          description: Stored configuration.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/LicenseLimit"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
    delete:
      tags:
        - channels
      operationId: deleteChannel
      summary: Delete the channel (closes its sessions)
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      responses:
        "204":
          description: Deleted.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /channels/{name}/status:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    get:
      tags:
        - channels
      operationId: getChannelStatus
      summary: Full live status
      responses:
        "200":
          description: Live status (refreshed at least once per second).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelStatus"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /channels/{name}/start:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    post:
      tags:
        - channels
      operationId: startChannel
      summary: Start a stopped channel (enabled = true, persisted)
      description: "Idempotent (`changed: false` when it already was enabled). The body is ignored."
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      responses:
        "200":
          description: New state.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnabledResult"
              example:
                name: sport1
                enabled: true
                changed: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/LicenseLimit"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /channels/{name}/stop:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    post:
      tags:
        - channels
      operationId: stopChannel
      summary: Stop a channel without deleting it (enabled = false, persisted)
      description: |
        Stops inputs, transcoder and outputs and closes the channel's
        sessions; the configuration is kept and the status shows
        `state: stopped`. Idempotent. The body is ignored.
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      responses:
        "200":
          description: New state.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnabledResult"
              example:
                name: sport1
                enabled: false
                changed: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /channels/{name}/make-static:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    post:
      tags:
        - channels
      operationId: makeChannelStatic
      summary: Store a dynamic channel as a configured channel
      description: |
        Copies the configuration a dynamic channel (server.config_lookup)
        currently runs with into the configuration (persisted like a POST
        /channels); from then on the configured channel wins and the
        lookup server is no longer asked for it. The running channel keeps
        running. Names with "/" cannot be configured channels (422).
      parameters:
        - $ref: "#/components/parameters/DryRun"
      responses:
        "201":
          description: Stored configuration.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
            Location:
              $ref: "#/components/headers/Location"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /channels/{name}/restart:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    post:
      tags:
        - channels
      operationId: restartChannel
      summary: Restart the channel runtime
      responses:
        "202":
          description: Restarting.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - status
                properties:
                  status:
                    const: restarting
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
  /channels/{name}/switch-input:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    post:
      tags:
        - channels
      operationId: switchInput
      summary: Manual failover (index) or back to automatic (null)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - index
              properties:
                index:
                  type:
                    - integer
                    - "null"
                  minimum: 0
            examples:
              pin:
                value:
                  index: 1
              auto:
                value:
                  index: null
      responses:
        "200":
          description: Switch requested (happens at the next keyframe).
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - active_input
                  - failover_mode
                properties:
                  active_input:
                    type:
                      - integer
                      - "null"
                  failover_mode:
                    $ref: "#/components/schemas/FailoverMode"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
  /channels/{name}/tracks/preview:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    post:
      tags:
        - channels
      operationId: previewTracks
      summary: Resolve a track selection against the live source (nothing is applied)
      description: |
        Any role. Resolves `tracks` (null = no selection) against the source
        program of `input` (default: the active input). A channel that does
        not run on this node resolves against no source (every slot
        missing, `input` null).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                tracks:
                  oneOf:
                    - type: "null"
                    - $ref: "#/components/schemas/TracksRequest"
                input:
                  type:
                    - integer
                    - "null"
                  minimum: 0
            examples:
              german:
                value:
                  tracks:
                    audio:
                      - lang:deu
                    teletext:
                      - page:777
      responses:
        "200":
          description: Resolution.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrackMap"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
  /channels/{name}/preview-token:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    post:
      tags:
        - channels
      operationId: createPreviewToken
      summary: Short-lived token to play the channel without the auth hook
      description: "Admins; viewers only with `api.viewer_preview: true`."
      requestBody:
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                ttl:
                  $ref: "#/components/schemas/Duration"
                  description: 10s..15m, default 5m
      responses:
        "201":
          description: Token and playback URLs (`""` for disabled outputs).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PreviewToken"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /events:
    get:
      tags:
        - events
      operationId: getEvents
      summary: Server-sent events (live status)
      description: |
        `text/event-stream`. Events (`event:` → `data:` schema): `snapshot` →
        `EventSnapshot`, `status` → `EventStatus` (changed channels only),
        `channel_status` → `ChannelStatus` (for `?channel=`),
        `channel_added` → `ChannelSummary`, `channel_removed` →
        `EventChannelRemoved`, `switch` → `EventSwitch`, `alarm_raised` /
        `alarm_cleared` → `AlarmEvent`. Keepalive comment
        every 15 s, `retry: 3000`, no replay after reconnect.
      security:
        - bearerAuth: []
        - basicAuth: []
        - accessToken: []
      parameters:
        - name: channel
          in: query
          description: Also stream the full status of these channels (repeatable).
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
      responses:
        "200":
          description: Event stream.
          content:
            text/event-stream:
              schema:
                type: string
        "401":
          $ref: "#/components/responses/Unauthorized"
  /profiles:
    get:
      tags:
        - profiles
      operationId: listProfiles
      summary: Transcode profiles
      responses:
        "200":
          description: Profiles sorted by name.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - profiles
                properties:
                  profiles:
                    type: array
                    items:
                      $ref: "#/components/schemas/Profile"
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags:
        - profiles
      operationId: createProfile
      summary: Create a transcode profile
      parameters:
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProfileRequest"
            example:
              name: abr2
              video:
                - name: 720p
                  codec: h264
                  width: 1280
                  height: 720
                  bitrate: 3000k
                - name: 360p
                  codec: h264
                  width: 640
                  height: 360
                  bitrate: 800k
              audio:
                - tracks: all
                  codec: aac
                  bitrate: 128k
      responses:
        "201":
          description: Created.
          headers:
            Location:
              $ref: "#/components/headers/Location"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /profiles/{name}:
    parameters:
      - $ref: "#/components/parameters/ProfileName"
    get:
      tags:
        - profiles
      operationId: getProfile
      summary: One profile
      responses:
        "200":
          description: The profile.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      tags:
        - profiles
      operationId: putProfile
      summary: Replace a profile (channels using it restart their transcoder)
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProfileRequest"
      responses:
        "200":
          description: Stored profile.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
    patch:
      tags:
        - profiles
      operationId: patchProfile
      summary: Change parts of a profile (JSON Merge Patch)
      description: Like `PATCH /channels/{name}`; `video` and `audio` arrays are replaced as a whole.
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: "#/components/schemas/ProfilePatch"
            example:
              deinterlace: on
              subtitles:
                copy: true
                copy_scte35: false
          application/json:
            schema:
              $ref: "#/components/schemas/ProfilePatch"
      responses:
        "200":
          description: Stored profile.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Profile"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
    delete:
      tags:
        - profiles
      operationId: deleteProfile
      summary: Delete a profile (409 in_use when channels use it)
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      responses:
        "204":
          description: Deleted.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /channel-templates:
    get:
      tags:
        - templates
      operationId: listChannelTemplates
      summary: Channel templates
      responses:
        "200":
          description: Templates sorted by name.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - templates
                properties:
                  templates:
                    type: array
                    items:
                      $ref: "#/components/schemas/ChannelTemplate"
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags:
        - templates
      operationId: createChannelTemplate
      summary: Create a channel template
      parameters:
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChannelTemplateRequest"
            example:
              name: sport-sd
              failover:
                loss_timeout: 3s
                standby: cold
              transcode: SD-allAudio
              outputs:
                http_ts: true
                hls:
                  segment: 2s
              labels:
                team: sport
      responses:
        "201":
          description: Created.
          headers:
            Location:
              $ref: "#/components/headers/Location"
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelTemplate"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /channel-templates/{name}:
    parameters:
      - $ref: "#/components/parameters/TemplateName"
    get:
      tags:
        - templates
      operationId: getChannelTemplate
      summary: One channel template
      responses:
        "200":
          description: The template.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelTemplate"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      tags:
        - templates
      operationId: putChannelTemplate
      summary: Replace a template (re-applied to its channels; only channels whose effective configuration changed restart)
      description: |
        The body is the complete set of the template's settings (omitted =
        not set). Every channel using the template keeps its overrides and
        gets the new effective configuration; a change that makes one of
        them invalid is `422` with details under `channels[i]...` naming
        the channel.
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ChannelTemplateRequest"
      responses:
        "200":
          description: Stored template.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelTemplate"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
    patch:
      tags:
        - templates
      operationId: patchChannelTemplate
      summary: Change parts of a template (JSON Merge Patch)
      description: Like `PATCH /channels/{name}`; `null` removes a setting from the template.
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: "#/components/schemas/ChannelTemplatePatch"
            example:
              outputs:
                hls:
                  window: 8
              on_demand: null
          application/json:
            schema:
              $ref: "#/components/schemas/ChannelTemplatePatch"
      responses:
        "200":
          description: Stored template.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelTemplate"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
    delete:
      tags:
        - templates
      operationId: deleteChannelTemplate
      summary: Delete a template (409 in_use with the channels using it)
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      responses:
        "204":
          description: Deleted.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /capabilities:
    get:
      tags:
        - capabilities
      operationId: getCapabilities
      summary: Hardware and encoders available for transcoding
      responses:
        "200":
          description: Probe result (without FFmpeg everything is unavailable, still 200).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Capabilities"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /capabilities/probe:
    post:
      tags:
        - capabilities
      operationId: probeCapabilities
      summary: Re-run the FFmpeg/hardware probe
      responses:
        "200":
          description: New probe result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Capabilities"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /sessions:
    get:
      tags:
        - sessions
      operationId: listSessions
      summary: Client sessions (newest first)
      parameters:
        - name: channel
          in: query
          schema:
            type: string
        - name: protocol
          in: query
          schema:
            type: string
            enum:
              - srt
              - http-ts
              - hls
              - dash
        - name: mode
          in: query
          schema:
            type: string
            enum:
              - read
              - publish
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 0
            maximum: 5000
            default: 500
        - name: offset
          in: query
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: Sessions.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - total
                  - sessions
                properties:
                  total:
                    type: integer
                    minimum: 0
                  sessions:
                    type: array
                    items:
                      $ref: "#/components/schemas/Session"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
    delete:
      tags:
        - sessions
      operationId: kickSessions
      summary: Kick every session of a user, token or client IP (all protocols, cluster-wide), optionally block it
      description: |
        Selectors are combined (all given ones must match); at least one of `user`, `token`, `ip` is
        required. Kicks SRT (shared listener and dedicated ports), HTTP-TS and HLS/DASH sessions and
        drops the auth hook's cached decisions for the selector, so reconnects ask the hook again.
        With `block` the selector is blocked first: matching clients are rejected before the auth
        hook is asked (SRT reject 1403, HTTP 403) until the block expires or is removed. In a cluster
        the kick is forwarded to every node and the block is replicated.
      parameters:
        - name: user
          in: query
          schema:
            type: string
        - name: token
          in: query
          schema:
            type: string
        - name: ip
          in: query
          description: Client IP address or CIDR prefix.
          schema:
            type: string
        - name: channel
          in: query
          description: Only this channel (default all).
          schema:
            type: string
        - $ref: "#/components/parameters/Block"
      responses:
        "200":
          description: Kicked sessions (possibly none) and the block.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KickResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /sessions/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    delete:
      tags:
        - sessions
      operationId: kickSession
      summary: Kick a session (optionally block its user/token/IP on its channel)
      description: |
        Drops the auth hook's cached decisions for the session's channel, user, token and IP so a
        reconnect asks the hook again. In a cluster a session that is not on this node is looked up
        on the other nodes. With `block` the session's user (else token, else client IP) is blocked
        on the session's channel.
      parameters:
        - $ref: "#/components/parameters/Block"
      responses:
        "200":
          description: Kicked and blocked (with `block`).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KickResult"
        "204":
          description: Closed.
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /blocks:
    get:
      tags:
        - sessions
      operationId: listBlocks
      summary: Active temporary blocks (newest first)
      responses:
        "200":
          description: Blocks.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - blocks
                properties:
                  blocks:
                    type: array
                    items:
                      $ref: "#/components/schemas/Block"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /blocks/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
    delete:
      tags:
        - sessions
      operationId: deleteBlock
      summary: Remove a block (cluster-wide)
      responses:
        "204":
          description: Removed.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"
  /config:
    get:
      tags:
        - config
      operationId: getConfig
      summary: Export the full configuration (JSON or YAML)
      parameters:
        - name: redact
          in: query
          description: Redact secrets (always for viewers).
          schema:
            type: boolean
        - name: format
          in: query
          description: "Output format; default: the format of the server's config file."
          schema:
            type: string
            enum:
              - json
              - yaml
      responses:
        "200":
          description: Config document (ARCHITECTURE §5 schema) in the config file's format, or the one asked for.
          headers:
            ETag:
              $ref: "#/components/headers/ETag"
          content:
            application/json:
              schema:
                type: object
            application/yaml:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
    put:
      tags:
        - config
      operationId: putConfig
      summary: Import the full configuration (JSON or YAML, detected from the content)
      parameters:
        - $ref: "#/components/parameters/IfMatch"
        - $ref: "#/components/parameters/DryRun"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
          application/yaml:
            schema:
              type: string
      responses:
        "200":
          description: Applied (or, with dry_run, what would change).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConfigImportResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/LicenseLimit"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "412":
          $ref: "#/components/responses/PreconditionFailed"
        "413":
          $ref: "#/components/responses/TooLarge"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /config/validate:
    post:
      tags:
        - config
      operationId: validateConfig
      summary: Validate a configuration (JSON or YAML) without applying it
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
          application/yaml:
            schema:
              type: string
      responses:
        "200":
          description: Validation result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConfigValidation"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "413":
          $ref: "#/components/responses/TooLarge"
  /analytics:
    get:
      tags:
        - status
      operationId: getAnalytics
      summary: Per-channel and per-source history (bitrate, viewers, errors, TR 101 290) and node CPU/GPU of the last hour, day or week
      description: "In-memory history since the server started (docs/API.md 14): totals per time bucket, per-channel sums ranked by operational errors, and with channel= that channel's full series."
      parameters:
        - name: range
          in: query
          schema:
            type: string
            enum:
              - 1h
              - 24h
              - 7d
            default: 1h
          description: "1h: 10 s buckets, 24h: 1 min, 7d: 10 min."
        - name: channel
          in: query
          schema:
            type: string
          description: Also return this channel's full series.
      responses:
        "200":
          description: Analytics of the range.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Analytics"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /cluster:
    get:
      tags:
        - cluster
      operationId: getCluster
      summary: Cluster nodes, roles, health, capacity and placement
      responses:
        "200":
          description: Cluster view (standalone nodes return the short form).
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ClusterStatus"
                  - $ref: "#/components/schemas/StandaloneStatus"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /cluster/placement:
    get:
      tags:
        - cluster
      operationId: getClusterPlacement
      summary: Channel placement
      responses:
        "200":
          description: Placement.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - placement
                  - unplaced
                  - unplaced_reasons
                  - strategy
                  - max_channels_per_node
                properties:
                  placement:
                    $ref: "#/components/schemas/Placement"
                  unplaced:
                    type: array
                    items:
                      type: string
                  unplaced_reasons:
                    type: object
                    additionalProperties:
                      type: string
                  strategy:
                    type: string
                    enum:
                      - spread
                      - pack
                  max_channels_per_node:
                    type: integer
                    minimum: 0
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/Unavailable"
  /cluster/events:
    get:
      tags:
        - cluster
      operationId: getClusterEvents
      summary: Cluster events (newest first, last 256)
      parameters:
        - name: after
          in: query
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: Events.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - events
                properties:
                  events:
                    type: array
                    items:
                      $ref: "#/components/schemas/ClusterEvent"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "503":
          $ref: "#/components/responses/Unavailable"
  /cluster/channels/{name}:
    parameters:
      - $ref: "#/components/parameters/ChannelName"
    get:
      tags:
        - cluster
      operationId: locateChannel
      summary: Where a channel is served
      responses:
        "200":
          description: Location.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelLocation"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"
  /cluster/nodes:
    post:
      tags:
        - cluster
      operationId: addClusterNode
      summary: Add a node (raft voter)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClusterNodeRef"
            example:
              id: ms-fra-4
              address: 10.0.0.8:7946
      responses:
        "201":
          description: Added.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClusterNodeRef"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /cluster/nodes/{id}:
    parameters:
      - $ref: "#/components/parameters/NodeID"
    delete:
      tags:
        - cluster
      operationId: removeClusterNode
      summary: Remove a node (its channels move)
      responses:
        "204":
          description: Removed.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          $ref: "#/components/responses/Conflict"
        "503":
          $ref: "#/components/responses/Unavailable"
  /cluster/nodes/{id}/drain:
    parameters:
      - $ref: "#/components/parameters/NodeID"
    post:
      tags:
        - cluster
      operationId: drainClusterNode
      summary: Drain a node (maintenance)
      responses:
        "202":
          description: Draining.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DrainResult"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"
    delete:
      tags:
        - cluster
      operationId: undrainClusterNode
      summary: End draining (nothing moves back automatically)
      responses:
        "202":
          description: No longer draining.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DrainResult"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"
  /gpus:
    get:
      tags:
        - gpus
      operationId: getGPUs
      summary: GPU monitoring, admission state and policy of this node (WP 6.3)
      description: Without monitoring configured `monitor.state` is `off` and the lists are empty (still `200`). Unreported driver values are `null`.
      responses:
        "200":
          description: GPU view.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GPUInfo"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /gpus/capacity:
    get:
      tags:
        - gpus
      operationId: getGPUCapacity
      summary: How many more channels of a profile fit on the GPUs
      parameters:
        - name: profile
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Capacity per GPU, node total and (cluster mode) per node. `total` -1 when the profile does not use an NVIDIA GPU here or monitoring is off.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GPUCapacity"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
  /alarms:
    get:
      tags:
        - gpus
      operationId: getAlarms
      summary: Active alarms and the last raise/clear events of this node
      parameters:
        - name: after
          in: query
          description: Only events with a larger seq.
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: Alarms (sorted by name and labels) and events (newest first, at most 256).
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - node
                  - alarms
                  - events
                  - last_seq
                properties:
                  node:
                    type: string
                  alarms:
                    type: array
                    items:
                      $ref: "#/components/schemas/Alarm"
                  events:
                    type: array
                    items:
                      $ref: "#/components/schemas/AlarmEvent"
                  last_seq:
                    type: integer
                    minimum: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /license:
    get:
      tags:
        - license
      operationId: getLicense
      summary: License status of this server (admin)
      description: |
        Status, tier, limits, expiry, lease age, install id, fingerprint,
        EULA, warnings and the license server's messages (docs/LICENSING.md
        §5). `mode: dev` = a build without embedded license keys: nothing
        is enforced (`status: dev`, `serving: true`).
      responses:
        "200":
          description: License status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LicenseStatus"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "503":
          $ref: "#/components/responses/Unavailable"
    put:
      tags:
        - license
      operationId: putLicense
      summary: Enter a license key (activate) (admin)
      description: |
        Verifies the key (`AMS1.…`, signed by the license server), activates
        it at the license server (`POST /activate`; not for offline
        licenses) and stores it. Errors: `400 invalid_license` (does not
        verify, offline license for another machine), `409` with the license
        server's code (`seats_exhausted`, `revoked`, `expired`) or
        `dev_build`, `502 license_server_unavailable` (the key is stored and
        activation is retried in the background).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - license
              properties:
                license:
                  type: string
                  maxLength: 16384
                  description: The license key text (AMS1.<payload>.<signature>).
      responses:
        "200":
          description: Activated; the new status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LicenseStatus"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/LicenseRefused"
        "413":
          $ref: "#/components/responses/TooLarge"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "502":
          $ref: "#/components/responses/LicenseServerUnavailable"
        "503":
          $ref: "#/components/responses/Unavailable"
  /license/eula:
    post:
      tags:
        - license
      operationId: acceptEULA
      summary: Accept the EULA (admin)
      description: |
        `version` must be the `eula.version` of `GET /license` (the text the
        admin was shown); another version is `409 eula_version`. The server
        serves no viewers and outputs before the EULA is accepted
        (`server.license.accept_eula: true` accepts it for automated
        installs).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - version
              properties:
                version:
                  type: string
      responses:
        "200":
          description: Accepted; the new status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LicenseStatus"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "422":
          $ref: "#/components/responses/ValidationFailed"
        "503":
          $ref: "#/components/responses/Unavailable"
  /license/deactivate:
    post:
      tags:
        - license
      operationId: deactivateLicense
      summary: Release the seat and remove the license from this server (admin)
      description: |
        Tells the license server (`POST /deactivate`, online licenses) and
        removes the license here. `released: false`: the license server
        could not be told — release the seat in the license portal. The
        server stops serving viewers and outputs.
      responses:
        "200":
          description: Removed.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - released
                  - license
                properties:
                  released:
                    type: boolean
                  message:
                    type: string
                  license:
                    $ref: "#/components/schemas/LicenseStatus"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "503":
          $ref: "#/components/responses/Unavailable"
  /notifications:
    get:
      tags:
        - notifications
      operationId: getNotifications
      summary: Notification targets (secrets redacted), delivery status and queue of this node
      description: |
        Targets come from the node-local `notifications:` section. Webhook
        keys are redacted (`https://mm.example/hooks/***`), SMTP passwords
        never shown. `state`: `ok` (last attempt succeeded), `failing`,
        `idle` (nothing sent yet). Failures of a target are reported here,
        in the log and in `alteoxms_notification_up` — not as alarms.
      responses:
        "200":
          description: Notifier status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Notifications"
        "401":
          $ref: "#/components/responses/Unauthorized"
  /notifications/test:
    post:
      tags:
        - notifications
      operationId: testNotifications
      summary: Send a test message to one target or all (admin)
      description: |
        Sends right away (no hold-down, filter, batching or retry) and waits
        for the outcome. `200` also when the delivery failed: see `ok` and
        `results[].error`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - target
              properties:
                target:
                  type: string
                  description: Target name or "all".
                  examples:
                    - ops-mattermost
                    - all
      responses:
        "200":
          description: Outcome per target.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required:
                  - ok
                  - results
                properties:
                  ok:
                    type: boolean
                    description: Every target accepted the message.
                  results:
                    type: array
                    items:
                      type: object
                      additionalProperties: false
                      required:
                        - target
                        - type
                        - ok
                        - duration_ms
                      properties:
                        target:
                          type: string
                        type:
                          type: string
                          enum:
                            - mattermost
                            - email
                        ok:
                          type: boolean
                        error:
                          type: string
                        duration_ms:
                          type: number
                          minimum: 0
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationFailed"
  /event-sinks:
    get:
      tags:
        - event-sinks
      operationId: getEventSinks
      summary: Event sinks of this node with delivery status
      description: |
        Sinks come from the node-local `event_sinks:` section. URLs are redacted. `state`:
        `idle` (nothing sent yet), `ok` (last POST succeeded), `retrying`
        (last POST failed; the batch is retried with backoff 1 s .. 1 min),
        `rejected` (the sink answered 4xx: batch dropped). Events and the
        mapping: docs/API.md §15.2.
      responses:
        "200":
          description: Sink status.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventSinks"
        "401":
          $ref: "#/components/responses/Unauthorized"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: API key (`alteoxms gen-key`), stored as SHA-256 in `api.keys`.
    basicAuth:
      type: http
      scheme: basic
      description: Admin/viewer user from `api.users` (bcrypt). Failed logins are throttled (429).
    accessToken:
      type: apiKey
      in: query
      name: access_token
      description: API key as query parameter; accepted only on `GET /events` (EventSource cannot set headers).
    sessionCookie:
      type: apiKey
      in: cookie
      name: alteoxms_session
      description: |
        Web UI session from `POST /auth/login` (HttpOnly, SameSite=Strict).
        Unsafe methods (`POST`, `PUT`, `PATCH`, `DELETE`) additionally need
        the header `X-CSRF-Token` (from the login response or `GET /me`)
        and a same-origin `Origin` / `Sec-Fetch-Site`; otherwise `403`.
        Ends after `api.session.idle_timeout` (8h) without requests, after
        `api.session.max_lifetime` (7 days), on logout or revocation, and
        when the user/key is removed or changes role or password.
  parameters:
    ChannelName:
      name: name
      in: path
      required: true
      description: A channel name; dynamic channels (server.config_lookup) may contain "/" (send it as %2F).
      schema:
        $ref: "#/components/schemas/ChannelNameAny"
    ProfileName:
      name: name
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/Name"
    TemplateName:
      name: name
      in: path
      required: true
      schema:
        $ref: "#/components/schemas/Name"
    NodeID:
      name: id
      in: path
      required: true
      schema:
        type: string
    DryRun:
      name: dry_run
      in: query
      description: Validate and answer as usual, but store and apply nothing.
      schema:
        type: boolean
        default: false
    IfMatch:
      name: If-Match
      in: header
      description: ETag from a GET; on mismatch `412 precondition_failed`. Without it the last writer wins.
      schema:
        type: string
    Block:
      name: block
      in: query
      description: Also block the client(s) for this long (Go duration, 1s..24h, e.g. `10m`).
      schema:
        type: string
        examples:
          - 10m
          - 1h
  headers:
    ETag:
      description: Opaque content hash of the stored resource (survives restarts; identical on every cluster node).
      schema:
        type: string
        pattern: ^"[0-9a-f]{16}"$
    Location:
      description: URL of the created resource.
      schema:
        type: string
    NagiosExitCode:
      description: "Nagios plugin exit code of `?format=nagios`: 0 OK, 1 WARNING, 2 CRITICAL, 3 UNKNOWN."
      schema:
        type: string
        enum:
          - "0"
          - "1"
          - "2"
          - "3"
  responses:
    BadRequest:
      description: "`bad_request`: malformed JSON/YAML, unknown field, wrong type, bad query parameter."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: '`unauthorized`: missing or invalid credentials (`WWW-Authenticate: Bearer realm="alteoxms"`).'
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Forbidden:
      description: "`forbidden`: the role does not allow the action, a cookie-authenticated write failed the CSRF check, or a `PUT /config` tries to change `api.allow_extra_args` (detail code `read_only`). `extra_args_disabled`: the write would set or change a transcode profile's `extra_args` while `api.allow_extra_args` is off (details name the field paths)."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: "`not_found`: channel/profile/session/node does not exist."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Conflict:
      description: "`already_exists`, `in_use` (details list the users) or `conflict` (impossible in the current state, or a cluster write raced; retry)."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    PreconditionFailed:
      description: "`precondition_failed`: `If-Match` does not match the current ETag."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    TooLarge:
      description: "`bad_request`: request body too large (JSON 1 MiB, YAML 4 MiB)."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    UnsupportedMediaType:
      description: "`unsupported_media_type`: PATCH body is not `application/merge-patch+json` / `application/json`."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    ValidationFailed:
      description: "`validation_failed`: semantically invalid; `details` per field."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error:
              code: validation_failed
              message: channel configuration is invalid
              details:
                - field: inputs[1].url
                  code: invalid
                  message: unsupported scheme "rtmp" (want one of srt, udp, rtp, http, https, publish)
                - field: failover.loss_timeout
                  code: out_of_range
                  message: 100ms out of range (200ms..60s)
    TooManyRequests:
      description: "`too_many_requests`: too many failed logins; `Retry-After` in seconds."
      headers:
        Retry-After:
          schema:
            type: string
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    LicenseLimit:
      description: "`license_limit`: the change would exceed the license's channel limit (enabled configured channels plus running dynamic ones; in a cluster limit × seats). A configuration already above the limit (downgrade) can still be edited while the count does not grow."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    LicenseRefused:
      description: The license server refused the key (`seats_exhausted`, `revoked`, `expired`), or `dev_build` (a build without license keys cannot verify keys).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    LicenseServerUnavailable:
      description: "`license_server_unavailable`: the key verified and is stored, but the license server could not be reached; activation is retried in the background."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unavailable:
      description: "`unavailable`: API disabled (no credentials configured), clustering disabled, no cluster leader / quorum, preview tokens or FFmpeg not available."
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    EventSinks:
      type: object
      additionalProperties: false
      required:
        - event_sinks
      properties:
        event_sinks:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - url
              - format
              - events
              - state
              - queued
              - queue_size
              - sent
              - dropped
              - batches
              - failures
              - last_error
              - last_success
              - last_failure
            properties:
              name:
                type: string
              url:
                type: string
                description: Redacted.
              format:
                type: string
                enum:
                  - ndjson
              events:
                type: array
                items:
                  type: string
                description: Event filter (empty = all).
              state:
                type: string
                enum:
                  - idle
                  - ok
                  - retrying
                  - rejected
              queued:
                type: integer
                minimum: 0
              queue_size:
                type: integer
                minimum: 0
              sent:
                type: integer
                minimum: 0
                description: Events delivered.
              dropped:
                type: integer
                minimum: 0
                description: "Events lost: queue full, batch rejected (4xx), shutdown while retrying."
              batches:
                type: integer
                minimum: 0
                description: Successful POSTs.
              failures:
                type: integer
                minimum: 0
                description: Failed POST attempts.
              last_error:
                type: string
              last_success:
                $ref: "#/components/schemas/Time"
              last_failure:
                $ref: "#/components/schemas/Time"
    Name:
      type: string
      pattern: ^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$
    ChannelNameAny:
      description: 'A configured channel name (Name) or a dynamic channel name: up to 8 such elements joined by "/" (128 characters at most).'
      type: string
      maxLength: 128
      pattern: ^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}(/[A-Za-z0-9][A-Za-z0-9_.-]{0,63}){0,7}$
    Duration:
      type: string
      description: Go duration string.
      examples:
        - 2s
        - 500ms
        - 1m30s
        - 0s
    ConfiguredBitrate:
      description: '"6000k", "6M" or an integer (bit/s); returned in the stored form.'
      type:
        - string
        - integer
    Time:
      description: RFC 3339 UTC with milliseconds; null = never.
      type:
        - string
        - "null"
      format: date-time
    Labels:
      type: object
      description: Free key/value tags (at most 32). Keys `^[A-Za-z0-9][A-Za-z0-9_./-]{0,62}$`, values `^[A-Za-z0-9_./-]{0,63}$`.
      maxProperties: 32
      additionalProperties:
        type: string
    Health:
      type: object
      additionalProperties: false
      required:
        - status
        - node
        - ts
        - reasons
        - channels
        - problems
        - transcoder_restarts_10m
        - input_failovers_10m
        - alarms_available
        - alarms
        - cluster
      properties:
        status:
          type: string
          enum:
            - ok
            - degraded
            - critical
        node:
          type: string
        ts:
          $ref: "#/components/schemas/Time"
        reasons:
          type: array
          items:
            type: string
          description: Why the status is not ok.
        channels:
          type: object
          additionalProperties: false
          required:
            - total
            - ok
            - degraded
            - failed
            - stopped
            - sleeping
          properties:
            total:
              type: integer
              minimum: 0
            ok:
              type: integer
              minimum: 0
            degraded:
              type: integer
              minimum: 0
            failed:
              type: integer
              minimum: 0
            stopped:
              type: integer
              minimum: 0
              description: Disabled or running on another cluster node.
            sleeping:
              type: integer
              minimum: 0
              description: On-demand channels without consumers (not a problem).
        problems:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - channel
              - state
              - health
              - reason
            properties:
              channel:
                type: string
              state:
                $ref: "#/components/schemas/ChannelState"
              health:
                type: string
                enum:
                  - degraded
                  - failed
              reason:
                type: string
        transcoder_restarts_10m:
          type: integer
          minimum: 0
        input_failovers_10m:
          type: integer
          minimum: 0
          description: Switches with reason input_lost.
        alarms_available:
          type: boolean
          description: An alarm source (e.g. GPU monitoring) is registered.
        alarms:
          type: array
          items:
            $ref: "#/components/schemas/ActiveAlarm"
        cluster:
          type: object
          additionalProperties: false
          required:
            - enabled
            - leader
          properties:
            enabled:
              type: boolean
            leader:
              type: string
              description: 'Raft leader ("" = none: critical); standalone: this node.'
    ActiveAlarm:
      type: object
      additionalProperties: false
      required:
        - id
        - severity
        - source
        - message
        - since
      properties:
        id:
          type: string
        severity:
          type: string
          enum:
            - critical
            - warning
            - info
        source:
          type: string
          examples:
            - gpu
        subject:
          type: string
          examples:
            - nvidia:0
        message:
          type: string
        since:
          $ref: "#/components/schemas/Time"
    Readiness:
      type: object
      additionalProperties: false
      required:
        - ready
        - checks
        - reasons
      properties:
        ready:
          type: boolean
        checks:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - ok
            properties:
              name:
                type: string
                enum:
                  - config
                  - http_listener
                  - srt_listener
                  - channels
                  - cluster
                  - shutdown
              ok:
                type: boolean
              detail:
                type: string
        reasons:
          type: array
          items:
            type: string
    Error:
      type: object
      additionalProperties: false
      required:
        - error
      properties:
        error:
          type: object
          additionalProperties: false
          required:
            - code
            - message
            - details
          properties:
            code:
              type: string
              enum:
                - bad_request
                - unauthorized
                - forbidden
                - not_found
                - method_not_allowed
                - already_exists
                - in_use
                - conflict
                - precondition_failed
                - unsupported_media_type
                - validation_failed
                - too_many_requests
                - internal
                - unavailable
                - extra_args_disabled
                - license_limit
                - invalid_license
                - dev_build
                - seats_exhausted
                - revoked
                - expired
                - license_server_unavailable
                - eula_version
            message:
              type: string
            details:
              type: array
              items:
                $ref: "#/components/schemas/Detail"
      example:
        error:
          code: validation_failed
          message: channel configuration is invalid
          details:
            - field: inputs[1].url
              code: invalid
              message: unsupported scheme "rtmp"
    Detail:
      type: object
      additionalProperties: false
      required:
        - field
        - code
        - message
      properties:
        field:
          type: string
          description: Path into the request ("outputs.udp[0].url"); "" = whole object.
        line:
          type: integer
          minimum: 1
          description: YAML line (config endpoints).
        code:
          type: string
          enum:
            - required
            - invalid
            - out_of_range
            - duplicate
            - unknown_field
            - not_found
            - unavailable
            - redacted_secret
            - in_use
            - extra_args_disabled
            - read_only
            - license_limit
            - invalid_license
        message:
          type: string
    Identity:
      type: object
      additionalProperties: false
      required:
        - user
        - method
        - role
        - allow_extra_args
      properties:
        user:
          type: string
        method:
          type: string
          enum:
            - basic
            - api_key
            - session
          description: "`session`: authenticated by the UI session cookie."
        role:
          type: string
          enum:
            - admin
            - viewer
        allow_extra_args:
          type: boolean
          description: Server capability `api.allow_extra_args` (config file only). false = API writes may not set or change transcode profiles' `extra_args` (read only in the UI).
        csrf_token:
          type: string
          description: "Cookie sessions only: send as `X-CSRF-Token` with unsafe methods."
        expires_at:
          $ref: "#/components/schemas/Time"
          description: "Cookie sessions only: absolute end of the session (idle timeout applies earlier)."
    LoginRequest:
      type: object
      additionalProperties: false
      description: Either `user` and `password`, or `api_key`.
      properties:
        user:
          type: string
        password:
          type: string
        api_key:
          type: string
    ServerInfo:
      type: object
      additionalProperties: false
      required:
        - version
        - commit
        - build_date
        - node
        - started_at
        - uptime_s
        - go_version
        - ffmpeg
        - cpu
        - memory
        - totals
      properties:
        version:
          type: string
        commit:
          type: string
        build_date:
          type: string
        node:
          type: string
        started_at:
          $ref: "#/components/schemas/Time"
        uptime_s:
          type: number
          minimum: 0
        go_version:
          type: string
        license:
          type: object
          additionalProperties: false
          description: License summary for every role (UI banner); details in GET /license (admin).
          required:
            - mode
            - status
            - serving
            - warnings
          properties:
            mode:
              $ref: "#/components/schemas/LicenseMode"
            status:
              $ref: "#/components/schemas/LicenseStatusValue"
            serving:
              type: boolean
            reason:
              type: string
            warnings:
              type: array
              items:
                $ref: "#/components/schemas/LicenseWarning"
        ffmpeg:
          type: object
          additionalProperties: false
          required:
            - available
            - version
          properties:
            available:
              type: boolean
            version:
              type: string
        cpu:
          type: object
          additionalProperties: false
          required:
            - cores
            - usage_pct
            - load1
          properties:
            cores:
              type: integer
            usage_pct:
              type: number
            load1:
              type: number
        memory:
          type: object
          additionalProperties: false
          required:
            - rss_bytes
            - system_total_bytes
            - system_used_pct
          properties:
            rss_bytes:
              type: integer
            system_total_bytes:
              type: integer
            system_used_pct:
              type: number
        totals:
          type: object
          additionalProperties: false
          required:
            - channels
            - channels_by_health
            - inputs
            - sessions
            - bitrate_in
            - bitrate_out
          properties:
            channels:
              type: integer
            channels_by_health:
              type: object
              additionalProperties: false
              required:
                - ok
                - degraded
                - failed
                - sleeping
              properties:
                ok:
                  type: integer
                degraded:
                  type: integer
                failed:
                  type: integer
                sleeping:
                  type: integer
            inputs:
              type: integer
            sessions:
              type: integer
            bitrate_in:
              type: integer
            bitrate_out:
              type: integer
    LicenseMode:
      type: string
      enum:
        - enforced
        - dev
      description: "dev = build without embedded license keys: nothing enforced."
    LicenseStatusValue:
      type: string
      enum:
        - unlicensed
        - trial
        - active
        - grace
        - expired
        - invalid
        - dev
    LicenseWarning:
      type: object
      additionalProperties: false
      required:
        - code
        - message
      properties:
        code:
          type: string
          enum:
            - lease_stale
            - license_expiring
            - eula_required
            - cluster_not_licensed
        message:
          type: string
        days:
          type: integer
          minimum: 1
          description: Days until expires_at (license_expiring).
    LicenseStatus:
      type: object
      additionalProperties: false
      required:
        - mode
        - status
        - serving
        - install_id
        - fingerprint
        - license
        - lease
        - limits
        - eula
        - warnings
        - messages
        - cms
      properties:
        mode:
          $ref: "#/components/schemas/LicenseMode"
        status:
          $ref: "#/components/schemas/LicenseStatusValue"
        serving:
          type: boolean
          description: Viewers and outputs are served (valid license and EULA accepted, or dev mode).
        reason:
          type: string
          description: Why the server does not serve.
        install_id:
          type: string
          examples:
            - ins_k3x2m4n5p6q7r8s9t2u3v4w5x6
        fingerprint:
          type: string
          examples:
            - fp_7zq3k4m5n6p7q8r9s2t3u4v5w6
        license:
          oneOf:
            - type: "null"
            - type: object
              additionalProperties: false
              required:
                - license_id
                - account_id
                - account_name
                - tier
                - max_channels
                - cluster
                - seats
                - trial
                - offline
                - issued_at
                - expires_at
                - updates_until
              properties:
                license_id:
                  type: string
                account_id:
                  type: string
                account_name:
                  type: string
                tier:
                  type: string
                  enum:
                    - starter
                    - medium
                    - pro
                max_channels:
                  type: integer
                  minimum: 0
                  description: 0 = unlimited.
                cluster:
                  type: boolean
                seats:
                  type: integer
                  minimum: 1
                trial:
                  type: boolean
                offline:
                  type: boolean
                issued_at:
                  type: string
                expires_at:
                  type: string
                updates_until:
                  type: string
        lease:
          oneOf:
            - type: "null"
            - type: object
              additionalProperties: false
              required:
                - server_time
                - expires_at
                - renew_after
                - age_s
              properties:
                server_time:
                  type: string
                expires_at:
                  type: string
                renew_after:
                  type: string
                age_s:
                  type: number
                  minimum: 0
        limits:
          type: object
          additionalProperties: false
          required:
            - max_channels
            - cluster
            - channels
          properties:
            max_channels:
              type: integer
              minimum: 0
              description: In force (0 = unlimited; also 0 without a valid license).
            cluster:
              type: boolean
            channels:
              type: integer
              minimum: 0
              description: Enabled configured channels plus running dynamic ones.
        eula:
          type: object
          additionalProperties: false
          required:
            - version
            - accepted
            - accepted_at
            - text
          properties:
            version:
              type: string
            accepted:
              type: boolean
            accepted_at:
              oneOf:
                - type: "null"
                - type: string
            accepted_by:
              type: string
            text:
              type: string
        warnings:
          type: array
          items:
            $ref: "#/components/schemas/LicenseWarning"
        messages:
          type: array
          description: Messages of the license server (last heartbeat).
          items:
            type: object
            additionalProperties: false
            required:
              - level
              - text
            properties:
              level:
                type: string
                enum:
                  - info
                  - warning
              text:
                type: string
        cms:
          type: object
          additionalProperties: false
          required:
            - url
            - last_heartbeat
            - activation_pending
          properties:
            url:
              type: string
            last_heartbeat:
              oneOf:
                - type: "null"
                - type: string
            heartbeat_error:
              type: string
            lease_error:
              type: string
            activation_pending:
              type: boolean
        build_date:
          type: string
    ChannelState:
      type: string
      enum:
        - starting
        - running
        - stopped
        - error
        - sleeping
        - license_limit
      description: "sleeping = on-demand channel without consumers (inputs stopped; not a failure). license_limit = enabled but not started: the license's channel limit is reached (docs/LICENSING.md §5)."
    AdmissionState:
      type: string
      enum:
        - admitted
        - no_gpu_capacity
        - queued
        - cpu_fallback
        - unenforced
    HealthValue:
      type: string
      enum:
        - ok
        - degraded
        - failed
        - sleeping
    FailoverMode:
      type: string
      enum:
        - auto
        - manual
    SwitchReason:
      type: string
      enum:
        - startup
        - input_lost
        - return_to_primary
        - manual
        - config_change
        - wake
        - quality
    OnDemandState:
      type: string
      enum:
        - sleeping
        - waking
        - awake
    DynamicStatus:
      type: object
      description: A dynamic channel from the lookup server (server.config_lookup, docs/API.md §4.15); omitted for configured channels.
      additionalProperties: false
      required:
        - source
        - static_list
        - created_at
        - refreshed_at
        - on_play
        - notes
      properties:
        source:
          type: string
          description: The lookup server URL, secrets masked (***).
        title:
          type: string
        provider:
          type: string
        comment:
          type: string
        static_list:
          type: boolean
          description: From the lookup server's static stream list (runs while listed) rather than a request for its name.
        created_at:
          $ref: "#/components/schemas/Time"
        refreshed_at:
          $ref: "#/components/schemas/Time"
        last_error:
          type: string
          description: The last refresh failed (the channel keeps its last good configuration); omitted after a successful one.
        on_play:
          type: boolean
          description: "The lookup server gave a play authorization URL (on_play): viewers are authorized by the auth hook (auth.read)."
        notes:
          type: array
          description: Remarks of the mapping (unsupported inputs, ignored transcoder options).
          items:
            type: object
            additionalProperties: false
            required:
              - severity
              - message
            properties:
              severity:
                type: string
                enum:
                  - info
                  - warning
                  - error
              message:
                type: string
    OnDemandStatus:
      type: object
      additionalProperties: false
      description: On-demand state of a channel (docs/API.md 4.3); present when on_demand or transcode_on_demand is configured.
      required:
        - on_demand
        - transcode_on_demand
        - idle
        - state
        - consumers
        - transcode_consumers
        - last_consumer_at
        - sleeping_since
        - wakeups
        - last_wake_at
        - last_wake_latency_ms
        - transcoder_wakeups
        - transcoder_last_wake_at
        - transcoder_last_wake_latency_ms
      properties:
        on_demand:
          type: boolean
        transcode_on_demand:
          type: boolean
        idle:
          $ref: "#/components/schemas/Duration"
        state:
          $ref: "#/components/schemas/OnDemandState"
          description: The channel (its inputs); always awake without on_demand.
        transcoder:
          type: string
          enum:
            - sleeping
            - waking
            - running
          description: Omitted for channels without transcoding.
        consumers:
          type: integer
          minimum: 0
          description: Connected consumers (SRT, HTTP-TS, copy://).
        transcode_consumers:
          type: integer
          minimum: 0
          description: Of these, consumers of a transcoded rendition.
        always_on:
          type: string
          description: Why it never sleeps, e.g. "1 UDP output" (UDP outputs are permanent consumers).
        last_consumer_at:
          $ref: "#/components/schemas/Time"
        sleeping_since:
          $ref: "#/components/schemas/Time"
        wakeups:
          type: integer
          minimum: 0
        last_wake_at:
          $ref: "#/components/schemas/Time"
        last_wake_latency_ms:
          type:
            - integer
            - "null"
          description: Wake-up until the first data.
        transcoder_wakeups:
          type: integer
          minimum: 0
        transcoder_last_wake_at:
          $ref: "#/components/schemas/Time"
        transcoder_last_wake_latency_ms:
          type:
            - integer
            - "null"
        error:
          type: string
          description: Why the current wake-up fails (e.g. "transcoder not admitted (...)", no data in time).
    ChannelConfig:
      type: object
      description: One entry of `channels:` in the YAML config (defaults filled in).
      additionalProperties: false
      required:
        - name
        - inputs
        - failover
        - transcode
        - outputs
        - auth
        - enabled
        - labels
      properties:
        name:
          $ref: "#/components/schemas/ChannelNameAny"
        dynamic:
          type: boolean
          const: true
          description: "Read-only: a dynamic channel from the lookup server (server.config_lookup), not stored in the configuration; omitted for configured channels."
        inputs:
          type: array
          minItems: 1
          maxItems: 16
          description: Array order is priority (index 0 = primary).
          items:
            type: object
            additionalProperties: false
            required:
              - url
              - headers
              - user_agent
              - srt_publish
            properties:
              url:
                type: string
                description: srt, udp, rtp, http(s) (HLS when .m3u8), hls(s), publish:// or copy://<channel>[/<rendition>]
              headers:
                type: object
                description: Extra HTTP request headers (http/https/hls/hlss inputs); values are secrets ("***" when redacted).
                additionalProperties:
                  type: string
              user_agent:
                type: string
                description: User-Agent override (http/https/hls/hlss); "" = default.
              srt_publish:
                oneOf:
                  - type: "null"
                  - $ref: "#/components/schemas/SRTPort"
                description: Dedicated SRT publish port of the publish:// input.
        failover:
          type: object
          additionalProperties: false
          required:
            - loss_timeout
            - return_after
            - standby
            - select
            - max_connected
          properties:
            loss_timeout:
              $ref: "#/components/schemas/Duration"
            return_after:
              $ref: "#/components/schemas/Duration"
            standby:
              type: string
              enum:
                - hot
                - cold
            select:
              $ref: "#/components/schemas/FailoverSelect"
            max_connected:
              $ref: "#/components/schemas/MaxConnected"
            node_standby:
              type: string
              enum:
                - cold
                - hot
        transcode:
          description: Name of a profile, the channel's own inline profile (object), or null = passthrough.
          oneOf:
            - type: string
            - type: "null"
            - $ref: "#/components/schemas/InlineProfile"
        outputs:
          type: object
          additionalProperties: false
          required:
            - srt
            - srt_play
            - http_ts
            - hls
            - dash
            - udp
          properties:
            srt:
              type: boolean
            srt_play:
              oneOf:
                - type: "null"
                - $ref: "#/components/schemas/SRTPort"
              description: Dedicated SRT play port (callers need no streamid).
            http_ts:
              type: boolean
            hls:
              oneOf:
                - type: "null"
                - type: object
                  additionalProperties: false
                  required:
                    - segment
                    - window
                    - ts_segments
                  properties:
                    segment:
                      $ref: "#/components/schemas/Duration"
                    window:
                      type: integer
                      minimum: 2
                      maximum: 60
                    ts_segments:
                      type: boolean
            dash:
              oneOf:
                - type: "null"
                - type: object
                  additionalProperties: false
                  required:
                    - segment
                    - window
                  properties:
                    segment:
                      $ref: "#/components/schemas/Duration"
                    window:
                      type: integer
                      minimum: 2
                      maximum: 60
            udp:
              type: array
              maxItems: 16
              items:
                type: object
                additionalProperties: false
                required:
                  - url
                properties:
                  url:
                    type: string
        auth:
          type: object
          additionalProperties: false
          required:
            - publish
            - read
          properties:
            publish:
              type: boolean
            read:
              type: boolean
        enabled:
          type: boolean
          description: false = configured but not running (state stopped).
        labels:
          $ref: "#/components/schemas/Labels"
        tracks:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/TracksConfig"
          description: Output track selection (ARCHITECTURE 4.4a); null = every source track.
        on_demand:
          type: boolean
          description: The whole channel (input pulls included) runs only while it has consumers.
        transcode_on_demand:
          type: boolean
          description: The transcoder runs only while a transcoded output has consumers (needs transcode).
        on_demand_idle:
          $ref: "#/components/schemas/Duration"
          description: Time without consumers before it sleeps (default 30s).
    ChannelResponse:
      description: A channel without template (ChannelConfig) or the stored form of a templated channel (TemplatedChannel).
      oneOf:
        - $ref: "#/components/schemas/ChannelConfig"
        - $ref: "#/components/schemas/TemplatedChannel"
    PartialChannel:
      type: object
      description: |
        The fields of a channel object (§4.1) that a template or a templated
        channel sets itself; every field is optional and has the shape of
        ChannelConfig (a label may be null: the channel removes the
        template's label).
      properties:
        failover:
          type: object
          additionalProperties: false
          properties:
            loss_timeout:
              $ref: "#/components/schemas/Duration"
            return_after:
              $ref: "#/components/schemas/Duration"
            standby:
              type: string
              enum:
                - hot
                - cold
            node_standby:
              type: string
              enum:
                - cold
                - hot
        transcode:
          oneOf:
            - type: string
            - type: "null"
            - $ref: "#/components/schemas/InlineProfile"
        outputs:
          type: object
          additionalProperties: false
          properties:
            srt:
              type: boolean
            srt_play:
              oneOf:
                - type: "null"
                - $ref: "#/components/schemas/SRTPort"
            http_ts:
              type: boolean
            hls:
              oneOf:
                - type: "null"
                - type: object
                  additionalProperties: false
                  properties:
                    segment:
                      $ref: "#/components/schemas/Duration"
                    window:
                      type: integer
                      minimum: 2
                      maximum: 60
                    ts_segments:
                      type: boolean
            dash:
              oneOf:
                - type: "null"
                - type: object
                  additionalProperties: false
                  properties:
                    segment:
                      $ref: "#/components/schemas/Duration"
                    window:
                      type: integer
                      minimum: 2
                      maximum: 60
            udp:
              type: array
              items:
                type: object
                additionalProperties: false
                required:
                  - url
                properties:
                  url:
                    type: string
        auth:
          type: object
          additionalProperties: false
          properties:
            publish:
              type: boolean
            read:
              type: boolean
        labels:
          type: object
          maxProperties: 32
          additionalProperties:
            type:
              - string
              - "null"
        tracks:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/TracksConfig"
        on_demand:
          type: boolean
        transcode_on_demand:
          type: boolean
        on_demand_idle:
          $ref: "#/components/schemas/Duration"
    TemplatedChannel:
      description: |
        Stored form of a channel with a template (docs/API.md 4.14): name,
        template, enabled and inputs, plus only the fields the channel sets
        itself; `overrides` lists their paths, `effective` is the complete
        channel (read only).
      allOf:
        - $ref: "#/components/schemas/PartialChannel"
        - type: object
          required:
            - name
            - template
            - enabled
            - inputs
            - overrides
            - effective
          properties:
            name:
              $ref: "#/components/schemas/Name"
            template:
              $ref: "#/components/schemas/Name"
            enabled:
              type: boolean
            inputs:
              type: array
              items:
                type: object
            overrides:
              type: array
              items:
                type: string
              description: Paths of the fields the channel sets itself, e.g. "outputs.hls.window", "labels.tier".
            effective:
              $ref: "#/components/schemas/ChannelConfig"
          propertyNames:
            enum:
              - name
              - template
              - enabled
              - inputs
              - overrides
              - effective
              - failover
              - transcode
              - outputs
              - auth
              - labels
              - tracks
              - on_demand
              - transcode_on_demand
              - on_demand_idle
    ChannelTemplate:
      description: |
        A channel template (docs/API.md 4.14): the channel fields it sets
        (a template never sets name, enabled, inputs or outputs.srt_play),
        the channels using it and the read-only `effective` settings (the
        template applied to a default channel).
      allOf:
        - $ref: "#/components/schemas/PartialChannel"
        - type: object
          required:
            - name
            - used_by
            - effective
          properties:
            name:
              $ref: "#/components/schemas/Name"
            used_by:
              type: array
              items:
                type: string
            effective:
              type: object
              additionalProperties: false
              required:
                - failover
                - transcode
                - outputs
                - auth
                - labels
                - tracks
                - on_demand
                - transcode_on_demand
                - on_demand_idle
              properties:
                failover:
                  type: object
                transcode:
                  oneOf:
                    - type: string
                    - type: "null"
                    - $ref: "#/components/schemas/InlineProfile"
                outputs:
                  type: object
                auth:
                  type: object
                labels:
                  $ref: "#/components/schemas/Labels"
                tracks:
                  oneOf:
                    - type: "null"
                    - $ref: "#/components/schemas/TracksConfig"
                on_demand:
                  type: boolean
                transcode_on_demand:
                  type: boolean
                on_demand_idle:
                  $ref: "#/components/schemas/Duration"
          propertyNames:
            enum:
              - name
              - used_by
              - effective
              - failover
              - transcode
              - outputs
              - auth
              - labels
              - tracks
              - on_demand
              - transcode_on_demand
              - on_demand_idle
    ChannelTemplateRequest:
      type: object
      description: |
        A template in requests: the fields of a channel request it sets
        (omitted = not set by the template); `used_by` and `effective` are
        read only and ignored. `inputs`, `enabled`, `template` and
        `outputs.srt_play` are rejected (422).
      additionalProperties: false
      properties:
        name:
          $ref: "#/components/schemas/Name"
        failover:
          type:
            - object
            - "null"
        transcode:
          oneOf:
            - type: string
            - type: "null"
            - $ref: "#/components/schemas/InlineProfileRequest"
        outputs:
          type:
            - object
            - "null"
        auth:
          type:
            - object
            - "null"
        labels:
          type:
            - object
            - "null"
          additionalProperties:
            type: string
        tracks:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/TracksRequest"
        on_demand:
          type: boolean
        transcode_on_demand:
          type: boolean
        on_demand_idle:
          type: string
        used_by:
          type: array
          items:
            type: string
        effective:
          type: object
    ChannelTemplatePatch:
      type: object
      description: JSON Merge Patch (RFC 7386) of a ChannelTemplate; `null` removes a setting from the template.
      additionalProperties: false
      properties:
        name:
          $ref: "#/components/schemas/Name"
        failover:
          type:
            - object
            - "null"
        transcode:
          oneOf:
            - type: string
            - type: "null"
            - $ref: "#/components/schemas/InlineProfileRequest"
        outputs:
          type:
            - object
            - "null"
        auth:
          type:
            - object
            - "null"
        labels:
          type:
            - object
            - "null"
          additionalProperties:
            type:
              - string
              - "null"
        tracks:
          type:
            - object
            - "null"
        on_demand:
          type:
            - boolean
            - "null"
        transcode_on_demand:
          type:
            - boolean
            - "null"
        on_demand_idle:
          type:
            - string
            - "null"
    ChannelConfigRequest:
      type: object
      description: |
        Channel configuration in requests. Omitted optional fields take
        their defaults (PUT: `enabled`, `labels`, `failover.node_standby`,
        `outputs.hls.ts_segments` keep the stored values). `outputs.hls` /
        `outputs.dash` also accept `true` (defaults) and `false` (off).
      additionalProperties: false
      properties:
        name:
          $ref: "#/components/schemas/Name"
        inputs:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - url
            properties:
              url:
                type: string
              headers:
                type:
                  - object
                  - "null"
                additionalProperties:
                  type: string
              user_agent:
                type:
                  - string
                  - "null"
              srt_publish:
                oneOf:
                  - type: "null"
                  - $ref: "#/components/schemas/SRTPortRequest"
        failover:
          type:
            - object
            - "null"
          additionalProperties: false
          properties:
            loss_timeout:
              $ref: "#/components/schemas/Duration"
            return_after:
              $ref: "#/components/schemas/Duration"
            standby:
              type: string
              enum:
                - hot
                - cold
            select:
              $ref: "#/components/schemas/FailoverSelect"
            max_connected:
              $ref: "#/components/schemas/MaxConnected"
            node_standby:
              type: string
              enum:
                - cold
                - hot
        transcode:
          description: Profile name, inline profile object, or null = passthrough.
          oneOf:
            - type: string
            - type: "null"
            - $ref: "#/components/schemas/InlineProfileRequest"
        outputs:
          type:
            - object
            - "null"
          additionalProperties: false
          properties:
            srt:
              type: boolean
            srt_play:
              oneOf:
                - type: "null"
                - $ref: "#/components/schemas/SRTPortRequest"
            http_ts:
              type: boolean
            hls:
              $ref: "#/components/schemas/SegmentedRequest"
            dash:
              $ref: "#/components/schemas/SegmentedRequest"
            udp:
              type: array
              items:
                type: object
                additionalProperties: false
                required:
                  - url
                properties:
                  url:
                    type: string
        auth:
          type:
            - object
            - "null"
          additionalProperties: false
          properties:
            publish:
              type: boolean
            read:
              type: boolean
        enabled:
          type: boolean
        labels:
          type:
            - object
            - "null"
          additionalProperties:
            type:
              - string
              - "null"
          description: A null value removes a label the channel's template sets.
        tracks:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/TracksRequest"
          description: Omitted in a PUT = keep; null = none.
        on_demand:
          type: boolean
          description: Omitted in a PUT = keep.
        transcode_on_demand:
          type: boolean
          description: Omitted in a PUT = keep; needs transcode.
        on_demand_idle:
          type: string
          description: Duration 1s..24h ("0" = default 30s); omitted in a PUT = keep.
        template:
          type:
            - string
            - "null"
          description: "Channel template (§4.14): the body is then the channel's stored form (omitted = inherited). null detaches a templated channel."
        overrides:
          type: array
          items:
            type: string
          description: Read only (ignored).
        effective:
          type: object
          description: Read only (ignored).
    TracksConfig:
      type: object
      description: |
        Output track selection per kind (docs/API.md 4.1 "tracks"): null =
        all (default), "all", "none", "first" or an ordered array of
        selectors (array order = output order; each selector chain is one
        slot with a fixed output PID).
      additionalProperties: false
      required:
        - video
        - audio
        - subtitles
        - teletext
        - scte35
        - data
        - keep_empty_slots
      properties:
        video:
          $ref: "#/components/schemas/TrackSet"
        audio:
          $ref: "#/components/schemas/TrackSet"
        subtitles:
          $ref: "#/components/schemas/TrackSet"
        teletext:
          $ref: "#/components/schemas/TrackSet"
        scte35:
          $ref: "#/components/schemas/TrackSet"
        data:
          $ref: "#/components/schemas/TrackSet"
        keep_empty_slots:
          type: boolean
    TrackSet:
      oneOf:
        - type: "null"
        - type: string
          enum:
            - all
            - none
            - first
        - type: array
          maxItems: 32
          items:
            $ref: "#/components/schemas/TrackSelector"
    TrackSelector:
      type: object
      additionalProperties: false
      required:
        - match
        - fallback
        - required
        - all_matches
        - also
        - out
        - text
      properties:
        match:
          $ref: "#/components/schemas/TrackMatch"
        fallback:
          type: string
          enum:
            - skip
            - next
          description: "next: if nothing matches, the next selector is tried for the same slot."
        required:
          type: boolean
          description: "Unmatched: status warning and track_missing alarm."
        all_matches:
          type: boolean
          description: Take every match (up to 8 PIDs) instead of the first.
        also:
          type: boolean
          description: The slot may take a source track another slot takes too (e.g. the same audio once copied, once transcoded); it does not claim the track either. Applies to the whole slot (any selector of a fallback chain). Shorthand term also.
        out:
          type: object
          additionalProperties: false
          required:
            - lang
            - label
          properties:
            lang:
              type: string
              description: Language override (3-letter ISO 639-2); "" = keep.
            label:
              type: string
              description: HLS/DASH audio rendition name; "" = from the language.
        text:
          type: string
          description: Shorthand string form (read-only), e.g. lang:deu,required.
    TrackMatch:
      type: object
      description: All given fields must match; {} matches any stream of the kind. pid = fixed binding, anything else = lookup rule.
      additionalProperties: false
      properties:
        pid:
          type: integer
          minimum: 16
          maximum: 8190
        lang:
          type: string
          description: ISO 639 (deu = ger = de).
        codec:
          type: string
          examples:
            - h264
            - hevc
            - mpeg2
            - aac
            - aac_latm
            - ac3
            - eac3
            - mp2
            - dvbsub
            - teletext
            - scte35
            - metadata
            - private
        index:
          type: integer
          minimum: 1
          maximum: 64
          description: n-th stream of the kind in source PMT order.
        page:
          type: integer
          minimum: 100
          maximum: 899
          description: Teletext page.
        hearing_impaired:
          type: boolean
        stream_type:
          type: integer
          minimum: 1
          maximum: 255
        name_regex:
          type: string
          maxLength: 256
    TracksRequest:
      type: object
      additionalProperties: false
      properties:
        video:
          $ref: "#/components/schemas/TrackSetRequest"
        audio:
          $ref: "#/components/schemas/TrackSetRequest"
        subtitles:
          $ref: "#/components/schemas/TrackSetRequest"
        teletext:
          $ref: "#/components/schemas/TrackSetRequest"
        scte35:
          $ref: "#/components/schemas/TrackSetRequest"
        data:
          $ref: "#/components/schemas/TrackSetRequest"
        keep_empty_slots:
          type: boolean
    TrackSetRequest:
      oneOf:
        - type: "null"
        - type: string
          enum:
            - all
            - none
            - first
        - type: array
          items:
            oneOf:
              - type: string
                description: 'Shorthand: "lang:deu", "pid:0x101", "page:777", "lang:eng|index:1", "codec:ac3,required", "codec:ac3,also".'
              - type: object
                additionalProperties: false
                properties:
                  match:
                    oneOf:
                      - type: string
                      - $ref: "#/components/schemas/TrackMatch"
                  fallback:
                    type: string
                    enum:
                      - skip
                      - next
                  required:
                    type: boolean
                  all_matches:
                    type: boolean
                  also:
                    type: boolean
                  out:
                    type: object
                    additionalProperties: false
                    properties:
                      lang:
                        type: string
                      label:
                        type: string
                  text:
                    type: string
                    description: Ignored.
    TrackMap:
      type: object
      description: Resolved track mapping (status.track_map, tracks preview).
      additionalProperties: false
      required:
        - configured
        - keep_empty_slots
        - input
        - sources
        - slots
        - warnings
      properties:
        configured:
          type: boolean
          description: "false: no tracks section, every source track passes (selector all)."
        keep_empty_slots:
          type: boolean
        input:
          type:
            - integer
            - "null"
          description: Input resolved against (null = no source program known).
        sources:
          type: array
          items:
            $ref: "#/components/schemas/TrackSource"
        slots:
          type: array
          items:
            $ref: "#/components/schemas/TrackSlot"
        warnings:
          type: array
          items:
            type: string
          description: Unmatched required slots.
    TrackSource:
      type: object
      additionalProperties: false
      required:
        - pid
        - kind
        - index
        - stream_type
        - codec
        - description
      properties:
        pid:
          type: integer
          description: Source (input) PID.
        kind:
          $ref: "#/components/schemas/TrackKind"
        index:
          type: integer
          minimum: 1
        stream_type:
          type: integer
        codec:
          type: string
        lang:
          type: string
        langs:
          type: array
          items:
            type: string
        pages:
          type: array
          items:
            type: integer
        hearing_impaired:
          type: boolean
        description:
          type: string
          description: "What name_regex matches: kind, codec, languages, pages, registration, service."
    TrackSlot:
      type: object
      additionalProperties: false
      required:
        - kind
        - slot
        - selector
        - fixed
        - required
        - also
        - state
        - matched_source_pid
        - source
        - output_pid
      properties:
        kind:
          $ref: "#/components/schemas/TrackKind"
        slot:
          type: integer
          minimum: 0
        selector:
          type: string
          description: Shorthand, "all" or "first".
        fixed:
          type: boolean
          description: Fixed PID binding (else lookup rule).
        required:
          type: boolean
        also:
          type: boolean
          description: "Selector also: the source may be shared with other slots (then several slots show the same matched_source_pid)."
        state:
          type: string
          enum:
            - matched
            - missing
            - empty
        matched_source_pid:
          type:
            - integer
            - "null"
        source:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/TrackSource"
        output_pid:
          type: integer
        lang:
          type: string
        label:
          type: string
    TrackKind:
      type: string
      enum:
        - video
        - audio
        - subtitles
        - teletext
        - scte35
        - data
    SRTPort:
      type: object
      description: |
        Dedicated per-channel SRT listener (`outputs.srt_play`,
        `inputs[].srt_publish`). Callers connect without streamid; the port
        identifies the channel. Unique across the configuration.
      additionalProperties: false
      required:
        - port
        - passphrase
        - pbkeylen
        - latency
        - max_clients
        - auth
      properties:
        port:
          type: integer
          minimum: 1
          maximum: 65535
        passphrase:
          type: string
          description: '"" (unencrypted) or 10..79 characters; "***" when redacted.'
        pbkeylen:
          type: integer
          enum:
            - 0
            - 16
            - 24
            - 32
          description: 0 = 16.
        latency:
          $ref: "#/components/schemas/Duration"
        max_clients:
          type: integer
          minimum: 0
          description: srt_play only; 0 = unlimited.
        auth:
          type:
            - boolean
            - "null"
          description: null = like the channel's auth.read / auth.publish.
        rendition:
          type: string
          description: "srt_play only (omitted when \"\"): what viewers get — \"\" = the read bus (first rendition when transcoding), \"source\" = the untranscoded source, or a rendition of the channel's transcode profile. A caller's streamid may name another rendition (\"720p\", \"read:720p\", \"<channel>/720p\")."
    SRTPortRequest:
      type: object
      additionalProperties: false
      required:
        - port
      properties:
        port:
          type: integer
        passphrase:
          type: string
        pbkeylen:
          type: integer
        latency:
          $ref: "#/components/schemas/Duration"
        max_clients:
          type: integer
        auth:
          type:
            - boolean
            - "null"
        rendition:
          type: string
          description: 'srt_play only: "", "source" or a rendition name.'
    SRTPortStatus:
      type: object
      description: State of a dedicated SRT port on this node.
      additionalProperties: false
      required:
        - port
        - url
        - encrypted
        - bound
        - clients
        - error
        - viewers
        - bitrate_out
      properties:
        port:
          type: integer
          minimum: 1
          maximum: 65535
        url:
          type: string
          description: srt://host:port (no streamid, no passphrase).
        encrypted:
          type: boolean
        bound:
          type: boolean
          description: Listening on this node (false on cluster nodes not serving the channel, or while the bind fails).
        clients:
          type: integer
          minimum: 0
        error:
          type: string
          description: Last bind error.
        viewers:
          type: integer
          minimum: 0
        bitrate_out:
          type: integer
          minimum: 0
        rendition:
          type: string
          description: "srt_play: the configured rendition (omitted for the default read bus)."
    SegmentedRequest:
      oneOf:
        - type:
            - boolean
            - "null"
        - type: object
          additionalProperties: false
          properties:
            segment:
              $ref: "#/components/schemas/Duration"
            window:
              type: integer
            ts_segments:
              type: boolean
              description: HLS only.
    ChannelPatch:
      type: object
      description: JSON Merge Patch (RFC 7386) of `ChannelConfig`; `null` removes a field (default), arrays replace.
      additionalProperties: false
      properties:
        name:
          $ref: "#/components/schemas/Name"
        inputs:
          type:
            - array
            - "null"
          items:
            type: object
        failover:
          type:
            - object
            - "null"
        transcode:
          description: Profile name, inline profile object, or null = passthrough.
          oneOf:
            - type: string
            - type: "null"
            - $ref: "#/components/schemas/InlineProfileRequest"
        outputs:
          type:
            - object
            - "null"
        auth:
          type:
            - object
            - "null"
        enabled:
          type:
            - boolean
            - "null"
        labels:
          type:
            - object
            - "null"
          additionalProperties:
            type:
              - string
              - "null"
        tracks:
          type:
            - object
            - "null"
        on_demand:
          type:
            - boolean
            - "null"
        transcode_on_demand:
          type:
            - boolean
            - "null"
        on_demand_idle:
          type:
            - string
            - "null"
        template:
          type:
            - string
            - "null"
          description: Attach (only the fields that differ from the template stay the channel's own) or, with null, detach.
    EnabledResult:
      type: object
      additionalProperties: false
      required:
        - name
        - enabled
        - changed
      properties:
        name:
          type: string
        enabled:
          type: boolean
        changed:
          type: boolean
          description: false when the channel already was in that state.
        dry_run:
          type: boolean
    BulkActionRequest:
      type: object
      additionalProperties: false
      required:
        - action
      description: Exactly one of `channels` or `selector`.
      properties:
        action:
          type: string
          enum:
            - restart
            - start
            - stop
        channels:
          type: array
          maxItems: 1000
          items:
            type: string
        selector:
          type: object
          additionalProperties: false
          minProperties: 1
          properties:
            name_prefix:
              type: string
            label:
              type: string
              description: "`key=value` or `key` (present)"
              examples:
                - team=sport
    BulkActionResponse:
      type: object
      additionalProperties: false
      required:
        - action
        - dry_run
        - matched
        - succeeded
        - failed
        - results
      properties:
        action:
          type: string
          enum:
            - restart
            - start
            - stop
        dry_run:
          type: boolean
        matched:
          type: integer
          minimum: 0
          description: Existing channels targeted.
        succeeded:
          type: integer
          minimum: 0
        failed:
          type: integer
          minimum: 0
        results:
          type: array
          description: Matched channels (sorted for selectors, request order for `channels`), then unknown names.
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - status
            properties:
              name:
                type: string
              status:
                type: string
                enum:
                  - ok
                  - unchanged
                  - error
              code:
                type: string
                description: Error code (e.g. not_found, conflict, internal) for status error.
              message:
                type: string
    ChannelSummary:
      type: object
      additionalProperties: false
      required:
        - name
        - transcode
        - outputs
        - input_count
        - status
        - enabled
        - labels
      properties:
        name:
          type: string
        transcode:
          type:
            - string
            - "null"
          description: Profile name, "(inline)" for a channel's own profile, null = passthrough.
        outputs:
          type: array
          items:
            type: string
            enum:
              - srt
              - srt_play
              - http_ts
              - hls
              - dash
              - udp
        input_count:
          type: integer
          minimum: 0
        enabled:
          type: boolean
        labels:
          $ref: "#/components/schemas/Labels"
        template:
          type: string
          description: Channel template (omitted when none).
        dynamic:
          type: boolean
          const: true
          description: A dynamic channel from the lookup server (omitted for configured channels).
        status:
          type: object
          additionalProperties: false
          required:
            - state
            - health
            - uptime_s
            - active_input
            - active_input_url
            - failover_mode
            - bitrate_in
            - bitrate_out
            - viewers
            - switches
            - last_switch
            - transcoder_state
            - video
            - bitrate_history
            - tracks
            - service_name
            - output_viewers
          properties:
            state:
              $ref: "#/components/schemas/ChannelState"
            health:
              $ref: "#/components/schemas/HealthValue"
            uptime_s:
              type: number
              minimum: 0
            active_input:
              type:
                - integer
                - "null"
            active_input_url:
              type: string
            failover_mode:
              $ref: "#/components/schemas/FailoverMode"
            bitrate_in:
              type: integer
              minimum: 0
            bitrate_out:
              type: integer
              minimum: 0
            viewers:
              type: integer
              minimum: 0
            switches:
              type: integer
              minimum: 0
            last_switch:
              oneOf:
                - type: "null"
                - $ref: "#/components/schemas/SwitchEvent"
            transcoder_state:
              type:
                - string
                - "null"
              enum:
                - starting
                - running
                - backoff
                - stopped
                - sleeping
                - null
            transcoder_gpu:
              type: string
              description: Where the transcoder was admitted ("nvidia:1", "cpu" for a fallback); omitted without admission control.
            transcoder_admission:
              $ref: "#/components/schemas/AdmissionState"
            video:
              type: string
            bitrate_history:
              type: array
              maxItems: 60
              items:
                type: integer
                minimum: 0
            tracks:
              type: object
              additionalProperties: false
              description: Source tracks of the active input by kind (all 0 without a source).
              required:
                - video
                - audio
                - subtitle
                - teletext
                - scte35
                - data
              properties:
                video:
                  type: integer
                  minimum: 0
                audio:
                  type: integer
                  minimum: 0
                subtitle:
                  type: integer
                  minimum: 0
                teletext:
                  type: integer
                  minimum: 0
                scte35:
                  type: integer
                  minimum: 0
                data:
                  type: integer
                  minimum: 0
            service_name:
              type: string
              description: SDT service name of the source ("" if none).
            output_viewers:
              type: object
              description: Viewers per output kind, one entry per item of outputs (udp is always 0).
              additionalProperties:
                type: integer
                minimum: 0
            on_demand:
              type: object
              additionalProperties: false
              description: On-demand channels only.
              required:
                - state
              properties:
                state:
                  $ref: "#/components/schemas/OnDemandState"
                transcoder:
                  type: string
                  enum:
                    - sleeping
                    - waking
                    - running
    SwitchEvent:
      type: object
      additionalProperties: false
      required:
        - at
        - from
        - to
        - reason
      properties:
        at:
          $ref: "#/components/schemas/Time"
        from:
          type:
            - integer
            - "null"
        to:
          type:
            - integer
            - "null"
        reason:
          $ref: "#/components/schemas/SwitchReason"
        detail:
          type: string
    ChannelStatus:
      type: object
      additionalProperties: false
      required:
        - name
        - state
        - health
        - started_at
        - uptime_s
        - active_input
        - pending_input
        - failover_mode
        - bitrate_in
        - inputs
        - failover
        - source
        - track_map
        - transcoder
        - outputs
        - viewers
        - bitrate_out
      properties:
        name:
          type: string
        state:
          $ref: "#/components/schemas/ChannelState"
        health:
          $ref: "#/components/schemas/HealthValue"
        started_at:
          $ref: "#/components/schemas/Time"
        uptime_s:
          type: number
          minimum: 0
        active_input:
          type:
            - integer
            - "null"
        pending_input:
          type:
            - integer
            - "null"
        failover_mode:
          $ref: "#/components/schemas/FailoverMode"
        bitrate_in:
          type: integer
          minimum: 0
        inputs:
          type: array
          items:
            $ref: "#/components/schemas/InputStatus"
        failover:
          type: object
          additionalProperties: false
          required:
            - loss_timeout
            - return_after
            - standby
            - select
            - max_connected
            - connected
            - preferred
            - switches
            - last_switch
            - history
          properties:
            loss_timeout:
              $ref: "#/components/schemas/Duration"
            return_after:
              $ref: "#/components/schemas/Duration"
            standby:
              type: string
              enum:
                - hot
                - cold
            select:
              $ref: "#/components/schemas/FailoverSelect"
            max_connected:
              $ref: "#/components/schemas/MaxConnected"
            connected:
              type: integer
              minimum: 0
              description: Pull inputs connected now.
            preferred:
              type:
                - integer
                - "null"
              description: Rank 1 of the selection order (best priority; with select quality the best-ranked input). null = none yet.
            switches:
              type: integer
              minimum: 0
            last_switch:
              oneOf:
                - type: "null"
                - $ref: "#/components/schemas/SwitchEvent"
            history:
              type: array
              maxItems: 50
              items:
                $ref: "#/components/schemas/SwitchEvent"
        source:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/SourceStatus"
        track_map:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/TrackMap"
        transcoder:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/TranscoderStatus"
        on_demand:
          $ref: "#/components/schemas/OnDemandStatus"
        dynamic:
          $ref: "#/components/schemas/DynamicStatus"
        outputs:
          type: object
          additionalProperties: false
          required:
            - srt
            - http_ts
            - hls
            - dash
            - udp
          properties:
            srt:
              $ref: "#/components/schemas/StreamOutput"
            srt_play:
              $ref: "#/components/schemas/SRTPortStatus"
            http_ts:
              $ref: "#/components/schemas/StreamOutput"
            hls:
              $ref: "#/components/schemas/StreamOutput"
            dash:
              $ref: "#/components/schemas/StreamOutput"
            udp:
              type: array
              items:
                type: object
                additionalProperties: false
                required:
                  - url
                  - bitrate_out
                  - packets
                  - errors
                properties:
                  url:
                    type: string
                  bitrate_out:
                    type: integer
                    minimum: 0
                  packets:
                    type: integer
                    minimum: 0
                  errors:
                    type: integer
                    minimum: 0
        viewers:
          type: integer
          minimum: 0
        bitrate_out:
          type: integer
          minimum: 0
    AnalyticsCounters:
      type: object
      additionalProperties: false
      description: "Error events of a bucket or range (deltas of the status counters). degraded_s / failed_s: seconds in that health. tr_p1..tr_p3: ETSI TR 101 290 errors of all inputs (not part of error_total)."
      required:
        - cc_errors
        - input_errors
        - reconnects
        - transcoder_restarts
        - transcoder_stalls
        - failovers
        - degraded_s
        - failed_s
        - tr_p1
        - tr_p2
        - tr_p3
      properties:
        cc_errors:
          type: integer
          minimum: 0
        input_errors:
          type: integer
          minimum: 0
        reconnects:
          type: integer
          minimum: 0
        transcoder_restarts:
          type: integer
          minimum: 0
        transcoder_stalls:
          type: integer
          minimum: 0
        failovers:
          type: integer
          minimum: 0
        degraded_s:
          type: integer
          minimum: 0
        failed_s:
          type: integer
          minimum: 0
        tr_p1:
          type: integer
          minimum: 0
        tr_p2:
          type: integer
          minimum: 0
        tr_p3:
          type: integer
          minimum: 0
    AnalyticsPoint:
      type: object
      additionalProperties: false
      description: "One time bucket: bitrates are averages, viewers the peak, the rest as in AnalyticsCounters."
      required:
        - t
        - bitrate_in
        - bitrate_out
        - viewers
        - cc_errors
        - input_errors
        - reconnects
        - transcoder_restarts
        - transcoder_stalls
        - failovers
        - degraded_s
        - failed_s
        - tr_p1
        - tr_p2
        - tr_p3
      properties:
        t:
          type: string
          format: date-time
          description: Bucket start.
        bitrate_in:
          type: integer
          minimum: 0
        bitrate_out:
          type: integer
          minimum: 0
        viewers:
          type: integer
          minimum: 0
        cc_errors:
          type: integer
          minimum: 0
        input_errors:
          type: integer
          minimum: 0
        reconnects:
          type: integer
          minimum: 0
        transcoder_restarts:
          type: integer
          minimum: 0
        transcoder_stalls:
          type: integer
          minimum: 0
        failovers:
          type: integer
          minimum: 0
        degraded_s:
          type: integer
          minimum: 0
        failed_s:
          type: integer
          minimum: 0
        tr_p1:
          type: integer
          minimum: 0
        tr_p2:
          type: integer
          minimum: 0
        tr_p3:
          type: integer
          minimum: 0
    Analytics:
      type: object
      additionalProperties: false
      required:
        - range
        - step_s
        - from
        - to
        - totals
        - sums
        - channels
        - sources
        - system
        - gpus
      properties:
        range:
          type: string
          enum:
            - 1h
            - 24h
            - 7d
        step_s:
          type: integer
        from:
          type: string
          format: date-time
        to:
          type: string
          format: date-time
        totals:
          type: array
          items:
            $ref: "#/components/schemas/AnalyticsPoint"
          description: All channels per bucket.
        sums:
          $ref: "#/components/schemas/AnalyticsCounters"
        channels:
          type: array
          description: Channels with data in the range, by error_total (descending), then name.
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - bitrate_in_avg
              - bitrate_out_avg
              - bitrate_out_max
              - viewers_peak
              - errors
              - error_total
              - spark
            properties:
              name:
                type: string
              bitrate_in_avg:
                type: integer
                minimum: 0
              bitrate_out_avg:
                type: integer
                minimum: 0
              bitrate_out_max:
                type: integer
                minimum: 0
              viewers_peak:
                type: integer
                minimum: 0
              errors:
                $ref: "#/components/schemas/AnalyticsCounters"
              error_total:
                type: integer
                minimum: 0
              spark:
                type: array
                items:
                  type: integer
                  minimum: 0
                description: Bitrate out, at most 60 averages.
        series:
          type: array
          items:
            $ref: "#/components/schemas/AnalyticsPoint"
          description: The channel's buckets (with channel=).
        sources:
          type: array
          description: "Sources (input URLs, redacted) over all channel inputs using them, worst first: error_total, then TR 101 290 P1+P2, then URL. Errors often belong to a source shared by several channels."
          items:
            type: object
            additionalProperties: false
            required:
              - url
              - used_by
              - bitrate_in_avg
              - errors
              - error_total
            properties:
              url:
                type: string
                description: Redacted input URL; publish inputs are publish://<channel>.
              used_by:
                type: array
                items:
                  type: string
                description: "Channel inputs that used the source in the range: <channel>#<n>, n from 1."
              bitrate_in_avg:
                type: integer
                minimum: 0
                description: Highest per-input average.
              errors:
                $ref: "#/components/schemas/AnalyticsCounters"
              error_total:
                type: integer
                minimum: 0
        system:
          type: array
          description: "The node per bucket: averages, GPU temperature the peak; null: not measured (no procfs, no GPU monitor)."
          items:
            type: object
            additionalProperties: false
            required:
              - t
              - cpu_pct
              - process_cpu_pct
              - memory_pct
              - load1
              - gpus
            properties:
              t:
                type: string
                format: date-time
              cpu_pct:
                type:
                  - number
                  - "null"
                description: Whole machine (FFmpeg included), %.
              process_cpu_pct:
                type:
                  - number
                  - "null"
                description: This server process, % of all cores.
              memory_pct:
                type:
                  - number
                  - "null"
                description: System memory used, %.
              load1:
                type:
                  - number
                  - "null"
              gpus:
                type: array
                items:
                  type: object
                  additionalProperties: false
                  required:
                    - index
                    - util
                    - encoder
                    - decoder
                    - memory_pct
                    - temperature_c
                    - power_w
                  properties:
                    index:
                      type: integer
                      minimum: 0
                    util:
                      type:
                        - number
                        - "null"
                    encoder:
                      type:
                        - number
                        - "null"
                      description: NVENC utilisation, %.
                    decoder:
                      type:
                        - number
                        - "null"
                      description: NVDEC utilisation, %.
                    memory_pct:
                      type:
                        - number
                        - "null"
                    temperature_c:
                      type:
                        - number
                        - "null"
                    power_w:
                      type:
                        - number
                        - "null"
        gpus:
          type: array
          description: GPUs seen since the server started.
          items:
            type: object
            additionalProperties: false
            required:
              - index
              - name
            properties:
              index:
                type: integer
                minimum: 0
              name:
                type: string
    TR101290Status:
      type: object
      additionalProperties: false
      description: "ETSI TR 101 290 errors of an input since it started (docs/API.md 14.1). Intervals are measured in stream time (PCR); pcr_accuracy_error is not measurable over IP (measured: false)."
      required:
        - p1
        - p2
        - p3
        - indicators
      properties:
        p1:
          type: integer
          minimum: 0
          description: Priority 1 errors (sync, PAT, CC, PMT, PID).
        p2:
          type: integer
          minimum: 0
          description: Priority 2 errors (transport, CRC, PCR, PTS, CAT).
        p3:
          type: integer
          minimum: 0
          description: Priority 3 errors (NIT/SDT repetition, unreferenced PID).
        indicators:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - priority
              - count
              - measured
            properties:
              name:
                type: string
                enum:
                  - ts_sync_loss
                  - sync_byte_error
                  - pat_error
                  - continuity_count_error
                  - pmt_error
                  - pid_error
                  - transport_error
                  - crc_error
                  - pcr_repetition_error
                  - pcr_discontinuity_indicator_error
                  - pcr_accuracy_error
                  - pts_error
                  - cat_error
                  - nit_actual_error
                  - sdt_actual_error
                  - unreferenced_pid
              priority:
                type: integer
                enum:
                  - 1
                  - 2
                  - 3
              count:
                type: integer
                minimum: 0
              last:
                $ref: "#/components/schemas/Time"
              measured:
                type: boolean
    InputStatus:
      type: object
      additionalProperties: false
      required:
        - index
        - url
        - state
        - connected
        - active
        - health
        - bitrate
        - bytes
        - packets
        - errors
        - cc_errors
        - reconnects
        - last_data
        - last_error
        - remote
        - healthy_for_s
      properties:
        index:
          type: integer
          minimum: 0
        url:
          type: string
          description: Secrets redacted.
        state:
          type: string
          enum:
            - idle
            - connecting
            - receiving
            - error
        connected:
          type: boolean
        active:
          type: boolean
        health:
          $ref: "#/components/schemas/HealthValue"
        bitrate:
          type: integer
          minimum: 0
        bytes:
          type: integer
          minimum: 0
        packets:
          type: integer
          minimum: 0
        errors:
          type: integer
          minimum: 0
        cc_errors:
          type: integer
          minimum: 0
        reconnects:
          type: integer
          minimum: 0
        last_data:
          $ref: "#/components/schemas/Time"
        last_error:
          type: string
        remote:
          type: string
          description: Peer address; "channel:<source>[/<rendition>]" for copy:// inputs.
        healthy_for_s:
          type: number
          minimum: 0
        srt_publish:
          $ref: "#/components/schemas/SRTPortStatus"
        tr101290:
          $ref: "#/components/schemas/TR101290Status"
        analysis:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/InputAnalysis"
        pool:
          oneOf:
            - type: "null"
            - $ref: "#/components/schemas/InputPool"
    InputAnalysis:
      type: object
      additionalProperties: false
      description: Media analysis of an input (docs/API.md 4.7b); the last one (live false) while it is disconnected.
      required:
        - live
        - measured_at
        - video_codec
        - width
        - height
        - scan
        - fps
        - class
        - bitrate
        - video_tracks
        - audio_tracks
        - subtitle_tracks
        - teletext_tracks
        - audio_languages
        - missing_required
        - window
      properties:
        live:
          type: boolean
        measured_at:
          $ref: "#/components/schemas/Time"
        video_codec:
          type: string
          description: '"" = no video.'
        width:
          type: integer
          minimum: 0
        height:
          type: integer
          minimum: 0
        scan:
          type: string
          enum:
            - ""
            - interlaced
            - progressive
        fps:
          type: number
          minimum: 0
        class:
          type: string
          enum:
            - uhd
            - hd
            - sd
            - audio
            - unknown
        bitrate:
          type: integer
          minimum: 0
        video_tracks:
          type: integer
          minimum: 0
        audio_tracks:
          type: integer
          minimum: 0
        subtitle_tracks:
          type: integer
          minimum: 0
        teletext_tracks:
          type: integer
          minimum: 0
        audio_languages:
          type: array
          items:
            type: string
        missing_required:
          type: integer
          minimum: 0
          description: Required track-map slots this input cannot fill.
        window:
          type: object
          additionalProperties: false
          required:
            - window_s
            - observed_s
            - errored_s
            - p1
            - p2
            - p1_per_min
            - cc_errors
            - reconnects
            - gaps
          properties:
            window_s:
              type: number
              minimum: 0
            observed_s:
              type: number
              minimum: 0
            errored_s:
              type: integer
              minimum: 0
              description: Seconds with TR 101 290 P1 or CC errors, a gap or a reconnect.
            p1:
              type: integer
              minimum: 0
            p2:
              type: integer
              minimum: 0
            p1_per_min:
              type: number
              minimum: 0
            cc_errors:
              type: integer
              minimum: 0
            reconnects:
              type: integer
              minimum: 0
            gaps:
              type: integer
              minimum: 0
    InputPool:
      type: object
      additionalProperties: false
      description: The input's rank, quality score and connection slot (failover.select, failover.max_connected).
      required:
        - rank
        - score
        - erroring
        - stable_for_s
        - slot
        - reason
        - failures
        - cooldown_until
        - last_failure
      properties:
        rank:
          type: integer
          minimum: 1
        score:
          type:
            - integer
            - "null"
          minimum: 0
          maximum: 100
          description: null = never measured.
        erroring:
          type: boolean
        stable_for_s:
          type: number
          minimum: 0
        slot:
          type: string
          enum:
            - active
            - switching
            - pinned
            - requested
            - pool
            - probing
            - cooldown
            - standby
            - connected
            - push
        reason:
          type: string
        failures:
          type: integer
          minimum: 0
        cooldown_until:
          $ref: "#/components/schemas/Time"
        last_failure:
          type: string
    FailoverSelect:
      type: string
      enum:
        - priority
        - quality
      description: "Input selection: priority (default) or quality (docs/API.md 4.7b)."
    MaxConnected:
      type: integer
      minimum: 0
      maximum: 64
      description: Pull inputs connected at once (hot standby only; 0 = all).
    SourceStatus:
      type: object
      additionalProperties: false
      required:
        - tracks
        - pcr_pid
        - service_name
        - provider
      properties:
        tracks:
          type: array
          items:
            $ref: "#/components/schemas/Track"
        pcr_pid:
          type: integer
        service_name:
          type: string
        provider:
          type: string
    Track:
      type: object
      additionalProperties: false
      required:
        - pid
        - input_pid
        - stream_type
        - type
        - codec
        - bitrate
      properties:
        pid:
          type: integer
        input_pid:
          type: integer
        stream_type:
          type: integer
        type:
          type: string
          enum:
            - video
            - audio
            - subtitle
            - teletext
            - data
        codec:
          type: string
        profile:
          type: string
        width:
          type: integer
        height:
          type: integer
        fps:
          type: number
        interlaced:
          type: boolean
        bitrate:
          type: integer
          minimum: 0
        language:
          type: string
        channels:
          type: integer
        sample_rate:
          type: integer
        hearing_impaired:
          type: boolean
        teletext_pages:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - page
              - language
              - kind
            properties:
              page:
                type: integer
              language:
                type: string
              kind:
                type: string
                enum:
                  - initial
                  - subtitle
                  - hearing_impaired
                  - info
                  - schedule
    TranscoderStatus:
      type: object
      additionalProperties: false
      required:
        - profile
        - state
        - hardware
        - fps
        - speed
        - uptime_s
        - restarts
        - stall_restarts
        - last_exit
        - last_errors
        - renditions
      properties:
        profile:
          type: string
        state:
          type: string
          enum:
            - starting
            - running
            - backoff
            - stopped
            - sleeping
          description: sleeping = on-demand transcoder without consumers (no FFmpeg, no GPU lease).
        hardware:
          type: string
        fps:
          type: number
        speed:
          type: number
        uptime_s:
          type: number
          minimum: 0
        restarts:
          type: integer
          minimum: 0
        stall_restarts:
          type: integer
          minimum: 0
        last_exit:
          type: string
        last_errors:
          type: array
          items:
            type: string
        error:
          type: string
        gpu:
          type: string
          description: GPU admission (WP 6.3); omitted without admission control.
        admission:
          $ref: "#/components/schemas/AdmissionState"
        admission_detail:
          type: string
        renditions:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - bitrate_out
            properties:
              name:
                type: string
              bitrate_out:
                type: integer
                minimum: 0
        remux:
          $ref: "#/components/schemas/RemuxStatus"
        gpu_device:
          type: integer
          minimum: 0
          description: GPU index (nvidia-smi order) FFmpeg uses; omitted = none / default GPU.
        decode:
          type: string
          enum:
            - gpu
            - cpu
          description: Where the source video is decoded.
        decode_reason:
          type: string
          description: "Why a GPU plan decodes on the CPU (decode: cpu, cpu_decode_codecs, fallback, source not NVDEC-decodable on this GPU generation / FFmpeg build), or, with decode gpu, that Blackwell NVDEC 4:2:2 frames go through system memory."
        shared_audio:
          type: boolean
          description: Audio encoded once and shared by all renditions.
        fallbacks:
          type: array
          items:
            type: string
            enum:
              - cpu_decode
              - more_surfaces
              - no_b_ref
              - no_bframes
              - no_temporal_aq
              - no_lookahead
          description: Hardware fallbacks applied after FFmpeg errors (sticky until the transcoder restarts).
        error_class:
          type: string
          enum:
            - nvdec_unsupported
            - nvdec_surfaces
            - nvenc_unsupported_feature
            - nvenc_session_limit
            - gpu_out_of_memory
            - gpu_unavailable
          description: Class of the last GPU-related FFmpeg error.
        source:
          type: object
          additionalProperties: false
          description: Source video sniffed from the input (PMT, SPS / sequence header, pictures); followed while the transcoder runs when its plan depends on it.
          required:
            - video_codec
            - width
            - height
            - chroma_format
            - bit_depth
            - interlaced
            - progressive
            - frame_rate
            - audio_tracks
          properties:
            video_codec:
              type: string
            width:
              type: integer
            height:
              type: integer
            chroma_format:
              type: integer
              description: 1 = 4:2:0, 2 = 4:2:2, 3 = 4:4:4, 0 unknown
            bit_depth:
              type: integer
            interlaced:
              type: boolean
              description: The source carries (or may carry) interlaced pictures.
            progressive:
              type: boolean
              description: "The source is known to be progressive (interlaced and progressive both false: unknown)."
            sar:
              type: string
              examples:
                - 64:45
              description: Sample aspect ratio as the decoder reports it (unspecified = 1:1); omitted when unknown.
            frame_rate:
              type: number
            audio_tracks:
              type: integer
        load:
          type: object
          additionalProperties: false
          description: Estimated cost of the current plan (transcode.EstimateLoadFor).
          required:
            - gpu
            - decode_on_gpu
            - encode_sessions
            - decode_mpix_s
            - encode_mpix_s
            - gpu_memory_mb
            - host_copies_per_s
            - cpu_cores
            - units
          properties:
            gpu:
              type: boolean
            decode_on_gpu:
              type: boolean
            encode_sessions:
              type: integer
            decode_mpix_s:
              type: number
            encode_mpix_s:
              type: number
              description: Preset-weighted (p4 = 1).
            gpu_memory_mb:
              type: integer
            host_copies_per_s:
              type: number
            cpu_cores:
              type: number
            units:
              type: number
        filters:
          type: array
          items:
            type: string
          examples:
            - - "deinterlace: skipped (progressive source)"
              - "fps: first (source 50 fps > 25)"
              - "720p: background skipped (source 16:9)"
          description: "Source-dependent filter decisions of the current plan (docs/TRANSCODING.md §2.5): deinterlacer skipped for a progressive source or run on interlaced frames only, fps moved first, fit/crop/background skipped when the source has the rendition's aspect ratio. A source change that alters them restarts FFmpeg with a new plan."
    RemuxStatus:
      type: object
      additionalProperties: false
      required:
        - copy_teletext
        - copy_dvbsub
        - copy_scte35
        - copy_data
        - locked
        - offset
        - offset_ms
        - packets_remuxed
        - late_drops
        - overflow_drops
        - resyncs
        - renditions
      properties:
        copy_teletext:
          type: boolean
        copy_dvbsub:
          type: boolean
        copy_scte35:
          type: boolean
        copy_data:
          type: boolean
        locked:
          type: boolean
        offset:
          type: integer
        offset_ms:
          type: integer
        packets_remuxed:
          type: integer
        late_drops:
          type: integer
        overflow_drops:
          type: integer
        resyncs:
          type: integer
        renditions:
          type:
            - array
            - "null"
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - locked
              - offset
              - offset_ms
              - max_residual_ms
              - resyncs
              - packets_remuxed
              - pes_remuxed
              - sections_remuxed
              - late_drops
              - overflow_drops
              - broken_drops
              - bad_pts
              - no_pts
              - queued
              - pmt_version
              - pids
            properties:
              name:
                type: string
              locked:
                type: boolean
              offset:
                type: integer
              offset_ms:
                type: integer
              max_residual_ms:
                type: number
              resyncs:
                type: integer
              packets_remuxed:
                type: integer
              pes_remuxed:
                type: integer
              sections_remuxed:
                type: integer
              late_drops:
                type: integer
              overflow_drops:
                type: integer
              broken_drops:
                type: integer
              bad_pts:
                type: integer
              no_pts:
                type: integer
              queued:
                type: integer
              pmt_version:
                type: integer
              pids:
                type:
                  - array
                  - "null"
                items:
                  type: object
                  additionalProperties: false
                  required:
                    - source_pid
                    - pid
                    - type
                    - codec
                  properties:
                    source_pid:
                      type: integer
                    pid:
                      type: integer
                    type:
                      type: string
                      enum:
                        - teletext
                        - subtitle
                        - data
                    codec:
                      type: string
                    language:
                      type: string
    StreamOutput:
      type: object
      additionalProperties: false
      required:
        - enabled
        - viewers
        - bitrate_out
        - url
      properties:
        enabled:
          type: boolean
        viewers:
          type: integer
          minimum: 0
        bitrate_out:
          type: integer
          minimum: 0
        url:
          type: string
          description: '"" when disabled.'
        ts_url:
          type: string
          description: HLS with ts_segments.
        rendition_urls:
          type: object
          additionalProperties:
            type: string
          description: 'SRT and HTTP-TS of a transcoded channel: URL per rendition name, plus "source" (the untranscoded source). Omitted when the output is disabled or the channel is not transcoded.'
        packager:
          $ref: "#/components/schemas/PackagerStatus"
    PackagerStatus:
      type: object
      additionalProperties: false
      required:
        - segments
        - last_segment
        - status
        - renditions
        - unpackaged_audio
      properties:
        segments:
          type: integer
          minimum: 0
        last_segment:
          $ref: "#/components/schemas/Time"
        status:
          type: string
        unpackaged_audio:
          description: Source audio tracks missing from the CMAF HLS (index.m3u8) and DASH output because CMAF cannot carry their codec (MP2, AAC-LATM); `in_ts_segments` = still carried in the TS-segment HLS.
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - index
              - pid
              - codec
              - language
              - in_ts_segments
              - reason
            properties:
              index:
                type: integer
                minimum: 0
                description: n-th audio track of the program (0-based)
              pid:
                type: integer
                minimum: 0
                maximum: 8191
              codec:
                type: string
                description: mp2, aac_latm
              language:
                type: string
              in_ts_segments:
                type: boolean
              reason:
                type: string
        renditions:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - running
              - segments
              - last_segment
              - restarts
              - status
              - last_error
            properties:
              name:
                type: string
              running:
                type: boolean
              segments:
                type: integer
                minimum: 0
              last_segment:
                $ref: "#/components/schemas/Time"
              restarts:
                type: integer
                minimum: 0
              status:
                type: string
              last_error:
                type: string
    PreviewToken:
      type: object
      additionalProperties: false
      required:
        - token
        - expires_at
        - hls_url
        - dash_url
        - http_ts_url
      properties:
        token:
          type: string
        expires_at:
          $ref: "#/components/schemas/Time"
        hls_url:
          type: string
        dash_url:
          type: string
        http_ts_url:
          type: string
    EventSnapshot:
      type: object
      additionalProperties: false
      required:
        - ts
        - server
        - channels
      properties:
        ts:
          $ref: "#/components/schemas/Time"
        server:
          $ref: "#/components/schemas/ServerInfo"
        channels:
          type: array
          items:
            $ref: "#/components/schemas/ChannelSummary"
        alarms:
          type: array
          items:
            $ref: "#/components/schemas/Alarm"
          description: Active alarms (snapshot only).
    EventStatus:
      $ref: "#/components/schemas/EventSnapshot"
      description: Same shape; channels holds only changed summaries.
    EventChannelRemoved:
      type: object
      additionalProperties: false
      required:
        - name
      properties:
        name:
          type: string
    EventSwitch:
      type: object
      additionalProperties: false
      required:
        - channel
        - at
        - from
        - to
        - reason
      properties:
        channel:
          type: string
        at:
          $ref: "#/components/schemas/Time"
        from:
          type:
            - integer
            - "null"
        to:
          type:
            - integer
            - "null"
        reason:
          $ref: "#/components/schemas/SwitchReason"
        detail:
          type: string
    Profile:
      type: object
      additionalProperties: false
      required:
        - name
        - hardware
        - deinterlace
        - video
        - audio
        - subtitles
        - used_by
      properties:
        name:
          $ref: "#/components/schemas/Name"
        hardware:
          type: string
          examples:
            - auto
            - cpu
            - nvidia
            - nvidia:0
            - intel-qsv
            - vaapi
        deinterlace:
          type: string
          enum:
            - auto
            - on
            - off
        deinterlace_rate:
          type: string
          enum:
            - frame
            - field
          description: "Omitted = frame. field: one frame per field (25i -> 50p)."
        deinterlacer:
          type: string
          enum:
            - auto
            - yadif
            - bwdif
          description: Omitted = auto (bwdif if available).
        decode:
          type: string
          enum:
            - auto
            - gpu
            - cpu
          description: NVIDIA decode location (docs/TRANSCODING.md 2.3). Omitted = auto.
        cpu_decode_codecs:
          type: array
          items:
            type: string
            enum:
              - h264
              - hevc
              - mpeg2video
          description: Source codecs decode=auto decodes on the CPU.
        crop:
          type: string
          pattern: ^[0-9]+:[0-9]+:[0-9]+:[0-9]+$
          examples:
            - 0:72:1920:936
          description: x:y:w:h in source display pixels.
        scaler:
          type: string
          enum:
            - bicubic
            - bilinear
            - lanczos
        separate_audio:
          type: boolean
          description: "true = encode audio per rendition (default: once, shared via the tee muxer)."
        video:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - codec
              - width
              - height
              - bitrate
              - gop
              - preset
              - fps
            properties:
              name:
                type: string
              codec:
                type: string
                enum:
                  - h264
                  - hevc
                  - copy
                  - none
                description: none = audio-only rendition.
              width:
                type: integer
                minimum: -1
                description: 0 or -1 = keep aspect ratio.
              height:
                type: integer
                minimum: -1
              bitrate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              max_rate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              buf_size:
                $ref: "#/components/schemas/ConfiguredBitrate"
              gop:
                $ref: "#/components/schemas/Duration"
              preset:
                type: string
              profile:
                type: string
              level:
                type: string
              fps:
                type: number
                minimum: 0
              resize:
                type: string
                enum:
                  - scale
                  - fit
                  - crop
              background:
                type: string
                examples:
                  - blur
                  - black
                  - "#627834"
                description: "Letterbox background for fit: blur or a colour."
              sar:
                type: string
                examples:
                  - 64:45
                  - 1:1
              pix_fmt:
                type: string
                enum:
                  - yuv420p
                  - nv12
              rc:
                type: string
                enum:
                  - cbr
                  - vbr
              gop_frames:
                type: integer
                minimum: 0
                maximum: 1000
                description: Keyframe interval in frames; gop is then "0".
              bframes:
                type: integer
                minimum: 0
                maximum: 4
              open_gop:
                type: boolean
              tune:
                type: string
                enum:
                  - hq
                  - ll
                  - ull
              multipass:
                type: string
                enum:
                  - disabled
                  - qres
                  - fullres
              spatial_aq:
                type: boolean
              temporal_aq:
                type: boolean
              lookahead:
                type: integer
                minimum: 0
                maximum: 32
        audio:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - tracks
              - codec
              - bitrate
              - channels
              - sample_rate
            properties:
              tracks:
                type: string
                examples:
                  - all
                  - first
                  - lang:deu,eng
                  - index:0,2
              codec:
                type: string
                enum:
                  - aac
                  - ac3
                  - mp2
                  - copy
              bitrate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              channels:
                type: integer
                minimum: 0
              sample_rate:
                type: integer
                minimum: 0
        subtitles:
          type: object
          additionalProperties: false
          required:
            - copy
            - copy_teletext
            - copy_dvbsub
            - copy_scte35
            - copy_data
          properties:
            copy:
              type: boolean
            copy_teletext:
              type: boolean
            copy_dvbsub:
              type: boolean
            copy_scte35:
              type: boolean
            copy_data:
              type: boolean
        extra_args:
          type: array
          items:
            type: string
          description: Raw FFmpeg output options (omitted when empty). Read only unless `api.allow_extra_args` is set in the config file.
        used_by:
          type: array
          items:
            type: string
          description: Read only.
    ProfileRequest:
      type: object
      additionalProperties: false
      properties:
        name:
          $ref: "#/components/schemas/Name"
        hardware:
          type: string
        deinterlace:
          type: string
        deinterlace_rate:
          type: string
        deinterlacer:
          type: string
        decode:
          type: string
        cpu_decode_codecs:
          type: array
          items:
            type: string
        crop:
          type: string
        scaler:
          type: string
        separate_audio:
          type: boolean
        video:
          type: array
          items:
            type: object
            additionalProperties: false
            properties:
              name:
                type: string
              codec:
                type: string
              width:
                type: integer
              height:
                type: integer
              bitrate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              max_rate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              buf_size:
                $ref: "#/components/schemas/ConfiguredBitrate"
              gop:
                $ref: "#/components/schemas/Duration"
              preset:
                type: string
              profile:
                type: string
              level:
                type: string
              fps:
                type: number
              resize:
                type: string
              background:
                type: string
              sar:
                type: string
              pix_fmt:
                type: string
              rc:
                type: string
              gop_frames:
                type: integer
              bframes:
                type: integer
              open_gop:
                type: boolean
              tune:
                type: string
              multipass:
                type: string
              spatial_aq:
                type: boolean
              temporal_aq:
                type: boolean
              lookahead:
                type: integer
        audio:
          type: array
          items:
            type: object
            additionalProperties: false
            properties:
              tracks:
                type: string
              codec:
                type: string
              bitrate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              channels:
                type: integer
              sample_rate:
                type: integer
        subtitles:
          type: object
          additionalProperties: false
          properties:
            copy:
              type: boolean
            copy_teletext:
              type: boolean
            copy_dvbsub:
              type: boolean
            copy_scte35:
              type: boolean
            copy_data:
              type: boolean
        extra_args:
          type:
            - array
            - "null"
          items:
            type: string
          description: Omitted or null = keep the stored ones (PUT). Setting or changing them needs `api.allow_extra_args` (else 403 `extra_args_disabled`); sending the stored value unchanged is always accepted.
        used_by:
          type: array
          items:
            type: string
          description: Ignored.
    InlineProfile:
      description: "A channel's own transcode profile (channel.transcode as an object): the fields of a Profile without its name and used_by."
      type: object
      additionalProperties: false
      required:
        - hardware
        - deinterlace
        - video
        - audio
        - subtitles
      properties:
        hardware:
          type: string
          examples:
            - auto
            - cpu
            - nvidia
            - nvidia:0
            - intel-qsv
            - vaapi
        deinterlace:
          type: string
          enum:
            - auto
            - on
            - off
        deinterlace_rate:
          type: string
          enum:
            - frame
            - field
          description: "Omitted = frame. field: one frame per field (25i -> 50p)."
        deinterlacer:
          type: string
          enum:
            - auto
            - yadif
            - bwdif
          description: Omitted = auto (bwdif if available).
        decode:
          type: string
          enum:
            - auto
            - gpu
            - cpu
          description: NVIDIA decode location (docs/TRANSCODING.md 2.3). Omitted = auto.
        cpu_decode_codecs:
          type: array
          items:
            type: string
            enum:
              - h264
              - hevc
              - mpeg2video
          description: Source codecs decode=auto decodes on the CPU.
        crop:
          type: string
          pattern: ^[0-9]+:[0-9]+:[0-9]+:[0-9]+$
          examples:
            - 0:72:1920:936
          description: x:y:w:h in source display pixels.
        scaler:
          type: string
          enum:
            - bicubic
            - bilinear
            - lanczos
        separate_audio:
          type: boolean
          description: "true = encode audio per rendition (default: once, shared via the tee muxer)."
        video:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - codec
              - width
              - height
              - bitrate
              - gop
              - preset
              - fps
            properties:
              name:
                type: string
              codec:
                type: string
                enum:
                  - h264
                  - hevc
                  - copy
                  - none
                description: none = audio-only rendition.
              width:
                type: integer
                minimum: -1
                description: 0 or -1 = keep aspect ratio.
              height:
                type: integer
                minimum: -1
              bitrate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              max_rate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              buf_size:
                $ref: "#/components/schemas/ConfiguredBitrate"
              gop:
                $ref: "#/components/schemas/Duration"
              preset:
                type: string
              profile:
                type: string
              level:
                type: string
              fps:
                type: number
                minimum: 0
              resize:
                type: string
                enum:
                  - scale
                  - fit
                  - crop
              background:
                type: string
                examples:
                  - blur
                  - black
                  - "#627834"
                description: "Letterbox background for fit: blur or a colour."
              sar:
                type: string
                examples:
                  - 64:45
                  - 1:1
              pix_fmt:
                type: string
                enum:
                  - yuv420p
                  - nv12
              rc:
                type: string
                enum:
                  - cbr
                  - vbr
              gop_frames:
                type: integer
                minimum: 0
                maximum: 1000
                description: Keyframe interval in frames; gop is then "0".
              bframes:
                type: integer
                minimum: 0
                maximum: 4
              open_gop:
                type: boolean
              tune:
                type: string
                enum:
                  - hq
                  - ll
                  - ull
              multipass:
                type: string
                enum:
                  - disabled
                  - qres
                  - fullres
              spatial_aq:
                type: boolean
              temporal_aq:
                type: boolean
              lookahead:
                type: integer
                minimum: 0
                maximum: 32
        audio:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - tracks
              - codec
              - bitrate
              - channels
              - sample_rate
            properties:
              tracks:
                type: string
                examples:
                  - all
                  - first
                  - lang:deu,eng
                  - index:0,2
              codec:
                type: string
                enum:
                  - aac
                  - ac3
                  - mp2
                  - copy
              bitrate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              channels:
                type: integer
                minimum: 0
              sample_rate:
                type: integer
                minimum: 0
        subtitles:
          type: object
          additionalProperties: false
          required:
            - copy
            - copy_teletext
            - copy_dvbsub
            - copy_scte35
            - copy_data
          properties:
            copy:
              type: boolean
            copy_teletext:
              type: boolean
            copy_dvbsub:
              type: boolean
            copy_scte35:
              type: boolean
            copy_data:
              type: boolean
        extra_args:
          type: array
          items:
            type: string
          description: Raw FFmpeg output options (omitted when empty). Read only unless `api.allow_extra_args` is set in the config file.
    InlineProfileRequest:
      description: "channel.transcode as an object in a channel write: the fields of a ProfileRequest without its name and used_by."
      type: object
      additionalProperties: false
      properties:
        hardware:
          type: string
        deinterlace:
          type: string
        deinterlace_rate:
          type: string
        deinterlacer:
          type: string
        decode:
          type: string
        cpu_decode_codecs:
          type: array
          items:
            type: string
        crop:
          type: string
        scaler:
          type: string
        separate_audio:
          type: boolean
        video:
          type: array
          items:
            type: object
            additionalProperties: false
            properties:
              name:
                type: string
              codec:
                type: string
              width:
                type: integer
              height:
                type: integer
              bitrate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              max_rate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              buf_size:
                $ref: "#/components/schemas/ConfiguredBitrate"
              gop:
                $ref: "#/components/schemas/Duration"
              preset:
                type: string
              profile:
                type: string
              level:
                type: string
              fps:
                type: number
              resize:
                type: string
              background:
                type: string
              sar:
                type: string
              pix_fmt:
                type: string
              rc:
                type: string
              gop_frames:
                type: integer
              bframes:
                type: integer
              open_gop:
                type: boolean
              tune:
                type: string
              multipass:
                type: string
              spatial_aq:
                type: boolean
              temporal_aq:
                type: boolean
              lookahead:
                type: integer
        audio:
          type: array
          items:
            type: object
            additionalProperties: false
            properties:
              tracks:
                type: string
              codec:
                type: string
              bitrate:
                $ref: "#/components/schemas/ConfiguredBitrate"
              channels:
                type: integer
              sample_rate:
                type: integer
        subtitles:
          type: object
          additionalProperties: false
          properties:
            copy:
              type: boolean
            copy_teletext:
              type: boolean
            copy_dvbsub:
              type: boolean
            copy_scte35:
              type: boolean
            copy_data:
              type: boolean
        extra_args:
          type:
            - array
            - "null"
          items:
            type: string
          description: Omitted or null = keep the stored ones (PUT). Setting or changing them needs `api.allow_extra_args` (else 403 `extra_args_disabled`); sending the stored value unchanged is always accepted.
    ProfilePatch:
      type: object
      description: JSON Merge Patch (RFC 7386) of `Profile`.
      additionalProperties: false
      properties:
        name:
          $ref: "#/components/schemas/Name"
        hardware:
          type:
            - string
            - "null"
        deinterlace:
          type:
            - string
            - "null"
        deinterlace_rate:
          type:
            - string
            - "null"
        deinterlacer:
          type:
            - string
            - "null"
        decode:
          type:
            - string
            - "null"
        cpu_decode_codecs:
          type:
            - array
            - "null"
          items:
            type: string
        crop:
          type:
            - string
            - "null"
        scaler:
          type:
            - string
            - "null"
        separate_audio:
          type:
            - boolean
            - "null"
        video:
          type:
            - array
            - "null"
          items:
            type: object
        audio:
          type:
            - array
            - "null"
          items:
            type: object
        subtitles:
          type:
            - object
            - "null"
        extra_args:
          type:
            - array
            - "null"
          items:
            type: string
          description: null removes them; changing them needs api.allow_extra_args.
        used_by:
          type:
            - array
            - "null"
    Capabilities:
      type: object
      additionalProperties: false
      required:
        - ffmpeg
        - probed_at
        - hardware
        - auto
        - encoders
      properties:
        ffmpeg:
          type: object
          additionalProperties: false
          required:
            - available
            - path
            - version
          properties:
            available:
              type: boolean
            path:
              type: string
            version:
              type: string
        probed_at:
          $ref: "#/components/schemas/Time"
        hardware:
          type: object
          propertyNames:
            enum:
              - cpu
              - nvidia
              - intel-qsv
              - vaapi
          additionalProperties:
            type: object
            additionalProperties: false
            required:
              - available
              - hevc
              - reason
              - devices
            properties:
              available:
                type: boolean
              hevc:
                type: boolean
              reason:
                type: string
              devices:
                type: array
                items:
                  type: object
                  additionalProperties: false
                  required:
                    - id
                    - name
                  properties:
                    id:
                      type: string
                    name:
                      type: string
        auto:
          type: string
          description: What hardware "auto" resolves to ("" if nothing works).
        encoders:
          type: array
          items:
            type: string
    Session:
      type: object
      additionalProperties: false
      required:
        - id
        - protocol
        - channel
        - mode
        - remote_addr
        - user
        - start
        - duration_s
        - bytes
        - bitrate
      properties:
        id:
          type: string
        protocol:
          type: string
          enum:
            - srt
            - http-ts
            - hls
            - dash
        channel:
          type: string
        mode:
          type: string
          enum:
            - read
            - publish
        rendition:
          type: string
          description: 'SRT / HTTP-TS readers of one rendition: "source" or a rendition name (omitted for the default read bus).'
        remote_addr:
          type: string
        user:
          type: string
        preview:
          type: boolean
        port:
          type: integer
          description: Dedicated SRT port the session arrived on (omitted for the shared listener).
        node:
          type: string
          description: "Kick responses only: the cluster node the session was kicked on (omitted for this node)."
        start:
          $ref: "#/components/schemas/Time"
        duration_s:
          type: number
          minimum: 0
        bytes:
          type: integer
          minimum: 0
        bitrate:
          type: integer
          minimum: 0
        rtt_ms:
          type: number
        packets_lost:
          type: integer
        packets_retrans:
          type: integer
        packets_dropped:
          type: integer
    KickResult:
      type: object
      additionalProperties: false
      required:
        - kicked
        - sessions
      properties:
        kicked:
          type: integer
          minimum: 0
        sessions:
          type: array
          items:
            $ref: "#/components/schemas/Session"
        block:
          $ref: "#/components/schemas/Block"
        failed_nodes:
          type: array
          items:
            type: string
          description: Cluster nodes the kick could not be forwarded to.
    Block:
      type: object
      additionalProperties: false
      required:
        - id
        - created
        - expires
        - expires_in_s
      properties:
        id:
          type: string
        channel:
          type: string
          description: Omitted = all channels.
        user:
          type: string
        token:
          type: string
          description: Masked (first 4 characters of long tokens, else ***).
        ip:
          type: string
          description: Address or CIDR prefix.
        created:
          $ref: "#/components/schemas/Time"
        expires:
          $ref: "#/components/schemas/Time"
        expires_in_s:
          type: number
          minimum: 0
        by:
          type: string
          description: API user that created the block.
    ConfigImportResult:
      type: object
      additionalProperties: false
      required:
        - applied
        - changes
        - restart_required
      properties:
        applied:
          type: boolean
        changes:
          $ref: "#/components/schemas/ConfigChanges"
        restart_required:
          type: array
          items:
            type: string
    ConfigChanges:
      type: object
      additionalProperties: false
      required:
        - channels_added
        - channels_removed
        - channels_changed
        - profiles_added
        - profiles_removed
        - profiles_changed
        - templates_added
        - templates_removed
        - templates_changed
      properties:
        channels_added:
          type: array
          items:
            type: string
        channels_removed:
          type: array
          items:
            type: string
        channels_changed:
          type: array
          items:
            type: string
          description: Effective configuration or stored form changed (a template change lists its channels whose effective configuration changed).
        profiles_added:
          type: array
          items:
            type: string
        profiles_removed:
          type: array
          items:
            type: string
        profiles_changed:
          type: array
          items:
            type: string
        templates_added:
          type: array
          items:
            type: string
        templates_removed:
          type: array
          items:
            type: string
        templates_changed:
          type: array
          items:
            type: string
    ConfigValidation:
      type: object
      additionalProperties: false
      required:
        - valid
        - errors
        - warnings
      properties:
        valid:
          type: boolean
        errors:
          type: array
          items:
            $ref: "#/components/schemas/Detail"
        warnings:
          type: array
          items:
            $ref: "#/components/schemas/Detail"
    Metric:
      type:
        - number
        - "null"
      description: Measured value; null = not reported by the driver ([N/A]).
    GPUCapacityModel:
      type: object
      additionalProperties: false
      required:
        - encode_mpix_s
        - decode_mpix_s
        - max_sessions
        - memory_per_channel_mb
      properties:
        encode_mpix_s:
          type: number
        decode_mpix_s:
          type: number
        max_sessions:
          type: integer
        memory_per_channel_mb:
          type: number
    GPUFree:
      type: object
      additionalProperties: false
      required:
        - encode_mpix_s
        - decode_mpix_s
        - memory_mb
        - sessions
      properties:
        encode_mpix_s:
          type: number
        decode_mpix_s:
          type: number
        memory_mb:
          type: number
          description: -1 = unknown
        sessions:
          type: integer
          description: -1 = unlimited
    GPULoad:
      type: object
      additionalProperties: false
      required:
        - decode_mpix_s
        - encode_mpix_s
        - sessions
        - memory_mb
      properties:
        decode_mpix_s:
          type: number
        encode_mpix_s:
          type: number
        sessions:
          type: integer
        memory_mb:
          type: number
    GPUInfo:
      type: object
      additionalProperties: false
      required:
        - node
        - monitor
        - enforced
        - policy
        - gpus
        - waiting
        - admissions
        - intel
      properties:
        node:
          type: string
        monitor:
          type: object
          additionalProperties: false
          required:
            - state
            - reason
            - path
            - last_error
            - restarts
            - parse_errors
            - samples
            - last_sample
            - dropped_fields
            - apps_at
            - apps_error
          properties:
            state:
              type: string
              enum:
                - off
                - not_found
                - starting
                - running
                - no_devices
                - failed
            reason:
              type: string
            path:
              type: string
            last_error:
              type: string
            restarts:
              type: integer
            parse_errors:
              type: integer
            samples:
              type: integer
            last_sample:
              $ref: "#/components/schemas/Time"
            dropped_fields:
              type:
                - array
                - "null"
              items:
                type: string
            apps_at:
              $ref: "#/components/schemas/Time"
            apps_error:
              type: string
        enforced:
          type: boolean
          description: Admission control active.
        policy:
          type: object
          additionalProperties: false
          required:
            - max_encoder_util
            - max_decoder_util
            - max_memory_used
            - max_temperature
            - max_sessions
            - reserve
            - hysteresis
            - on_full
            - retry_interval
            - capacity
            - models
            - assume_source
          properties:
            max_encoder_util:
              type: number
            max_decoder_util:
              type: number
            max_memory_used:
              type: number
            max_temperature:
              type: number
            max_sessions:
              type: string
              description: '"auto" or a number'
            reserve:
              type: number
            hysteresis:
              type: number
            on_full:
              type: string
            retry_interval:
              type: string
            capacity:
              $ref: "#/components/schemas/GPUCapacityModel"
            models:
              type:
                - object
                - "null"
              additionalProperties:
                $ref: "#/components/schemas/GPUCapacityModel"
            assume_source:
              type: string
        gpus:
          type:
            - array
            - "null"
          items:
            $ref: "#/components/schemas/GPUDevice"
        waiting:
          type:
            - array
            - "null"
          items:
            type: object
            additionalProperties: false
            required:
              - channel
              - profile
              - reason
              - since
              - cpu_fallback
              - queued
            properties:
              channel:
                type: string
              profile:
                type: string
              reason:
                type: string
              since:
                $ref: "#/components/schemas/Time"
              cpu_fallback:
                type: boolean
              queued:
                type: boolean
        admissions:
          type: object
          additionalProperties: false
          required:
            - admitted
            - rejected
            - cpu_fallbacks
            - queued
            - unenforced
            - session_limits_learned
            - moves
          properties:
            admitted:
              type: integer
            rejected:
              type: integer
            cpu_fallbacks:
              type: integer
            queued:
              type: integer
            unenforced:
              type: integer
            session_limits_learned:
              type: integer
            moves:
              type: integer
        intel:
          oneOf:
            - type: "null"
            - type: object
              description: Experimental Intel view (gpu.intel_experimental).
              additionalProperties: false
              required:
                - experimental
                - state
                - reason
                - last_error
                - devices
                - sample
                - video_busy
                - render_busy
              properties:
                experimental:
                  type: boolean
                state:
                  type: string
                reason:
                  type: string
                last_error:
                  type: string
                devices:
                  type:
                    - array
                    - "null"
                  items:
                    type: object
                    additionalProperties: false
                    required:
                      - card
                      - pci_id
                      - cur_freq_mhz
                      - max_freq_mhz
                    properties:
                      card:
                        type: string
                      pci_id:
                        type: string
                      cur_freq_mhz:
                        $ref: "#/components/schemas/Metric"
                      max_freq_mhz:
                        $ref: "#/components/schemas/Metric"
                sample:
                  oneOf:
                    - type: "null"
                    - type: object
                      additionalProperties: false
                      required:
                        - engines
                        - freq_mhz
                        - power_gpu_w
                      properties:
                        engines:
                          type:
                            - object
                            - "null"
                          additionalProperties:
                            type: number
                        freq_mhz:
                          $ref: "#/components/schemas/Metric"
                        power_gpu_w:
                          $ref: "#/components/schemas/Metric"
                video_busy:
                  $ref: "#/components/schemas/Metric"
                render_busy:
                  $ref: "#/components/schemas/Metric"
    GPUDevice:
      type: object
      additionalProperties: false
      required:
        - kind
        - index
        - uuid
        - name
        - driver_version
        - available
        - last_sample
        - utilization
        - memory
        - temperature_c
        - power_w
        - power_limit_w
        - clock_sm_mhz
        - fan_pct
        - pstate
        - encoder
        - throttle
        - capacity
        - model
        - committed
        - effective
        - session_limit
        - session_limit_source
        - headroom
        - full
        - channels
      properties:
        kind:
          const: nvidia
        index:
          type: integer
        uuid:
          type: string
        name:
          type: string
        driver_version:
          type: string
        available:
          type: boolean
          description: false when no sample for 5 s (alarm gpu_unavailable).
        last_sample:
          $ref: "#/components/schemas/Time"
        utilization:
          type: object
          additionalProperties: false
          required:
            - gpu
            - memory
            - encoder
            - decoder
          properties:
            gpu:
              $ref: "#/components/schemas/Metric"
            memory:
              $ref: "#/components/schemas/Metric"
            encoder:
              $ref: "#/components/schemas/Metric"
            decoder:
              $ref: "#/components/schemas/Metric"
        memory:
          type: object
          additionalProperties: false
          required:
            - total_mb
            - used_mb
            - used_pct
          properties:
            total_mb:
              $ref: "#/components/schemas/Metric"
            used_mb:
              $ref: "#/components/schemas/Metric"
            used_pct:
              $ref: "#/components/schemas/Metric"
        temperature_c:
          $ref: "#/components/schemas/Metric"
        power_w:
          $ref: "#/components/schemas/Metric"
        power_limit_w:
          $ref: "#/components/schemas/Metric"
        clock_sm_mhz:
          $ref: "#/components/schemas/Metric"
        fan_pct:
          $ref: "#/components/schemas/Metric"
        pstate:
          type: string
        encoder:
          type: object
          additionalProperties: false
          required:
            - sessions
            - avg_fps
          properties:
            sessions:
              $ref: "#/components/schemas/Metric"
            avg_fps:
              $ref: "#/components/schemas/Metric"
        throttle:
          type: object
          additionalProperties: false
          required:
            - active
            - mask
            - reasons
          properties:
            active:
              type: boolean
            mask:
              type: string
            reasons:
              type:
                - array
                - "null"
              items:
                type: string
        capacity:
          $ref: "#/components/schemas/GPUCapacityModel"
        model:
          type: object
          additionalProperties: false
          required:
            - known
            - arch
            - nvenc
            - nvdec
            - memory_mb
            - consumer
            - verified
            - capacity_source
          properties:
            known:
              type: boolean
            arch:
              type: string
            nvenc:
              type: integer
            nvdec:
              type: integer
            memory_mb:
              type: integer
            consumer:
              type: boolean
            verified:
              type: boolean
            capacity_source:
              type: string
              examples:
                - configured
                - model_table
                - default
        committed:
          type: object
          additionalProperties: false
          required:
            - encode_mpix_s
            - decode_mpix_s
            - sessions
          properties:
            encode_mpix_s:
              type: number
            decode_mpix_s:
              type: number
            sessions:
              type: integer
        effective:
          type: object
          additionalProperties: false
          required:
            - encode_pct
            - decode_pct
            - memory_pct
          properties:
            encode_pct:
              type: number
            decode_pct:
              type: number
            memory_pct:
              $ref: "#/components/schemas/Metric"
        session_limit:
          type: integer
          description: 0 = none
        session_limit_source:
          type: string
          examples:
            - configured
            - learned
            - consumer_default
            - none
        headroom:
          $ref: "#/components/schemas/GPUFree"
        full:
          type: boolean
        channels:
          type:
            - array
            - "null"
          items:
            type: object
            additionalProperties: false
            required:
              - channel
              - profile
              - pid
              - memory_mb
              - encode_mpix_s
              - decode_mpix_s
              - sessions
              - since
            properties:
              channel:
                type: string
              profile:
                type: string
              pid:
                type: integer
              memory_mb:
                $ref: "#/components/schemas/Metric"
              encode_mpix_s:
                type: number
              decode_mpix_s:
                type: number
              sessions:
                type: integer
              since:
                $ref: "#/components/schemas/Time"
    GPUCapacity:
      type: object
      additionalProperties: false
      required:
        - node
        - profile
        - hardware
        - uses_gpu
        - enforced
        - pinned_gpu
        - load
        - gpus
        - total
        - note
      properties:
        node:
          type: string
        profile:
          type: string
        hardware:
          type: string
        uses_gpu:
          type: boolean
        enforced:
          type: boolean
        pinned_gpu:
          type:
            - integer
            - "null"
        load:
          $ref: "#/components/schemas/GPULoad"
        gpus:
          type:
            - array
            - "null"
          items:
            type: object
            additionalProperties: false
            required:
              - index
              - uuid
              - name
              - fits
            properties:
              index:
                type: integer
              uuid:
                type: string
              name:
                type: string
              fits:
                type: integer
              reason:
                type: string
        total:
          type: integer
          description: -1 = no estimate
        note:
          type: string
        cluster:
          type: object
          additionalProperties: false
          required:
            - nodes
            - total
          properties:
            total:
              type: integer
            nodes:
              type: array
              items:
                type: object
                additionalProperties: false
                required:
                  - node
                  - state
                  - reported
                  - enforced
                  - fits
                  - best_gpu
                  - gpus
                properties:
                  node:
                    type: string
                  state:
                    type: string
                  reported:
                    type: boolean
                  enforced:
                    type: boolean
                  fits:
                    type: integer
                  best_gpu:
                    type: integer
                  gpus:
                    type: integer
    GPUNodeReport:
      type: object
      description: GPU inventory and free capacity a node publishes to the cluster (WP 6.3).
      additionalProperties: false
      required:
        - monitor
        - enforced
        - gpus
      properties:
        monitor:
          type: string
        enforced:
          type: boolean
        gpus:
          type:
            - array
            - "null"
          items:
            type: object
            additionalProperties: false
            required:
              - index
              - uuid
              - name
              - free
              - encode_mpix_s
              - decode_mpix_s
              - mem_per_channel_mb
              - load
            properties:
              index:
                type: integer
              uuid:
                type: string
              name:
                type: string
              free:
                $ref: "#/components/schemas/GPUFree"
              encode_mpix_s:
                type: number
              decode_mpix_s:
                type: number
              mem_per_channel_mb:
                type: number
              load:
                type: number
        channels:
          type: array
          items:
            type: string
        waiting:
          type: array
          items:
            type: string
    Alarm:
      type: object
      additionalProperties: false
      required:
        - name
        - severity
        - labels
        - message
        - node
        - source
        - since
        - updated
      properties:
        name:
          type: string
          examples:
            - gpu_high_util
            - gpu_mem_high
            - gpu_temp_high
            - gpu_throttling
            - gpu_session_limit
            - gpu_unavailable
            - nvidia_smi_failed
            - gpu_capacity_exhausted
            - transcoder_no_gpu
            - channel_down
            - channel_degraded
            - input_failover
            - all_inputs_failed
            - publisher_disconnected
            - transcoder_crashloop
            - transcoder_fallback
            - output_udp_error
            - track_missing
            - cluster_node_down
            - cluster_no_leader
            - channel_unplaced
            - license_not_serving
            - license_lease_stale
            - license_expiring
            - license_channel_limit
            - license_cluster
        severity:
          type: string
          enum:
            - critical
            - warning
            - info
        labels:
          type: object
          additionalProperties:
            type: string
        message:
          type: string
        node:
          type: string
        source:
          type: string
          examples:
            - gpu
            - cluster
            - channel
            - license
        since:
          $ref: "#/components/schemas/Time"
        updated:
          $ref: "#/components/schemas/Time"
    AlarmEvent:
      type: object
      additionalProperties: false
      required:
        - seq
        - type
        - at
        - alarm
        - notifications
      properties:
        seq:
          type: integer
          minimum: 1
        type:
          type: string
          enum:
            - alarm_raised
            - alarm_cleared
        at:
          $ref: "#/components/schemas/Time"
        alarm:
          $ref: "#/components/schemas/Alarm"
        duration_s:
          type: number
          minimum: 0
          description: How long it was active (clears).
        notifications:
          type: array
          description: What the notifier did with this event, per target (`target` "*" for flap suppression and a full queue). Empty without notifications or while waiting for the hold-down.
          items:
            $ref: "#/components/schemas/NotificationDelivery"
    NotificationDelivery:
      type: object
      additionalProperties: false
      required:
        - target
        - type
        - status
        - at
        - digest
      properties:
        target:
          type: string
        type:
          type: string
          enum:
            - mattermost
            - email
            - ""
        status:
          type: string
          enum:
            - sent
            - failed
            - dropped
            - suppressed
        at:
          $ref: "#/components/schemas/Time"
        digest:
          type: boolean
          description: Sent as part of a summary message.
        error:
          type: string
    Notifications:
      type: object
      additionalProperties: false
      required:
        - node
        - enabled
        - queue_length
        - queue_capacity
        - pending_hold_down
        - hold_down_s
        - digest_threshold
        - digest_window_s
        - rate_limit_per_minute
        - resolved
        - sends_cluster_alarms
        - dropped
        - flaps_suppressed
        - skipped_not_leader
        - unmatched
        - targets
      properties:
        node:
          type: string
        enabled:
          type: boolean
          description: At least one target is configured.
        queue_length:
          type: integer
          minimum: 0
          description: Alarm events and messages waiting.
        queue_capacity:
          type: integer
          minimum: 0
        pending_hold_down:
          type: integer
          minimum: 0
          description: Raises waiting for the hold-down.
        hold_down_s:
          type: number
          minimum: 0
        digest_threshold:
          type: integer
          minimum: 0
        digest_window_s:
          type: number
          minimum: 0
        rate_limit_per_minute:
          type: integer
          minimum: 0
        resolved:
          type: boolean
          description: Resolved messages are sent.
        sends_cluster_alarms:
          type: boolean
          description: This node sends cluster-wide alarms (standalone, or the cluster leader).
        dropped:
          type: integer
          minimum: 0
          description: Alarm events dropped (queue full).
        flaps_suppressed:
          type: integer
          minimum: 0
        skipped_not_leader:
          type: integer
          minimum: 0
        unmatched:
          type: integer
          minimum: 0
          description: Raises no target's filter selected.
        targets:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - name
              - type
              - destination
              - min_severity
              - events
              - labels
              - state
              - last_attempt
              - last_success
              - last_error
              - last_error_at
              - sent
              - failed
              - dropped
              - digests
              - attempts
              - queue_length
              - batched
            properties:
              name:
                type: string
              type:
                type: string
                enum:
                  - mattermost
                  - email
              destination:
                type: string
                description: "Redacted: webhook URL with the key masked, or smtp://host:port (tls) → recipients."
              min_severity:
                type: string
                enum:
                  - info
                  - warning
                  - critical
              events:
                type: array
                items:
                  type: string
              labels:
                type: object
                additionalProperties:
                  type: string
              state:
                type: string
                enum:
                  - ok
                  - failing
                  - idle
              last_attempt:
                $ref: "#/components/schemas/Time"
              last_success:
                $ref: "#/components/schemas/Time"
              last_error:
                type: string
              last_error_at:
                $ref: "#/components/schemas/Time"
              sent:
                type: integer
                minimum: 0
              failed:
                type: integer
                minimum: 0
              dropped:
                type: integer
                minimum: 0
              digests:
                type: integer
                minimum: 0
              attempts:
                type: integer
                minimum: 0
              queue_length:
                type: integer
                minimum: 0
              batched:
                type: integer
                minimum: 0
                description: Events waiting for the next summary.
    Placement:
      type: object
      additionalProperties:
        type: object
        additionalProperties: false
        required:
          - primary
        properties:
          primary:
            type: string
          standby:
            type: string
    ClusterStatus:
      type: object
      additionalProperties: false
      required:
        - enabled
        - mode
        - node
        - leader
        - term
        - config_index
        - strategy
        - max_channels_per_node
        - nodes
        - placement
        - unplaced
        - unplaced_reasons
      properties:
        enabled:
          const: true
        mode:
          const: cluster
        node:
          type: string
        leader:
          type: string
        term:
          type: integer
        config_index:
          type: integer
        strategy:
          type: string
          enum:
            - spread
            - pack
        max_channels_per_node:
          type: integer
          minimum: 0
        nodes:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - id
              - address
              - api_url
              - role
              - suffrage
              - state
              - draining
              - channels
              - standby_channels
              - running
              - capacity
              - last_seen
              - since
              - version
            properties:
              id:
                type: string
              address:
                type: string
              api_url:
                type: string
              srt_address:
                type: string
              role:
                type: string
                enum:
                  - leader
                  - follower
              suffrage:
                type: string
                enum:
                  - voter
                  - nonvoter
                  - removed
              state:
                type: string
                enum:
                  - up
                  - down
                  - joining
              draining:
                type: boolean
              channels:
                type: array
                items:
                  type: string
              standby_channels:
                type: array
                items:
                  type: string
              running:
                type: array
                items:
                  type: string
              capacity:
                type: object
                additionalProperties: false
                required:
                  - cpu_cores
                  - gpus
                  - transcode
                properties:
                  cpu_cores:
                    type: integer
                  gpus:
                    type: array
                    items:
                      type: string
                  transcode:
                    type: boolean
                  gpu:
                    $ref: "#/components/schemas/GPUNodeReport"
              last_seen:
                $ref: "#/components/schemas/Time"
              since:
                $ref: "#/components/schemas/Time"
              version:
                type: string
        placement:
          $ref: "#/components/schemas/Placement"
        unplaced:
          type: array
          items:
            type: string
        unplaced_reasons:
          type: object
          description: 'Channel → why it is unplaced ("no_gpu_capacity: …").'
          additionalProperties:
            type: string
    StandaloneStatus:
      type: object
      additionalProperties: false
      required:
        - enabled
        - mode
        - leader
        - nodes
      properties:
        enabled:
          const: false
        mode:
          const: standalone
        leader:
          type: string
        nodes:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - id
              - address
              - role
              - state
              - channels
              - last_seen
              - version
            properties:
              id:
                type: string
              address:
                type: string
              role:
                const: standalone
              state:
                const: up
              channels:
                type: array
                items:
                  type: string
              last_seen:
                $ref: "#/components/schemas/Time"
              version:
                type: string
    ClusterEvent:
      type: object
      additionalProperties: false
      required:
        - seq
        - at
        - type
      properties:
        seq:
          type: integer
          minimum: 1
        at:
          $ref: "#/components/schemas/Time"
        type:
          type: string
          enum:
            - node_joined
            - node_up
            - node_down
            - node_removed
            - node_drain
            - node_undrain
            - config_changed
            - channel_placed
            - channel_moved
            - channel_promoted
            - standby_assigned
            - channel_unplaced
        node:
          type: string
        channel:
          type: string
        from:
          type: string
        to:
          type: string
        detail:
          type: string
        duration_s:
          type: number
          minimum: 0
    ChannelLocation:
      type: object
      additionalProperties: false
      required:
        - channel
        - configured
        - primary
        - local
        - primary_up
      properties:
        channel:
          type: string
        configured:
          type: boolean
        primary:
          type: string
        standby:
          type: string
        local:
          type: boolean
        primary_up:
          type: boolean
        url:
          type: string
        srt_address:
          type: string
    ClusterNodeRef:
      type: object
      additionalProperties: false
      required:
        - id
        - address
      properties:
        id:
          type: string
        address:
          type: string
          description: Cluster (raft/RPC) address host:port.
    DrainResult:
      type: object
      additionalProperties: false
      required:
        - id
        - draining
      properties:
        id:
          type: string
        draining:
          type: boolean
