openapi: 3.1.0
info:
  title: Freedome media server API
  version: 1.0.0
  summary: Native API of freedome-mediaserver (L3), one per dome installation.
  description: |
    Base path `/api/v1` on the installation (LAN, mDNS `_freedome._tcp`); `/health` and `/ready` are unversioned.
    Conventions: docs/conventions.md (errors RFC 9457, merge-patch, ETag / If-Match, Idempotency-Key, cursor pages).
    Versions before 1.0.0 (0.x previews, 1.0.0-rc.N) are not a stability promise; 1.0.0 freezes the API (M-CT2), then
    only additive changes within v1 (docs/versioning.md). Other transports: GraphQL `/api/v1/graphql`
    (graphql/mediaserver.graphql), WebSocket `/api/v1/ws` (asyncapi/ws.yaml), socket.io `/v1` (asyncapi/socketio.yaml),
    OSC (osc/addresses.yaml), legacy profile (asyncapi/socketio-legacy.yaml, osc/legacy.yaml).
    Support: `support@freedome.nativemind.net`. Commercial use → https://freedome.nativemind.us/commercial.
  license:
    name: NativeMindNONC
    url: https://freedome.nativemind.us/license
servers:
  - url: '{scheme}://{host}:{port}/api/v1'
    description: Media server of a dome installation
    variables:
      scheme:
        default: http
        enum:
          - http
          - https
      host:
        default: freedome.local
      port:
        default: '8080'
tags:
  - name: discovery
    description: Server self-description and ops endpoints (02e §5.1).
  - name: venues
    description: Venues = `.dome` venue manifests + server fields (02e §5.2).
  - name: displays
    description: Displays of a venue, live state, identify, test patterns, power (02e §5.2).
  - name: output
    description: Master brightness / volume / blackout / rotation / lighting (02e §5.2).
  - name: calibration
    description: Calibration state, sidecar files and calibration jobs (02e §5.2).
  - name: jobs
    description: Long-running operations (02e §3.1).
  - name: auth
    description: Sessions, refresh, cloud token exchange, pairing of apps, principal (02e §3.6, §5.1).
  - name: users
    description: Local user accounts (02e §5.1).
  - name: api-keys
    description: API keys for services, kiosks and gateways (02e §3.6).
  - name: shows
    description: Shows (.dome show manifests), playlists, timelines, cues, triggers, matrix, export, imports (02e §5.3).
  - name: playback
    description: Media players, transport, cues, active show, parameters (02e §5.3).
  - name: uploads
    description: Resumable uploads (tus 1.0.0, 02e §5.4).
  - name: assets
    description: Assets, downloads, thumbnails, storage (02e §5.4).
  - name: audio
    description: Audio state, layout, zones, devices, presets, sessions, generator, sources (02e §5.5).
  - name: engines
    description: Engine status mirror, compute backend, restart (02e §5.6).
  - name: gateways
    description: Compatibility gateways and OSC send (02e §5.6, 02d).
  - name: apps
    description: Ecosystem-app payloads (02e §5.6).
  - name: clients
    description: Client devices, commands, telemetry (02e §5.6).
  - name: events
    description: Event history / resume (02e §5.6, §7).
  - name: scenarios
    description: Scenario plugins and sessions (02d §4a UC-SC1–SC5).
  - name: devices
    description: Visual device agents (freedome.device.v1).
  - name: system
    description: Host status, logs, configuration, diagnostics, backup / restore, update, power, cluster nodes (02e §5.1).
paths:
  /info:
    get:
      operationId: getInfo
      tags:
        - discovery
      summary: Server self-description
      description: Product, versions, transports, auth methods, venues summary and capabilities. Anonymous.
      security:
        - {}
      x-scope: anon
      parameters:
        - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: Server info.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/info.schema'
              example:
                product: freedome-mediaserver
                serverVersion: 0.1.0
                contractVersion: 0.1.0
                instanceId: 0192f3a4-5b6c-7d8e-9f01-23456789abcd
                name: Demo dome 8 m
                apiVersions:
                  - v1
                legacyProfile: false
                transports:
                  rest:
                    basePath: /api/v1
                  ws:
                    path: /api/v1/ws
                    subprotocol: freedome.v1
                  osc:
                    udpIn: 7000
                    udpOut: 7001
                    tcp: 7000
                auth:
                  methods:
                    - password
                    - apiKey
                    - pairing
                venues:
                  - id: default
                    title:
                      en: Demo dome 8 m
                    default: true
                    status: ready
                capabilities:
                  engines:
                    - name: visual
                      status: up
                      protocol: freedome.engine.v1
        '304':
          $ref: '#/components/responses/NotModified'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Problem'
  /health:
    servers:
      - url: '{scheme}://{host}:{port}'
        description: Unversioned ops endpoints (no /api/v1 prefix)
        variables:
          scheme:
            default: http
            enum:
              - http
              - https
          host:
            default: freedome.local
          port:
            default: '8080'
    get:
      operationId: getHealth
      tags:
        - discovery
      summary: Liveness
      description: '`ok` while the process serves requests; `degraded` lists failing dependencies (engines down do not make the server unhealthy).'
      security:
        - {}
      x-scope: anon
      responses:
        '200':
          description: Alive.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/health'
              example:
                status: ok
                checks:
                  engine.visual:
                    status: ok
        default:
          $ref: '#/components/responses/Problem'
  /ready:
    servers:
      - url: '{scheme}://{host}:{port}'
        description: Unversioned ops endpoints (no /api/v1 prefix)
        variables:
          scheme:
            default: http
            enum:
              - http
              - https
          host:
            default: freedome.local
          port:
            default: '8080'
    get:
      operationId: getReady
      tags:
        - discovery
      summary: Readiness
      description: 200 when the server accepts API traffic (storage open, venue loaded); 503 problem otherwise.
      security:
        - {}
      x-scope: anon
      responses:
        '200':
          description: Ready.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/health'
              example:
                status: ok
        '503':
          description: Not ready.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/problem.schema'
              example:
                type: https://freedome.nativemind.us/problems/degraded
                title: Not ready
                status: 503
                code: degraded
                detail: venue not loaded
        default:
          $ref: '#/components/responses/Problem'
  /venues:
    get:
      operationId: listVenues
      tags:
        - venues
      summary: List stored venues
      description: Stored venues (one is active per instance; `default` marks the alias target).
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of venues.
          headers:
            Link:
              $ref: '#/components/headers/Link'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page'
              example:
                items:
                  - id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                    revision: 3
                    status: ready
                    default: true
                    activeShowId: null
                    meta:
                      id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                      title:
                        en: FD-VP-F fisheye rig, 6 m dome
                    dome:
                      mode: classical
                      shape: hemisphere
                      radius: 3
                    displays:
                      - id: p1
                        type: projector
                        resolution:
                          - 4096
                          - 2160
                        lens:
                          kind: fisheye
                          fov: 180
                        output:
                          host: node-1
                          connector: HDMI-1
                nextCursor: null
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createVenue
      tags:
        - venues
      summary: Create a venue
      description: Body = `.dome` venue manifest without `format` / `kind`. `meta.id` becomes the venue id (409 if taken).
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/input'
            example:
              meta:
                id: 7a2b3c4d-5e6f-4a71-8b8c-9d0e1f2a3b4c
                title:
                  en: Prism M
              dome:
                mode: quantum
                shape: pyramid
                radius: 0.212
              displays:
                - id: u1
                  type: prism_unit
                  resolution:
                    - 1920
                    - 1080
                  optics:
                    faces: 4
                    faceAngle: 45
                  output:
                    host: node-1
                    connector: HDMI-1
      responses:
        '201':
          description: Created.
          headers:
            Location:
              $ref: '#/components/headers/Location'
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venue.schema'
              example:
                id: 7a2b3c4d-5e6f-4a71-8b8c-9d0e1f2a3b4c
                revision: 0
                status: idle
                meta:
                  id: 7a2b3c4d-5e6f-4a71-8b8c-9d0e1f2a3b4c
                  title:
                    en: Prism M
                dome:
                  mode: quantum
                  shape: pyramid
                  radius: 0.212
                displays:
                  - id: u1
                    type: prism_unit
                    resolution:
                      - 1920
                      - 1080
                    optics:
                      faces: 4
                      faceAngle: 45
                    output:
                      host: node-1
                      connector: HDMI-1
          links:
            getVenue:
              operationId: getVenue
              parameters:
                venueId: $response.body#/id
            listDisplays:
              operationId: listDisplays
              parameters:
                venueId: $response.body#/id
            startCalibrationJob:
              operationId: startCalibrationJob
              parameters:
                venueId: $response.body#/id
            getCalibration:
              operationId: getCalibration
              parameters:
                venueId: $response.body#/id
            getOutput:
              operationId: getOutput
              parameters:
                venueId: $response.body#/id
            patchOutput:
              operationId: patchOutput
              parameters:
                venueId: $response.body#/id
            exportVenue:
              operationId: exportVenue
              parameters:
                venueId: $response.body#/id
            getDisplay:
              operationId: getDisplay
              parameters:
                venueId: $response.body#/id
                displayId: $response.body#/displays/0/id
            setTestPattern:
              operationId: setTestPattern
              parameters:
                venueId: $response.body#/id
                displayId: $response.body#/displays/0/id
            identifyDisplay:
              operationId: identifyDisplay
              parameters:
                venueId: $response.body#/id
                displayId: $response.body#/displays/0/id
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: getVenue
      tags:
        - venues
      summary: Get a venue
      description: Venue = `.dome` venue sections + `id`, `revision`, `status`, `activeShowId`; ETag = revision.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      parameters:
        - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: The venue.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venue.schema'
              example:
                id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                revision: 3
                status: ready
                default: true
                activeShowId: null
                meta:
                  id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                  title:
                    en: FD-VP-F fisheye rig, 6 m dome
                dome:
                  mode: classical
                  shape: hemisphere
                  radius: 3
                displays:
                  - id: p1
                    type: projector
                    resolution:
                      - 4096
                      - 2160
                    lens:
                      kind: fisheye
                      fov: 180
                    output:
                      host: node-1
                      connector: HDMI-1
                calibration:
                  displays:
                    p1:
                      warp:
                        file: calibration/p1/warp.mesh
                        kind: uv-grid
                        size:
                          - 65
                          - 65
        '304':
          $ref: '#/components/responses/NotModified'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putVenue
      tags:
        - venues
      summary: Replace (or create with a client-chosen id) a venue
      description: 'Replace needs `If-Match`; create needs `If-None-Match: *` and the path id equal to `meta.id`.'
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IfMatchOrCreate'
        - $ref: '#/components/parameters/IfNoneMatchCreate'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/input'
            example:
              meta:
                id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                title:
                  en: Fisheye 6 m
              dome:
                mode: classical
                shape: hemisphere
                radius: 3
              displays:
                - id: p1
                  type: projector
                  resolution:
                    - 4096
                    - 2160
                  lens:
                    kind: fisheye
                    fov: 180
                  output:
                    host: node-1
                    connector: HDMI-1
      responses:
        '200':
          description: Replaced.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venue.schema'
              example:
                id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                revision: 4
                status: ready
                meta:
                  id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                  title:
                    en: Fisheye 6 m
                dome:
                  mode: classical
                  shape: hemisphere
                  radius: 3
                displays:
                  - id: p1
                    type: projector
                    resolution:
                      - 4096
                      - 2160
                    lens:
                      kind: fisheye
                      fov: 180
                    output:
                      host: node-1
                      connector: HDMI-1
        '201':
          description: Created (If-None-Match `*`).
          headers:
            Location:
              $ref: '#/components/headers/Location'
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venue.schema'
              example:
                id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                revision: 0
                status: idle
                meta:
                  id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                  title:
                    en: Fisheye 6 m
                dome:
                  mode: classical
                  shape: hemisphere
                  radius: 3
                displays:
                  - id: p1
                    type: projector
                    resolution:
                      - 4096
                      - 2160
                    lens:
                      kind: fisheye
                      fov: 180
                    output:
                      host: node-1
                      connector: HDMI-1
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchVenue
      tags:
        - venues
      summary: Merge-patch a venue
      description: RFC 7396 merge patch over the venue sections; the merged result is validated like a PUT body.
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch'
            example:
              meta:
                title:
                  en: Main dome
      responses:
        '200':
          description: Patched.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venue.schema'
              example:
                id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                revision: 5
                status: ready
                meta:
                  id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                  title:
                    en: Main dome
                dome:
                  mode: classical
                  shape: hemisphere
                  radius: 3
                displays:
                  - id: p1
                    type: projector
                    resolution:
                      - 4096
                      - 2160
                    lens:
                      kind: fisheye
                      fov: 180
                    output:
                      host: node-1
                      connector: HDMI-1
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteVenue
      tags:
        - venues
      summary: Delete a venue
      description: Refused with 409 while the venue is playing or is the default venue.
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/export:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: exportVenue
      tags:
        - venues
      summary: Export the venue as a .dome
      description: '`kind: venue` `.dome` (01b v0.2) incl. calibration sidecars; reference-writer layout (mimetype first, deterministic).'
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: The .dome file.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/vnd.nativemind.freedome.dome+zip:
              schema:
                type: string
                contentMediaType: application/vnd.nativemind.freedome.dome+zip
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/displays:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listDisplays
      tags:
        - displays
      summary: List the venue's displays with live state
      description: Configuration (`.dome` entry) and live state of every display of the venue.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of displays.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-2'
              example:
                items:
                  - id: p1
                    venueId: default
                    revision: 3
                    config:
                      id: p1
                      type: projector
                      resolution:
                        - 4096
                        - 2160
                      lens:
                        kind: fisheye
                        fov: 180
                    state:
                      online: true
                      power: 'on'
                      signal: true
                      pattern: null
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/displays/{displayId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/DisplayId'
    get:
      operationId: getDisplay
      tags:
        - displays
      summary: Get a display (config + live state)
      description: Configuration (`.dome` entry) and live state; ETag = venue revision.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      responses:
        '200':
          description: The display.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/display.schema'
              example:
                id: u1
                venueId: default
                revision: 2
                config:
                  id: u1
                  type: prism_unit
                  lens:
                    kind: pyramid
                  optics:
                    faces: 4
                    faceAngle: 45
                    activeRegion:
                      - 0.21875
                      - 0
                      - 0.5625
                      - 1
                state:
                  online: true
                  power: 'on'
                  signal: true
                  temperatureC: null
                  pattern:
                    kind: crosshair
                    face: 0
                  capabilities:
                    - identify
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchDisplay
      tags:
        - displays
      summary: Merge-patch the display configuration
      description: Patches `config` (the `.dome` display entry); bumps the venue revision. `If-Match` = venue ETag.
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-2'
            example:
              lens:
                fov: 185
      responses:
        '200':
          description: Patched.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/display.schema'
              example:
                id: p1
                venueId: default
                revision: 4
                config:
                  id: p1
                  type: projector
                  lens:
                    kind: fisheye
                    fov: 185
                state:
                  online: true
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/displays/{displayId}/identify:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/DisplayId'
    post:
      operationId: identifyDisplay
      tags:
        - displays
      summary: Flash the display id overlay
      description: Shows the display id over the output for `durationS` seconds (default 5).
      security:
        - bearer:
            - displays:control
        - apiKey:
            - displays:control
      x-scope: displays:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/identify'
            example:
              durationS: 5
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/displays/{displayId}/pattern:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/DisplayId'
    put:
      operationId: setTestPattern
      tags:
        - displays
      summary: Show a test pattern (or `off`)
      description: Engine `freedome_pattern_*`; result also published as event `display.pattern` on `venues/{v}/displays/{d}`.
      security:
        - bearer:
            - displays:control
        - apiKey:
            - displays:control
      x-scope: displays:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/testPattern'
            example:
              kind: grid
              params:
                spacingDeg: 10
                lineWidthPx: 2
                colour: '#ffffff'
      responses:
        '200':
          description: Pattern shown; current display state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/state'
              example:
                online: true
                power: 'on'
                signal: true
                pattern:
                  kind: grid
                  params:
                    spacingDeg: 10
                    lineWidthPx: 2
                    colour: '#ffffff'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/displays/{displayId}/power:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/DisplayId'
    post:
      operationId: setDisplayPower
      tags:
        - displays
      summary: Switch display power / shutter
      description: Only what `state.capabilities` lists (PJLink, device agent); otherwise 422 `validation-failed`.
      security:
        - bearer:
            - displays:control
        - apiKey:
            - displays:control
      x-scope: displays:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/power'
            example:
              'on': true
              shutter: open
      responses:
        '200':
          description: Command sent; current display state (power may pass through warming).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/state'
              example:
                online: true
                power: warming
                shutter: open
                capabilities:
                  - power
                  - shutter
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/output:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: getOutput
      tags:
        - output
      summary: Get the venue output state
      description: Current master brightness, volume, blackout, rotation, lighting and NDI output.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: Output state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/output.schema'
              example:
                brightness: 0.8
                volume: 0.5
                gainDb: -6
                mute: false
                blackout: false
                rotation: 0
                lighting: true
                ndi:
                  enabled: false
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchOutput
      tags:
        - output
      summary: Change output (brightness, volume, blackout …)
      description: Last writer wins (playback class, 02e §3.5); published as `output.changed` on `venues/{v}/output`.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-3'
            example:
              brightness: 0.25
              blackout: false
      responses:
        '200':
          description: New output state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/output.schema'
              example:
                brightness: 0.25
                volume: 0.5
                blackout: false
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/calibration:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: getCalibration
      tags:
        - calibration
      summary: Get the venue calibration (per display warp / blend / colour / report)
      description: The `.dome` `calibration` block of the venue; ETag = venue revision.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      responses:
        '200':
          description: '`.dome` `calibration` block; file paths are readable through getCalibrationFile.'
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/calibration'
              example:
                displays:
                  p1:
                    warp:
                      file: calibration/p1/warp.mesh
                      kind: uv-grid
                      size:
                        - 65
                        - 65
                    blend:
                      file: calibration/p1/blend.png
                      bitDepth: 16
                      gamma: 2.2
                    report:
                      method: camera-homography
                      meanError: 0.4
                      maxError: 1.2
                      unit: px
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/calibration/displays/{displayId}/{file}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/DisplayId'
      - $ref: '#/components/parameters/CalibrationFile'
    get:
      operationId: getCalibrationFile
      tags:
        - calibration
      summary: Download a calibration sidecar
      description: Binary sidecar of one display; media type by `file`.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      responses:
        '200':
          description: warp → FDMESH (01b §5.1), blend → 16-bit PNG, colour → .cube.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/vnd.nativemind.freedome.fdmesh:
              schema:
                type: string
                contentMediaType: application/vnd.nativemind.freedome.fdmesh
            image/png:
              schema:
                type: string
                contentMediaType: image/png
            text/plain:
              schema:
                type: string
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putCalibrationFile
      tags:
        - calibration
      summary: Upload / import a calibration sidecar
      description: Validated (FDMESH grammar + size, PNG 16-bit grey, .cube 33³ / 65³); updates the venue calibration (`revision`++). `If-Match` = venue ETag.
      security:
        - bearer:
            - calibration:run
        - apiKey:
            - calibration:run
      x-scope: calibration:run
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/vnd.nativemind.freedome.fdmesh:
            schema:
              type: string
              contentMediaType: application/vnd.nativemind.freedome.fdmesh
          image/png:
            schema:
              type: string
              contentMediaType: image/png
          text/plain:
            schema:
              type: string
      responses:
        '200':
          description: Stored; the new calibration block.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/calibration'
              example:
                displays:
                  p1:
                    warp:
                      file: calibration/p1/warp.mesh
                      kind: uv-grid
                      size:
                        - 65
                        - 65
        '412':
          $ref: '#/components/responses/Problem'
        '415':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/calibration/jobs:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listCalibrationJobs
      tags:
        - calibration
      summary: List the venue's calibration jobs
      description: Newest first; `status` filters (e.g. running jobs for a "resume" dialog).
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - name: status
          in: query
          description: Job status filter.
          schema:
            $ref: '#/components/schemas/status'
      responses:
        '200':
          description: Page of calibration jobs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-3'
              example:
                items: []
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: startCalibrationJob
      tags:
        - calibration
      summary: Start a calibration job
      description: Visual kinds need `calibration:run`; audio kinds (`audio-room`, `audio-speaker-check`) need `audio:calibrate`. 202 + Location → getCalibrationJob.
      security:
        - bearer:
            - calibration:run
        - apiKey:
            - calibration:run
      x-scope: calibration:run
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/start'
            example:
              kind: visual-camera
              displays:
                - p1
              options:
                grid: 17
      responses:
        '202':
          description: Accepted.
          headers:
            Location:
              $ref: '#/components/headers/Location'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/calibration-job.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789abd0
                venueId: default
                kind: visual-camera
                status: queued
                progress: 0
                displays:
                  - p1
                steps:
                  - id: pattern-p1
                    displayId: p1
                    status: pending
                  - id: solve
                    status: pending
                currentStep: null
                awaiting: null
                result: null
                createdAt: '2026-10-19T12:00:00.000Z'
          links:
            getCalibrationJob:
              operationId: getCalibrationJob
              parameters:
                venueId: $response.body#/venueId
                jobId: $response.body#/id
            cancelCalibrationJob:
              operationId: cancelCalibrationJob
              parameters:
                venueId: $response.body#/venueId
                jobId: $response.body#/id
            getJob:
              operationId: getJob
              parameters:
                jobId: $response.body#/id
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/calibration/jobs/{jobId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/JobId'
    get:
      operationId: getCalibrationJob
      tags:
        - calibration
      summary: Calibration job state (steps, awaited input, residual errors)
      description: Steps, the input awaited from the operator / camera, residual errors and the proposed result.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      responses:
        '200':
          description: Job state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/calibration-job.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789abd0
                venueId: default
                kind: visual-camera
                status: awaiting_input
                progress: 0.3
                displays:
                  - p1
                currentStep: capture-p1-grid
                steps:
                  - id: pattern-p1
                    displayId: p1
                    status: done
                  - id: capture-p1-grid
                    displayId: p1
                    status: awaiting_input
                  - id: solve
                    status: pending
                awaiting:
                  kind: camera-capture
                  stepId: capture-p1-grid
                  displayId: p1
                  pattern:
                    kind: grid
                  instructions:
                    en: Photograph the dome from the sweet spot
                result: null
                createdAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/calibration/jobs/{jobId}/captures:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/JobId'
    post:
      operationId: submitCalibrationCapture
      tags:
        - calibration
      summary: Submit a camera frame for the awaited step
      description: JSON with a tus `uploadId` or multipart/form-data with an `image` part (≤ 32 MB) for the awaited step; 409 when no capture is awaited.
      security:
        - bearer:
            - calibration:run
        - apiKey:
            - calibration:run
      x-scope: calibration:run
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/capture'
            example:
              uploadId: 0192f3a4-5b6c-7d8e-9f01-23456789abd2
              stepId: capture-p1-grid
          multipart/form-data:
            schema:
              type: object
              required:
                - image
              properties:
                image:
                  type: string
                  contentMediaType: image/jpeg
                  description: JPEG / PNG frame, ≤ 32 MB
                stepId:
                  $ref: '#/components/schemas/docId'
      responses:
        '200':
          description: Capture accepted; job state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/calibration-job.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789abd0
                venueId: default
                kind: visual-camera
                status: running
                progress: 0.6
                steps:
                  - id: capture-p1-grid
                    displayId: p1
                    status: done
                  - id: solve
                    status: running
                currentStep: solve
                awaiting: null
                createdAt: '2026-10-19T12:00:00.000Z'
        '409':
          $ref: '#/components/responses/Problem'
        '413':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/calibration/jobs/{jobId}/apply:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/JobId'
    post:
      operationId: applyCalibrationJob
      tags:
        - calibration
      summary: Apply the solved calibration to the venue
      description: Writes `result` into the venue calibration (`revision`++); 409 unless the job `succeeded`.
      security:
        - bearer:
            - calibration:run
        - apiKey:
            - calibration:run
      x-scope: calibration:run
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Applied; the new venue calibration.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/calibration'
              example:
                displays:
                  p1:
                    warp:
                      file: calibration/p1/warp.mesh
                      kind: uv-grid
                      size:
                        - 65
                        - 65
                    report:
                      method: camera-homography
                      meanError: 0.4
                      maxError: 1.2
                      unit: px
        '409':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/calibration/jobs/{jobId}/cancel:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/JobId'
    post:
      operationId: cancelCalibrationJob
      tags:
        - calibration
      summary: Cancel a calibration job
      description: Stops the job and restores the pre-job patterns; the venue calibration is unchanged.
      security:
        - bearer:
            - calibration:run
        - apiKey:
            - calibration:run
      x-scope: calibration:run
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Cancelled (or already finished); job state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/calibration-job.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789abd0
                venueId: default
                kind: visual-camera
                status: cancelled
                steps: []
                createdAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /metrics:
    servers:
      - url: '{scheme}://{host}:{port}'
        description: Unversioned ops endpoints (no /api/v1 prefix)
        variables:
          scheme:
            default: http
            enum:
              - http
              - https
          host:
            default: freedome.local
          port:
            default: '8080'
    get:
      operationId: getMetrics
      tags:
        - discovery
      summary: Prometheus metrics
      description: Prometheus text exposition 0.0.4; metric names are listed in docs/metrics.md. Anonymous by default on the LAN listener; installations may restrict it to `system:read` (config `[metrics] require_auth`).
      security:
        - {}
        - bearer:
            - system:read
        - apiKey:
            - system:read
      x-scope: anon
      responses:
        '200':
          description: Metrics.
          content:
            text/plain:
              schema:
                type: string
              example: |
                # TYPE freedome_http_requests_total counter
                freedome_http_requests_total{route="/api/v1/info",status="200"} 42
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /openapi.json:
    get:
      operationId: getOpenApi
      tags:
        - discovery
      summary: This API description (OpenAPI 3.1 JSON)
      description: The bundled contract document the server implements, with `servers` set to this instance. Swagger UI at `/api/v1/docs`.
      security:
        - {}
      x-scope: anon
      responses:
        '200':
          description: OpenAPI document.
          content:
            application/json:
              schema:
                type: object
                required:
                  - openapi
                  - info
                  - paths
                properties:
                  openapi:
                    type: string
                  info:
                    type: object
                  paths:
                    type: object
              example:
                openapi: 3.1.0
                info:
                  title: Freedome media server API
                  version: 1.0.0
                paths: {}
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/activate:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    post:
      operationId: activateVenue
      tags:
        - venues
      summary: Make this the active venue
      description: 'One active venue per instance (RC6): pushes displays + applied calibration to the engines; the alias `default` then resolves to it. Refused (409) while another venue is playing.'
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Active venue.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venue.schema'
              example:
                id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                revision: 3
                status: ready
                default: true
                activeShowId: null
                meta:
                  id: 6f1a2b3c-4d5e-4f60-8a7b-8c9d0e1f2a3b
                  title:
                    en: Demo dome 8 m
                dome:
                  mode: classical
                  shape: hemisphere
                  radius: 4
                displays:
                  - id: p1
                    type: projector
                    lens:
                      kind: fisheye
                      fov: 180
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/session:
    post:
      operationId: createSession
      tags:
        - auth
      summary: Sign in with a local account
      description: '`{username, password}` → access JWT (15 min) + rotating refresh token. Rate limited per IP and username.'
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/sessionRequest'
            example:
              username: operator
              password: correct-horse-battery
              client:
                app: freedome_manager
                appVersion: 4.0.0
      responses:
        '201':
          description: Session created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tokens'
              example:
                accessToken: example.access-token.not-a-secret
                tokenType: Bearer
                expiresIn: 900
                refreshToken: example-refresh-token-not-a-secret
                refreshExpiresIn: 2592000
                scopes:
                  - info:read
                  - events:subscribe
                  - playback:read
                  - content:read
                  - playback:control
                principal:
                  subject: 0192f3a4-5b6c-7d8e-9f01-23456789abcd
                  kind: user
                  name: operator
                  role: operator
                  scopes:
                    - info:read
                    - playback:control
        '401':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteSession
      tags:
        - auth
      summary: Sign out (revoke the current session and its refresh family)
      description: Revokes the session of the presented access token; emits `session.revoked` on `auth/sessions`.
      security:
        - bearer: []
      x-scope: any
      x-concurrency: none
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/refresh:
    post:
      operationId: refreshSession
      tags:
        - auth
      summary: Rotate the refresh token
      description: Returns a new token pair; presenting an already rotated refresh token revokes every session of that family (401).
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/refreshRequest'
            example:
              refreshToken: example-refresh-token-not-a-secret
      responses:
        '200':
          description: New token pair.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tokens'
              example:
                accessToken: example.access-token.not-a-secret
                tokenType: Bearer
                expiresIn: 900
                refreshToken: example-refresh-token-not-a-secret
                refreshExpiresIn: 2592000
                scopes:
                  - info:read
                  - events:subscribe
                  - playback:read
                  - content:read
                  - playback:control
                principal:
                  subject: 0192f3a4-5b6c-7d8e-9f01-23456789abcd
                  kind: user
                  name: operator
                  role: operator
                  scopes:
                    - info:read
                    - playback:control
        '401':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/token-exchange:
    post:
      operationId: exchangeCloudToken
      tags:
        - auth
      summary: Exchange a cloud (L7) token for a local session
      description: Linked installations only — the L7 JWT is verified offline with the cached JWKS of freedome.nativemind.us and its org role is mapped to a local role. Unlinked → 401 `unauthenticated` "installation not linked".
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/tokenExchangeRequest'
            example:
              subjectToken: example.cloud-jwt.not-a-secret
              client:
                app: freedome_manager
      responses:
        '200':
          description: Local token pair.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tokens'
              example:
                accessToken: example.access-token.not-a-secret
                tokenType: Bearer
                expiresIn: 900
                refreshToken: example-refresh-token-not-a-secret
                refreshExpiresIn: 2592000
                scopes:
                  - info:read
                  - events:subscribe
                  - playback:read
                  - content:read
                  - playback:control
                principal:
                  subject: 0192f3a4-5b6c-7d8e-9f01-23456789abcd
                  kind: user
                  name: operator
                  role: operator
                  scopes:
                    - info:read
                    - playback:control
        '401':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/pairings:
    post:
      operationId: createPairing
      tags:
        - auth
      summary: Ask to pair an ecosystem app (guest token)
      description: Returns a 6-digit code to show to the operator (also as QR `freedome://pair?host=…&code=…`); the app polls `getPairing` until approved / denied / expired (5 min).
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/pairingRequest'
            example:
              app: net.nativemind.starveda
              deviceName: Anna iPhone
      responses:
        '201':
          description: Pairing pending.
          headers:
            Location:
              $ref: '#/components/headers/Location'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pairing'
              example:
                pairingId: 0192f3a4-5b6c-7d8e-9f01-23456789ab01
                status: pending
                code: '482913'
                qr: freedome://pair?host=freedome.local&code=482913
                app: net.nativemind.starveda
                deviceName: Anna iPhone
                expiresAt: '2026-10-19T12:05:00.000Z'
        '422':
          $ref: '#/components/responses/Problem'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        default:
          $ref: '#/components/responses/Problem'
    get:
      operationId: listPairings
      tags:
        - auth
      summary: List pending pairings (operator view)
      description: Pending and recently decided pairings, for the approval dialog in Manager / on the dome.
      security:
        - bearer:
            - users:manage
        - apiKey:
            - users:manage
      x-scope: users:manage
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of pairings.
          content:
            application/json:
              schema:
                type: object
                required:
                  - items
                  - nextCursor
                properties:
                  items:
                    type: array
                    items:
                      $ref: '#/components/schemas/pairing'
                  nextCursor:
                    type:
                      - string
                      - 'null'
              example:
                items:
                  - pairingId: 0192f3a4-5b6c-7d8e-9f01-23456789ab01
                    status: pending
                    code: '482913'
                    app: net.nativemind.starveda
                    deviceName: Anna iPhone
                    expiresAt: '2026-10-19T12:05:00.000Z'
                nextCursor: null
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/pairings/{pairingId}:
    parameters:
      - $ref: '#/components/parameters/PairingId'
    get:
      operationId: getPairing
      tags:
        - auth
      summary: Poll a pairing
      description: Anonymous poll by the requesting app (the unguessable id is the capability). After approval the first poll returns `tokens` once.
      security:
        - {}
      x-scope: anon
      responses:
        '200':
          description: Pairing state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pairing'
              example:
                pairingId: 0192f3a4-5b6c-7d8e-9f01-23456789ab01
                status: approved
                role: guest
                expiresAt: '2026-10-19T12:05:00.000Z'
                tokens:
                  accessToken: example.guest-token.not-a-secret
                  tokenType: Bearer
                  expiresIn: 2592000
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/pairings/{pairingId}/approve:
    parameters:
      - $ref: '#/components/parameters/PairingId'
    post:
      operationId: approvePairing
      tags:
        - auth
      summary: Approve (or deny) a pairing
      description: Operator decision; `approve:false` denies. The granted role never exceeds the approver's.
      security:
        - bearer:
            - users:manage
        - apiKey:
            - users:manage
      x-scope: users:manage
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/pairingApproval'
            example:
              approve: true
              role: guest
              ttlSec: 2592000
      responses:
        '200':
          description: Decided.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pairing'
              example:
                pairingId: 0192f3a4-5b6c-7d8e-9f01-23456789ab01
                status: approved
                role: guest
                app: net.nativemind.starveda
                expiresAt: '2026-10-19T12:05:00.000Z'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/me:
    get:
      operationId: getMe
      tags:
        - auth
      summary: Current principal and scopes
      description: Who is calling (user, API key, pairing, cloud) and the effective scopes.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      responses:
        '200':
          description: Principal.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/principal'
              example:
                subject: ci000001
                kind: apiKey
                name: CI conformance
                role: admin
                scopes:
                  - info:read
                  - venues:write
                  - calibration:run
                origin: rest
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /users:
    get:
      operationId: listUsers
      tags:
        - users
      summary: List local users
      description: Cursor page of local accounts; `?q=` matches username / display name.
      security:
        - bearer:
            - users:manage
        - apiKey:
            - users:manage
      x-scope: users:manage
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Query'
      responses:
        '200':
          description: Page of users.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-4'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789ac01
                    username: operator
                    displayName: Night operator
                    role: operator
                    revision: 1
                    createdAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createUser
      tags:
        - users
      summary: Create a local user
      description: The new user's role cannot exceed the caller's.
      security:
        - bearer:
            - users:manage
        - apiKey:
            - users:manage
      x-scope: users:manage
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/input-2'
            example:
              username: operator
              displayName: Night operator
              password: correct-horse-battery
              role: operator
      responses:
        '201':
          description: Created.
          headers:
            Location:
              $ref: '#/components/headers/Location'
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/user.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789ac01
                username: operator
                displayName: Night operator
                role: operator
                revision: 1
                createdAt: '2026-10-19T12:00:00.000Z'
        '403':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /users/{userId}:
    parameters:
      - $ref: '#/components/parameters/UserId'
    get:
      operationId: getUser
      tags:
        - users
      summary: Get a user
      description: One local account.
      security:
        - bearer:
            - users:manage
        - apiKey:
            - users:manage
      x-scope: users:manage
      responses:
        '200':
          description: The user.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/user.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789ac01
                username: operator
                role: operator
                revision: 1
                createdAt: '2026-10-19T12:00:00.000Z'
                lastLoginAt: '2026-10-19T18:30:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchUser
      tags:
        - users
      summary: Merge-patch a user
      description: Display name, password, role, disabled. A password change revokes the user's sessions.
      security:
        - bearer:
            - users:manage
        - apiKey:
            - users:manage
      x-scope: users:manage
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-4'
            example:
              disabled: true
      responses:
        '200':
          description: Patched.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/user.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789ac01
                username: operator
                role: operator
                disabled: true
                revision: 2
                createdAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteUser
      tags:
        - users
      summary: Delete a user
      description: Revokes the user's sessions. The last superadmin cannot be deleted (409).
      security:
        - bearer:
            - users:manage
        - apiKey:
            - users:manage
      x-scope: users:manage
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /api-keys:
    get:
      operationId: listApiKeys
      tags:
        - api-keys
      summary: List API keys
      description: Metadata only — the secret is never returned after creation. Revoked keys stay listed with `revokedAt` (audit).
      security:
        - bearer:
            - keys:manage
        - apiKey:
            - keys:manage
      x-scope: keys:manage
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of keys.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-5'
              example:
                items:
                  - id: ci000001
                    name: CI conformance
                    role: admin
                    scopes:
                      - info:read
                      - venues:write
                    prefix: fdk_ci000001
                    createdAt: '2026-10-19T12:00:00.000Z'
                    lastUsedAt: null
                nextCursor: null
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createApiKey
      tags:
        - api-keys
      summary: Create an API key (token shown once)
      description: The key's role cannot exceed the caller's; `scopes` may narrow the role.
      security:
        - bearer:
            - keys:manage
        - apiKey:
            - keys:manage
      x-scope: keys:manage
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/input-3'
            example:
              name: Lobby kiosk
              role: guest
      responses:
        '201':
          description: Created; `token` is shown only in this response.
          headers:
            Location:
              $ref: '#/components/headers/Location'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/created'
              example:
                key:
                  id: k0192abc
                  name: Lobby kiosk
                  role: guest
                  scopes:
                    - info:read
                    - events:subscribe
                    - playback:read
                    - content:read
                  prefix: fdk_k0192abc
                  createdAt: '2026-10-19T12:00:00.000Z'
                token: fdk_k0192abc_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE0
        '403':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /api-keys/{keyId}:
    parameters:
      - $ref: '#/components/parameters/KeyId'
    get:
      operationId: getApiKey
      tags:
        - api-keys
      summary: Get API key metadata
      description: Metadata of one key. A revoked key → 404 (it is still listed by listApiKeys with `revokedAt`).
      security:
        - bearer:
            - keys:manage
        - apiKey:
            - keys:manage
      x-scope: keys:manage
      responses:
        '200':
          description: The key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/api-key.schema'
              example:
                id: ci000001
                name: CI conformance
                role: admin
                scopes:
                  - info:read
                createdAt: '2026-10-19T12:00:00.000Z'
                revokedAt: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteApiKey
      tags:
        - api-keys
      summary: Revoke an API key
      description: Immediate; cached verifications are dropped. Idempotent — revoking an already revoked key → 204; an unknown id → 404.
      security:
        - bearer:
            - keys:manage
        - apiKey:
            - keys:manage
      x-scope: keys:manage
      x-concurrency: none
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /system:
    get:
      operationId: getSystem
      tags:
        - system
      summary: Host and service status
      description: Host, OS, CPU / GPU, temperatures, disks, uptime, component versions.
      security:
        - bearer:
            - system:read
        - apiKey:
            - system:read
      x-scope: system:read
      responses:
        '200':
          description: Status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/system.schema'
              example:
                host:
                  hostname: dome-8m
                  os: Debian 12
                  kernel: 6.1.0-26-amd64
                  arch: x86_64
                  cpu:
                    model: Ryzen 9 7950X
                    cores: 16
                    loadPercent: 23.5
                  memory:
                    totalBytes: 68719476736
                    usedBytes: 9126805504
                  gpus:
                    - name: RTX 4080
                      driver: '550.120'
                      loadPercent: 41
                temperatures:
                  cpu: 61.5
                  gpu0: 58
                disks:
                  - mount: /var/lib/freedome
                    totalBytes: 4000787030016
                    freeBytes: 3120000000000
                uptimeSec: 86400
                serviceUptimeSec: 3600
                versions:
                  freedome-mediaserver: 1.0.0
                  freedome_contract: 1.0.0
                  freedome-engined: 1.0.0
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /system/logs:
    get:
      operationId: getLogs
      tags:
        - system
      summary: Server log (ring buffer)
      description: Newest first; `level` = minimum level. Live tail = topic `system/logs`.
      security:
        - bearer:
            - system:read
        - apiKey:
            - system:read
      x-scope: system:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Since'
        - name: level
          in: query
          description: Minimum level.
          schema:
            $ref: '#/components/schemas/logLevel'
      responses:
        '200':
          description: Page of log entries.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/logPage'
              example:
                items:
                  - seq: 1201
                    ts: '2026-10-19T12:00:00.123Z'
                    level: warn
                    target: fd_adapters::engine_grpc
                    message: engine reconnect
                    fields:
                      attempt: 2
                nextCursor: '1200'
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: clearLogs
      tags:
        - system
      summary: Clear the log ring buffer
      description: Files written by the OS journal are not touched.
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      x-concurrency: none
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /system/config:
    get:
      operationId: getConfig
      tags:
        - system
      summary: Effective configuration (secrets redacted)
      description: Secrets are always `"***"` and listed in `redacted`.
      security:
        - bearer:
            - system:read
        - apiKey:
            - system:read
      x-scope: system:read
      responses:
        '200':
          description: Configuration.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/config'
              example:
                revision: 4
                values:
                  http:
                    listen: 0.0.0.0:8080
                    base_path: ''
                  osc:
                    udp_in: 7000
                    udp_out: 7001
                  cloud:
                    link_token: '***'
                  legacy:
                    enabled: true
                redacted:
                  - /cloud/link_token
                restartRequired:
                  - /http/listen
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putConfig
      tags:
        - system
      summary: Replace the configuration (and reload)
      description: Validated, written atomically, hot-reloaded where possible; `"***"` keeps the stored secret.
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/configInput'
            example:
              values:
                http:
                  listen: 0.0.0.0:8080
                  base_path: ''
                osc:
                  udp_in: 7000
                  udp_out: 7001
                cloud:
                  link_token: '***'
                legacy:
                  enabled: false
      responses:
        '200':
          description: Applied.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/config'
              example:
                revision: 4
                values:
                  http:
                    listen: 0.0.0.0:8080
                    base_path: ''
                  osc:
                    udp_in: 7000
                    udp_out: 7001
                  cloud:
                    link_token: '***'
                  legacy:
                    enabled: true
                redacted:
                  - /cloud/link_token
                restartRequired:
                  - /http/listen
        '403':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /system/diagnostics:
    post:
      operationId: runDiagnostics
      tags:
        - system
      summary: Run diagnostics (job)
      description: Checks engines, displays, audio, network, storage, sync, gateways; report in the job `result`.
      security:
        - bearer:
            - system:read
        - apiKey:
            - system:read
      x-scope: system:read
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/diagnosticsRequest'
            example:
              checks:
                - engines
                - displays
                - storage
      responses:
        '202':
          $ref: '#/components/responses/AcceptedJob'
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /system/backup:
    post:
      operationId: createBackup
      tags:
        - system
      summary: Create a backup (job → downloadable archive)
      description: The finished job's `links.resource` points to the archive download.
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/backupRequest'
            example:
              include:
                - config
                - database
                - venues
                - shows
              note: before update
      responses:
        '202':
          $ref: '#/components/responses/AcceptedJob'
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /system/restore:
    post:
      operationId: restoreBackup
      tags:
        - system
      summary: Restore a backup (job; service restarts)
      description: The archive is uploaded first with tus (`purpose=restore`).
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/restoreRequest'
            example:
              uploadId: 0192f3a4-5b6c-7d8e-9f01-23456789ad01
              confirm: true
      responses:
        '202':
          $ref: '#/components/responses/AcceptedJob'
        '403':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /system/update:
    get:
      operationId: getUpdate
      tags:
        - system
      summary: Update status
      description: Installed and available versions per component.
      security:
        - bearer:
            - system:read
        - apiKey:
            - system:read
      x-scope: system:read
      responses:
        '200':
          description: Update status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/updateStatus'
              example:
                status: available
                current:
                  freedome-mediaserver: 1.0.0
                available:
                  freedome-mediaserver: 1.0.1
                checkedAt: '2026-10-19T12:00:00.000Z'
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: startUpdate
      tags:
        - system
      summary: Start a package / firmware update (job)
      description: From the configured channel, a version, or an uploaded offline package.
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/updateRequest'
            example:
              channel: stable
              version: 1.0.1
      responses:
        '202':
          $ref: '#/components/responses/AcceptedJob'
        '403':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /system/reboot:
    post:
      operationId: rebootSystem
      tags:
        - system
      summary: Reboot the host
      description: Requires `{confirm:true}`; disabled unless allowed in config (403 otherwise). Emits `system.health` before going down.
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/power-2'
            example:
              confirm: true
              delaySec: 5
              reason: driver update
      responses:
        '202':
          description: Scheduled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/powerAccepted'
              example:
                action: reboot
                at: '2026-10-19T12:00:05.000Z'
        '403':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /system/shutdown:
    post:
      operationId: shutdownSystem
      tags:
        - system
      summary: Shut the host down
      description: Requires `{confirm:true}`; disabled unless allowed in config (403 otherwise).
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/power-2'
            example:
              confirm: true
              delaySec: 30
      responses:
        '202':
          description: Scheduled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/powerAccepted'
              example:
                action: shutdown
                at: '2026-10-19T12:00:30.000Z'
        '403':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /nodes:
    get:
      operationId: listNodes
      tags:
        - system
      summary: Cluster nodes and their clocks
      description: This server and the engine hosts it drives, with PTP / NTP clock status (multi-node sync).
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of nodes.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/nodePage'
              example:
                items:
                  - id: node-1
                    name: dome-8m
                    role: primary
                    status: up
                    engines:
                      - visual
                      - anantadigital
                    clock:
                      source: ptp
                      offsetMs: 0.02
                      jitterMs: 0.01
                      masterId: node-1
                      locked: true
                nextCursor: null
        default:
          $ref: '#/components/responses/Problem'
  /nodes/{nodeId}:
    parameters:
      - $ref: '#/components/parameters/NodeId'
    get:
      operationId: getNode
      tags:
        - system
      summary: One cluster node
      description: Node status and clock.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      responses:
        '200':
          description: The node.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/node'
              example:
                id: node-2
                role: engine
                status: up
                address: 10.0.0.12:50061
                engines:
                  - visual
                clock:
                  source: ptp
                  offsetMs: 0.08
                  locked: true
                lastSeen: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /shows:
    get:
      operationId: listShows
      tags:
        - shows
      summary: List shows
      description: Summaries (no asset / show bodies); `?q=` matches title and tags, `?tag=` filters.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Query'
        - $ref: '#/components/parameters/Sort'
        - name: tag
          in: query
          description: Tag filter.
          schema:
            type: string
            maxLength: 64
      responses:
        '200':
          description: Page of show summaries.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-6'
              example:
                items:
                  - id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                    revision: 2
                    meta:
                      id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                      title:
                        en: Intro show
                    status: ready
                    assetCount: 1
                    duration: 600
                    updatedAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createShow
      tags:
        - shows
      summary: Create a show
      description: Body = `.dome` show manifest without `format` / `kind`; id = `meta.id`. 409 if it exists (use putShow to replace).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/input-4'
            example:
              meta:
                id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                title:
                  en: Intro show
              assets:
                - id: intro
                  src: asset:0192f3a4-5b6c-7d8e-9f01-23456789ae01
                  sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                  size: 734003200
                  type: video
                  mapping:
                    projection: domemaster
                    fov: 180
                  duration: 600
              show:
                playlists:
                  - id: main
                    items:
                      - asset: intro
                cues:
                  - id: dim
                    at:
                      trigger: go
                    actions:
                      - do: setBrightness
                        target: main
                        args:
                          value: 0.7
                triggers:
                  - id: go
                    source: osc
                    match: /dome/go
                startup:
                  playlist: main
      responses:
        '201':
          description: Created.
          headers:
            Location:
              $ref: '#/components/headers/Location'
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/show.schema'
              example:
                id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                revision: 1
                status: ready
                meta:
                  id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                  title:
                    en: Intro show
                assets:
                  - id: intro
                    src: asset:0192f3a4-5b6c-7d8e-9f01-23456789ae01
                    sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                    size: 734003200
                    type: video
                    mapping:
                      projection: domemaster
                      fov: 180
                    duration: 600
                show:
                  playlists:
                    - id: main
                      items:
                        - asset: intro
                  cues:
                    - id: dim
                      at:
                        trigger: go
                      actions:
                        - do: setBrightness
                          target: main
                          args:
                            value: 0.7
                  triggers:
                    - id: go
                      source: osc
                      match: /dome/go
                  startup:
                    playlist: main
                warnings: []
                createdAt: '2026-10-19T12:00:00.000Z'
                updatedAt: '2026-10-19T12:00:00.000Z'
          links:
            getShow:
              operationId: getShow
              parameters:
                showId: $response.body#/id
            exportShow:
              operationId: exportShow
              parameters:
                showId: $response.body#/id
            getPlaylist:
              operationId: getPlaylist
              parameters:
                showId: $response.body#/id
                itemId: $response.body#/show/playlists/0/id
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /shows/{showId}:
    parameters:
      - $ref: '#/components/parameters/ShowId'
    get:
      operationId: getShow
      tags:
        - shows
      summary: Get a show
      description: Full show with validation warnings against the active venue.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: The show.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/show.schema'
              example:
                id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                revision: 1
                status: ready
                meta:
                  id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                  title:
                    en: Intro show
                assets:
                  - id: intro
                    src: asset:0192f3a4-5b6c-7d8e-9f01-23456789ae01
                    sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                    size: 734003200
                    type: video
                    mapping:
                      projection: domemaster
                      fov: 180
                    duration: 600
                show:
                  playlists:
                    - id: main
                      items:
                        - asset: intro
                  cues:
                    - id: dim
                      at:
                        trigger: go
                      actions:
                        - do: setBrightness
                          target: main
                          args:
                            value: 0.7
                  triggers:
                    - id: go
                      source: osc
                      match: /dome/go
                  startup:
                    playlist: main
                warnings: []
                createdAt: '2026-10-19T12:00:00.000Z'
                updatedAt: '2026-10-19T12:00:00.000Z'
        '304':
          $ref: '#/components/responses/NotModified'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putShow
      tags:
        - shows
      summary: Replace (or create with a client-chosen id)
      description: '`If-Match` replaces; `If-None-Match: *` creates with this id (412 if taken).'
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatchOrCreate'
        - $ref: '#/components/parameters/IfNoneMatchCreate'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/input-4'
            example:
              meta:
                id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                title:
                  en: Intro show
              assets:
                - id: intro
                  src: asset:0192f3a4-5b6c-7d8e-9f01-23456789ae01
                  sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                  size: 734003200
                  type: video
                  mapping:
                    projection: domemaster
                    fov: 180
                  duration: 600
              show:
                playlists:
                  - id: main
                    items:
                      - asset: intro
                cues:
                  - id: dim
                    at:
                      trigger: go
                    actions:
                      - do: setBrightness
                        target: main
                        args:
                          value: 0.7
                triggers:
                  - id: go
                    source: osc
                    match: /dome/go
                startup:
                  playlist: main
      responses:
        '200':
          description: Replaced.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/show.schema'
              example:
                id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                revision: 1
                status: ready
                meta:
                  id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                  title:
                    en: Intro show
                assets:
                  - id: intro
                    src: asset:0192f3a4-5b6c-7d8e-9f01-23456789ae01
                    sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                    size: 734003200
                    type: video
                    mapping:
                      projection: domemaster
                      fov: 180
                    duration: 600
                show:
                  playlists:
                    - id: main
                      items:
                        - asset: intro
                  cues:
                    - id: dim
                      at:
                        trigger: go
                      actions:
                        - do: setBrightness
                          target: main
                          args:
                            value: 0.7
                  triggers:
                    - id: go
                      source: osc
                      match: /dome/go
                  startup:
                    playlist: main
                warnings: []
                createdAt: '2026-10-19T12:00:00.000Z'
                updatedAt: '2026-10-19T12:00:00.000Z'
        '201':
          description: Created.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/show.schema'
              example:
                id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                revision: 1
                status: ready
                meta:
                  id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                  title:
                    en: Intro show
                assets:
                  - id: intro
                    src: asset:0192f3a4-5b6c-7d8e-9f01-23456789ae01
                    sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                    size: 734003200
                    type: video
                    mapping:
                      projection: domemaster
                      fov: 180
                    duration: 600
                show:
                  playlists:
                    - id: main
                      items:
                        - asset: intro
                  cues:
                    - id: dim
                      at:
                        trigger: go
                      actions:
                        - do: setBrightness
                          target: main
                          args:
                            value: 0.7
                  triggers:
                    - id: go
                      source: osc
                      match: /dome/go
                  startup:
                    playlist: main
                warnings: []
                createdAt: '2026-10-19T12:00:00.000Z'
                updatedAt: '2026-10-19T12:00:00.000Z'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchShow
      tags:
        - shows
      summary: Merge-patch a show
      description: RFC 7396 over the show sections; the result must validate as a show.
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-5'
            example:
              meta:
                title:
                  en: Intro show (v2)
      responses:
        '200':
          description: Patched.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/show.schema'
              example:
                id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                revision: 1
                status: ready
                meta:
                  id: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                  title:
                    en: Intro show
                assets:
                  - id: intro
                    src: asset:0192f3a4-5b6c-7d8e-9f01-23456789ae01
                    sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                    size: 734003200
                    type: video
                    mapping:
                      projection: domemaster
                      fov: 180
                    duration: 600
                show:
                  playlists:
                    - id: main
                      items:
                        - asset: intro
                  cues:
                    - id: dim
                      at:
                        trigger: go
                      actions:
                        - do: setBrightness
                          target: main
                          args:
                            value: 0.7
                  triggers:
                    - id: go
                      source: osc
                      match: /dome/go
                  startup:
                    playlist: main
                warnings: []
                createdAt: '2026-10-19T12:00:00.000Z'
                updatedAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteShow
      tags:
        - shows
      summary: Delete a show
      description: Refused (409) while it is the active show of a playing venue. Assets are kept.
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /shows/{showId}/export:
    parameters:
      - $ref: '#/components/parameters/ShowId'
    get:
      operationId: exportShow
      tags:
        - shows
      summary: Export the show as a .dome
      description: '`kind: show` `.dome`; `?embedAssets=true` stores the media inside (stored entries, ZIP64). Range supported.'
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - name: embedAssets
          in: query
          description: Embed the asset files (otherwise external references with sha256 + size).
          schema:
            type: boolean
            default: false
        - name: Range
          in: header
          description: Byte range (RFC 9110).
          schema:
            type: string
      responses:
        '200':
          description: The .dome file.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/vnd.nativemind.freedome.dome+zip:
              schema:
                type: string
                contentMediaType: application/vnd.nativemind.freedome.dome+zip
        '206':
          description: Partial content.
          content:
            application/vnd.nativemind.freedome.dome+zip:
              schema:
                type: string
                contentMediaType: application/vnd.nativemind.freedome.dome+zip
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /shows/{showId}/playlists/{itemId}:
    parameters:
      - $ref: '#/components/parameters/ShowId'
      - $ref: '#/components/parameters/DocItemId'
    get:
      operationId: getPlaylist
      tags:
        - shows
      summary: Get a playlist of the show
      description: The `.dome` `show.playlists[]` entry with this id; ETag = show revision.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: The playlist.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/playlist'
              example:
                id: main
                name: Main
                loop: false
                items:
                  - asset: intro
                    transition:
                      kind: fade
                      duration: 2
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putPlaylist
      tags:
        - shows
      summary: Create or replace a playlist of the show
      description: Body `id` must equal the path id. `If-Match` = show ETag; bumps the show revision (event `show.updated`, sub-kind `playlist.updated`).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/playlist'
            example:
              id: main
              name: Main
              loop: false
              items:
                - asset: intro
                  transition:
                    kind: fade
                    duration: 2
      responses:
        '200':
          description: Stored; the playlist as saved.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/playlist'
              example:
                id: main
                name: Main
                loop: false
                items:
                  - asset: intro
                    transition:
                      kind: fade
                      duration: 2
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deletePlaylist
      tags:
        - shows
      summary: Delete a playlist of the show
      description: 409 when other show entries still reference it (e.g. a cue → trigger, startup → playlist).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /shows/{showId}/timelines/{itemId}:
    parameters:
      - $ref: '#/components/parameters/ShowId'
      - $ref: '#/components/parameters/DocItemId'
    get:
      operationId: getTimeline
      tags:
        - shows
      summary: Get a timeline of the show
      description: The `.dome` `show.timelines[]` entry with this id; ETag = show revision.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: The timeline.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/timeline'
              example:
                id: tl
                fps: 60
                duration: 600
                tracks:
                  - id: v
                    kind: video
                    clips:
                      - asset: intro
                        start: 0
                markers:
                  - id: half
                    time: 300
                    name: Half
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putTimeline
      tags:
        - shows
      summary: Create or replace a timeline of the show
      description: Body `id` must equal the path id. `If-Match` = show ETag; bumps the show revision (event `show.updated`, sub-kind `timeline.updated`).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/timeline'
            example:
              id: tl
              fps: 60
              duration: 600
              tracks:
                - id: v
                  kind: video
                  clips:
                    - asset: intro
                      start: 0
              markers:
                - id: half
                  time: 300
                  name: Half
      responses:
        '200':
          description: Stored; the timeline as saved.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/timeline'
              example:
                id: tl
                fps: 60
                duration: 600
                tracks:
                  - id: v
                    kind: video
                    clips:
                      - asset: intro
                        start: 0
                markers:
                  - id: half
                    time: 300
                    name: Half
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteTimeline
      tags:
        - shows
      summary: Delete a timeline of the show
      description: 409 when other show entries still reference it (e.g. a cue → trigger, startup → playlist).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /shows/{showId}/cues/{itemId}:
    parameters:
      - $ref: '#/components/parameters/ShowId'
      - $ref: '#/components/parameters/DocItemId'
    get:
      operationId: getCue
      tags:
        - shows
      summary: Get a cue of the show
      description: The `.dome` `show.cues[]` entry with this id; ETag = show revision.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: The cue.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/cue'
              example:
                id: dim
                at:
                  trigger: go
                actions:
                  - do: setBrightness
                    target: main
                    args:
                      value: 0.7
                      fade: 3
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putCue
      tags:
        - shows
      summary: Create or replace a cue of the show
      description: Body `id` must equal the path id. `If-Match` = show ETag; bumps the show revision (event `show.updated`, sub-kind `cue.updated`).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cue'
            example:
              id: dim
              at:
                trigger: go
              actions:
                - do: setBrightness
                  target: main
                  args:
                    value: 0.7
                    fade: 3
      responses:
        '200':
          description: Stored; the cue as saved.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/cue'
              example:
                id: dim
                at:
                  trigger: go
                actions:
                  - do: setBrightness
                    target: main
                    args:
                      value: 0.7
                      fade: 3
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteCue
      tags:
        - shows
      summary: Delete a cue of the show
      description: 409 when other show entries still reference it (e.g. a cue → trigger, startup → playlist).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /shows/{showId}/triggers/{itemId}:
    parameters:
      - $ref: '#/components/parameters/ShowId'
      - $ref: '#/components/parameters/DocItemId'
    get:
      operationId: getTrigger
      tags:
        - shows
      summary: Get a trigger of the show
      description: The `.dome` `show.triggers[]` entry with this id; ETag = show revision.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: The trigger.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/trigger'
              example:
                id: go
                source: osc
                match: /dome/go
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putTrigger
      tags:
        - shows
      summary: Create or replace a trigger of the show
      description: Body `id` must equal the path id. `If-Match` = show ETag; bumps the show revision (event `show.updated`, sub-kind `trigger.updated`).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/trigger'
            example:
              id: go
              source: osc
              match: /dome/go
      responses:
        '200':
          description: Stored; the trigger as saved.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/trigger'
              example:
                id: go
                source: osc
                match: /dome/go
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteTrigger
      tags:
        - shows
      summary: Delete a trigger of the show
      description: 409 when other show entries still reference it (e.g. a cue → trigger, startup → playlist).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /shows/{showId}/matrix:
    parameters:
      - $ref: '#/components/parameters/ShowId'
    get:
      operationId: getMatrix
      tags:
        - shows
      summary: Get the show matrix
      description: Screenberry-style matrix (`.dome` `show.matrix`); 404 when the show has none.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: The matrix.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/matrix'
              example:
                rows:
                  - id: r1
                columns:
                  - id: c1
                  - id: c2
                cells:
                  - row: r1
                    column: c1
                    asset: intro
                  - row: r1
                    column: c2
                    asset: poster
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putMatrix
      tags:
        - shows
      summary: Replace the show matrix
      description: '`If-Match` = show ETag; emits `matrix.updated`.'
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/matrix'
            example:
              rows:
                - id: r1
              columns:
                - id: c1
                - id: c2
              cells:
                - row: r1
                  column: c1
                  asset: intro
                - row: r1
                  column: c2
                  asset: poster
      responses:
        '200':
          description: Stored.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/matrix'
              example:
                rows:
                  - id: r1
                columns:
                  - id: c1
                  - id: c2
                cells:
                  - row: r1
                    column: c1
                    asset: intro
                  - row: r1
                    column: c2
                    asset: poster
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /imports:
    post:
      operationId: startImport
      tags:
        - shows
      summary: Import a file (job)
      description: Source = a tus upload (`purpose=import`), an allow-listed URL, or a stored asset. Content sniffing (02e §9.2) wins over `format` when they disagree (`unsupported-format` if neither matches).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/import.schema'
            example:
              uploadId: 0192f3a4-5b6c-7d8e-9f01-23456789af01
              format: dome
              target:
                kind: auto
                activate: true
      responses:
        '202':
          $ref: '#/components/responses/AcceptedJob'
        '404':
          $ref: '#/components/responses/Problem'
        '415':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/players:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listPlayers
      tags:
        - playback
      summary: List the venue's media players
      description: Player `main` always exists; others come from the active show's `mediaPlayers`.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of players.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-7'
              example:
                items:
                  - id: main
                    venueId: default
                    status: playing
                    showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                    playlistId: main
                    itemIndex: 0
                    assetId: intro
                    position: 12.5
                    duration: 600
                    rate: 1
                    loop: false
                    volume: 0.8
                    opacity: 1
                    pending: false
                    revision: 41
                    updatedAt: '2026-10-19T12:00:12.500Z'
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/players/{playerId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/PlayerId'
    get:
      operationId: getPlayer
      tags:
        - playback
      summary: Player state
      description: Live state; also pushed as `player.state` / `player.position` on `venues/{v}/players/{p}`.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: The player.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/player.schema'
              example:
                id: main
                venueId: default
                status: playing
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                playlistId: main
                itemIndex: 0
                assetId: intro
                position: 12.5
                duration: 600
                rate: 1
                loop: false
                volume: 0.8
                opacity: 1
                pending: false
                revision: 41
                updatedAt: '2026-10-19T12:00:12.500Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchPlayer
      tags:
        - playback
      summary: Set rate / loop / volume / opacity / brightness / rotation
      description: Merge-patch of the player's live parameters (UC-P6); last writer wins.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-6'
            example:
              volume: 0.6
              rate: 1
      responses:
        '200':
          description: Updated player.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/player.schema'
              example:
                id: main
                venueId: default
                status: playing
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                playlistId: main
                itemIndex: 0
                assetId: intro
                position: 12.5
                duration: 600
                rate: 1
                volume: 0.8
                pending: false
                revision: 42
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/players/{playerId}/play:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/PlayerId'
    post:
      operationId: play
      tags:
        - playback
      summary: Play (optionally a given source, optionally scheduled)
      description: 'Without a body: resume the loaded source or start `show.startup`. `at` schedules the start on the server clock (multi-node sync). Playing on playing = no-op success.'
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/play'
            example:
              playlistId: main
              itemIndex: 0
              position: 0
              at: '2026-10-19T12:00:05.000Z'
      responses:
        '200':
          description: Player state after the command (pending = not yet acknowledged by the engine).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/player.schema'
              example:
                id: main
                venueId: default
                status: playing
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                playlistId: main
                itemIndex: 0
                assetId: intro
                position: 12.5
                duration: 600
                rate: 1
                volume: 0.8
                pending: false
                revision: 42
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/players/{playerId}/pause:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/PlayerId'
    post:
      operationId: pause
      tags:
        - playback
      summary: Pause
      description: Pause at the current position (no-op when already paused).
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Player state after the command (pending = not yet acknowledged by the engine).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/player.schema'
              example:
                id: main
                venueId: default
                status: paused
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                playlistId: main
                itemIndex: 0
                assetId: intro
                position: 12.5
                duration: 600
                rate: 1
                volume: 0.8
                pending: false
                revision: 42
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/players/{playerId}/resume:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/PlayerId'
    post:
      operationId: resume
      tags:
        - playback
      summary: Resume
      description: Resume a paused player.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Player state after the command (pending = not yet acknowledged by the engine).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/player.schema'
              example:
                id: main
                venueId: default
                status: playing
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                playlistId: main
                itemIndex: 0
                assetId: intro
                position: 12.5
                duration: 600
                rate: 1
                volume: 0.8
                pending: false
                revision: 42
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/players/{playerId}/stop:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/PlayerId'
    post:
      operationId: stop
      tags:
        - playback
      summary: Stop
      description: Stop and rewind; the player shows the configured idle content.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Player state after the command (pending = not yet acknowledged by the engine).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/player.schema'
              example:
                id: main
                venueId: default
                status: stopped
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                playlistId: main
                itemIndex: 0
                assetId: intro
                position: 12.5
                duration: 600
                rate: 1
                volume: 0.8
                pending: false
                revision: 42
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/players/{playerId}/next:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/PlayerId'
    post:
      operationId: next
      tags:
        - playback
      summary: Next item
      description: Next playlist item / timeline marker / matrix column.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Player state after the command (pending = not yet acknowledged by the engine).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/player.schema'
              example:
                id: main
                venueId: default
                status: playing
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                playlistId: main
                itemIndex: 0
                assetId: intro
                position: 12.5
                duration: 600
                rate: 1
                volume: 0.8
                pending: false
                revision: 42
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/players/{playerId}/previous:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/PlayerId'
    post:
      operationId: previous
      tags:
        - playback
      summary: Previous item
      description: Previous playlist item / timeline marker / matrix column.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Player state after the command (pending = not yet acknowledged by the engine).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/player.schema'
              example:
                id: main
                venueId: default
                status: playing
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                playlistId: main
                itemIndex: 0
                assetId: intro
                position: 12.5
                duration: 600
                rate: 1
                volume: 0.8
                pending: false
                revision: 42
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/players/{playerId}/seek:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/PlayerId'
    post:
      operationId: seek
      tags:
        - playback
      summary: Seek
      description: Exactly one of `position` (s), `marker` (timeline marker id) or `normalized` (0..1); optional `at` for synchronised seeks.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/seek'
            example:
              position: 120
      responses:
        '200':
          description: Player state after the command (pending = not yet acknowledged by the engine).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/player.schema'
              example:
                id: main
                venueId: default
                status: playing
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                playlistId: main
                itemIndex: 0
                assetId: intro
                position: 12.5
                duration: 600
                rate: 1
                volume: 0.8
                pending: false
                revision: 42
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/cues/{cueId}/fire:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/CueId'
    post:
      operationId: fireCue
      tags:
        - playback
      summary: Fire a cue (GO)
      description: Runs the cue actions of the active show (or `showId`); result per action. Also `cue.fired` / `cue.failed` on `venues/{v}/cues`.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/fireCue'
            example:
              showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
      responses:
        '200':
          description: Cue fired.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/cueFired'
              example:
                cueId: dim
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                firedAt: '2026-10-19T12:01:00.000Z'
                actions:
                  - do: setBrightness
                    status: ok
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/active-show:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: getActiveShow
      tags:
        - playback
      summary: The venue's active show
      description: Show loaded onto the venue (`showId` null = none) with mapping warnings.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: Active show.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/activeShowState'
              example:
                venueId: default
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                warnings: []
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: setActiveShow
      tags:
        - playback
      summary: Load a show onto the venue
      description: 'Maps the show onto this venue''s geometry (02 §9); `autoplay` starts `show.startup` on `main`; `showId: null` unloads. Playback-class operation: no If-Match.'
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/activeShow'
            example:
              showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
              autoplay: true
      responses:
        '200':
          description: Loaded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/activeShowState'
              example:
                venueId: default
                showId: 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                warnings:
                  - code: remapped-fov
                    detail: asset intro is 180° domemaster, venue tilt 15° — remapped
                    pointer: /assets/0
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/parameters:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listParameters
      tags:
        - playback
      summary: List node / layer parameters
      description: 'Parameter registry (UC-N2): path, type, value, range; `?prefix=` narrows (e.g. `players.main`). Live changes: topic `venues/{v}/parameters`.'
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - name: prefix
          in: query
          description: Dotted path prefix.
          schema:
            type: string
            maxLength: 256
      responses:
        '200':
          description: Page of parameters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/parameterPage'
              example:
                items:
                  - path: master.brightness
                    type: float
                    value: 1
                    min: 0
                    max: 1
                  - path: players.main.opacity
                    type: float
                    value: 1
                    min: 0
                    max: 1
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/parameters/{path}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/ParameterPath'
    get:
      operationId: getParameter
      tags:
        - playback
      summary: Get a parameter
      description: One parameter with type and range.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: The parameter.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/parameter'
              example:
                path: master.brightness
                type: float
                value: 0.8
                min: 0
                max: 1
                unit: ''
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: setParameter
      tags:
        - playback
      summary: Set a parameter
      description: Screenberry-compatible parameter surface (UC-N1); optional `fadeSec`. Read-only parameters → 422.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/parameterValue'
            example:
              value: 0.8
              fadeSec: 2
      responses:
        '200':
          description: New value.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/parameter'
              example:
                path: master.brightness
                type: float
                value: 0.8
                min: 0
                max: 1
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /uploads:
    options:
      operationId: getUploadCapabilities
      tags:
        - uploads
      summary: tus discovery
      description: Returns the tus capability headers (no body). Anonymous so that tus clients can probe before auth.
      security:
        - {}
      x-scope: anon
      responses:
        '204':
          description: Capabilities.
          headers:
            Tus-Resumable:
              $ref: '#/components/headers/TusResumable'
            Tus-Version:
              $ref: '#/components/headers/TusVersion'
            Tus-Extension:
              $ref: '#/components/headers/TusExtension'
            Tus-Max-Size:
              $ref: '#/components/headers/TusMaxSize'
            Tus-Checksum-Algorithm:
              $ref: '#/components/headers/TusChecksumAlgorithm'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createUpload
      tags:
        - uploads
      summary: Create an upload (tus Creation, optionally with the first chunk)
      description: '`Upload-Length` + `Upload-Metadata` (filename, contentType, sha256, purpose, assetId). With a body (`Content-Type: application/offset+octet-stream`) the first chunk is stored (Creation-With-Upload) and `Upload-Offset` returned. Upload-Defer-Length is not supported.'
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/TusResumable'
        - name: Upload-Length
          in: header
          required: true
          description: Total size in bytes (≤ Tus-Max-Size, else 413).
          schema:
            type: integer
            minimum: 0
        - name: Upload-Metadata
          in: header
          required: true
          description: '`filename` and `purpose` required; `sha256` strongly recommended.'
          schema:
            type: string
        - $ref: '#/components/parameters/UploadChecksum'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/offset+octet-stream:
            schema:
              type: string
              contentMediaType: application/octet-stream
      responses:
        '201':
          description: Created; `Location` = /api/v1/uploads/{uploadId}.
          headers:
            Location:
              $ref: '#/components/headers/Location'
            Tus-Resumable:
              $ref: '#/components/headers/TusResumable'
            Upload-Offset:
              $ref: '#/components/headers/UploadOffset'
            Upload-Expires:
              $ref: '#/components/headers/UploadExpires'
        '400':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '413':
          $ref: '#/components/responses/Problem'
        '415':
          $ref: '#/components/responses/Problem'
        '460':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /uploads/{uploadId}:
    parameters:
      - $ref: '#/components/parameters/UploadId'
    head:
      operationId: getUploadOffset
      tags:
        - uploads
      summary: Current offset (tus HEAD)
      description: '`Upload-Offset`, `Upload-Length`, `Upload-Metadata`, `Cache-Control: no-store`. Expired / unknown → 404 (or 410).'
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/TusResumable'
      responses:
        '200':
          description: Offset.
          headers:
            Tus-Resumable:
              $ref: '#/components/headers/TusResumable'
            Upload-Offset:
              $ref: '#/components/headers/UploadOffset'
            Upload-Length:
              $ref: '#/components/headers/UploadLength'
            Upload-Metadata:
              $ref: '#/components/headers/UploadMetadata'
            Upload-Expires:
              $ref: '#/components/headers/UploadExpires'
            Cache-Control:
              $ref: '#/components/headers/CacheControl'
        '404':
          $ref: '#/components/responses/Problem'
        '410':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: appendUpload
      tags:
        - uploads
      summary: Append a chunk (tus PATCH)
      description: '`Upload-Offset` must equal the server offset (409 otherwise); `Upload-Checksum: sha256 …` per chunk (460 on mismatch, chunk discarded). Concurrency is the offset, not If-Match.'
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/TusResumable'
        - $ref: '#/components/parameters/UploadOffsetReq'
        - $ref: '#/components/parameters/UploadChecksum'
      requestBody:
        required: true
        content:
          application/offset+octet-stream:
            schema:
              type: string
              contentMediaType: application/octet-stream
      responses:
        '204':
          description: Chunk stored; new offset. When offset = length the upload completes (asset / import job follows).
          headers:
            Tus-Resumable:
              $ref: '#/components/headers/TusResumable'
            Upload-Offset:
              $ref: '#/components/headers/UploadOffset'
            Upload-Expires:
              $ref: '#/components/headers/UploadExpires'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '410':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '413':
          $ref: '#/components/responses/Problem'
        '415':
          $ref: '#/components/responses/Problem'
        '460':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteUpload
      tags:
        - uploads
      summary: Terminate an upload (tus Termination)
      description: Frees the stored bytes; later requests → 404.
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/TusResumable'
      responses:
        '204':
          description: Terminated.
          headers:
            Tus-Resumable:
              $ref: '#/components/headers/TusResumable'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    get:
      operationId: getUpload
      tags:
        - uploads
      summary: Upload status as JSON
      description: For clients without a tus library (progress UIs); also pushed as `upload.progress` on `uploads/{u}`.
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      responses:
        '200':
          description: Upload status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/upload.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789ae10
                length: 734003200
                offset: 209715200
                status: uploading
                metadata:
                  filename: intro-4k-hapq.mov
                  contentType: video/quicktime
                  sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                  purpose: asset
                expiresAt: '2026-10-20T12:00:00.000Z'
                createdAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /assets:
    get:
      operationId: listAssets
      tags:
        - assets
      summary: List / search assets
      description: Filters `type`, `tag`, `status`, full text `q` (name, tags).
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Query'
        - $ref: '#/components/parameters/Sort'
        - $ref: '#/components/parameters/Count'
        - name: type
          in: query
          description: Asset type.
          schema:
            $ref: '#/components/schemas/type'
        - name: tag
          in: query
          description: Tag filter.
          schema:
            type: string
            maxLength: 64
        - name: status
          in: query
          description: Status filter.
          schema:
            $ref: '#/components/schemas/status-3'
      responses:
        '200':
          description: Page of assets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-8'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789ae01
                    name: intro-4k-hapq.mov
                    type: video
                    mediaType: video/quicktime
                    sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                    size: 734003200
                    status: ready
                    revision: 2
                    createdAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
                count: 1
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createAsset
      tags:
        - assets
      summary: Create an asset from an upload, a URL, or a small multipart file
      description: 'JSON: `{uploadId | url, id?, name?, type?, mapping?, tags}`. `multipart/form-data` with part `file` for ≤ 100 MB (larger → 413, use tus). Probing (ffprobe, thumbnails) runs in the background: status `processing` → `ready` | `failed`, events on `assets/{a}`.'
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/input-5'
            example:
              uploadId: 0192f3a4-5b6c-7d8e-9f01-23456789ae10
              name: Intro 4K
              type: video
              mapping:
                projection: domemaster
                fov: 180
              tags:
                - intro
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/multipart'
            encoding:
              file:
                contentType: application/octet-stream
      responses:
        '201':
          description: Created (processing).
          headers:
            Location:
              $ref: '#/components/headers/Location'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/asset.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789ae01
                name: Intro 4K
                type: video
                sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                size: 734003200
                status: processing
                mapping:
                  projection: domemaster
                  fov: 180
                tags:
                  - intro
                revision: 1
                createdAt: '2026-10-19T12:00:00.000Z'
          links:
            getAsset:
              operationId: getAsset
              parameters:
                assetId: $response.body#/id
            downloadAsset:
              operationId: downloadAsset
              parameters:
                assetId: $response.body#/id
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '413':
          $ref: '#/components/responses/Problem'
        '415':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /assets/{assetId}:
    parameters:
      - $ref: '#/components/parameters/AssetId'
    get:
      operationId: getAsset
      tags:
        - assets
      summary: Get an asset
      description: Metadata, probe data, thumbnails, shows using it.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: The asset.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/asset.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789ae01
                name: intro-4k-hapq.mov
                type: video
                mediaType: video/quicktime
                sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
                size: 734003200
                status: ready
                mapping:
                  projection: domemaster
                  fov: 180
                media:
                  container: mov
                  duration: 600
                  video:
                    codec: hap-q
                    resolution:
                      - 4096
                      - 4096
                    fps: 60
                  audio:
                    codec: pcm_s24le
                    channels: 16
                    ambisonicOrder: 3
                    sampleRate: 48000
                thumbnails:
                  - size: 256
                    url: /api/v1/assets/0192f3a4-5b6c-7d8e-9f01-23456789ae01/thumbnail?size=256
                usedBy:
                  - 9c4d5e6f-7a8b-4c93-8d0e-1f2a3b4c5d6e
                revision: 2
                createdAt: '2026-10-19T12:00:00.000Z'
        '304':
          $ref: '#/components/responses/NotModified'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchAsset
      tags:
        - assets
      summary: Merge-patch asset metadata
      description: Name, type, mapping, tags (content is immutable — upload a new asset or re-upload with `assetId`).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-7'
            example:
              tags:
                - intro
                - 4k
      responses:
        '200':
          description: Patched.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/asset.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789ae01
                name: intro-4k-hapq.mov
                type: video
                status: ready
                tags:
                  - intro
                  - 4k
                revision: 3
                createdAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteAsset
      tags:
        - assets
      summary: Delete an asset
      description: Refused (409) while referenced by a show unless `?force=true` (cues referencing it then report failed; a playing show continues).
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
        - $ref: '#/components/parameters/Force'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /assets/{assetId}/content:
    parameters:
      - $ref: '#/components/parameters/AssetId'
    get:
      operationId: downloadAsset
      tags:
        - assets
      summary: Download the asset content
      description: Range requests (206), `ETag` = sha256, `If-None-Match` → 304. Registered URL assets → 303 to the URL.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - $ref: '#/components/parameters/Range'
        - $ref: '#/components/parameters/IfNoneMatch'
      responses:
        '200':
          description: Full content.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
            Accept-Ranges:
              $ref: '#/components/headers/AcceptRanges'
          content:
            '*/*':
              schema:
                type: string
                contentMediaType: application/octet-stream
        '206':
          description: Partial content.
          headers:
            Content-Range:
              $ref: '#/components/headers/ContentRange'
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            '*/*':
              schema:
                type: string
                contentMediaType: application/octet-stream
        '303':
          description: External asset — see `Location`.
          headers:
            Location:
              $ref: '#/components/headers/Location'
        '304':
          $ref: '#/components/responses/NotModified'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '416':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /assets/{assetId}/thumbnail:
    parameters:
      - $ref: '#/components/parameters/AssetId'
    get:
      operationId: getAssetThumbnail
      tags:
        - assets
      summary: Asset thumbnail
      description: PNG / JPEG / WebP; `?size=` = longest edge in px (nearest generated size).
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - name: size
          in: query
          description: Longest edge in pixels.
          schema:
            type: integer
            minimum: 16
            maximum: 4096
            default: 256
      responses:
        '200':
          description: Image.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            image/png:
              schema:
                type: string
                contentMediaType: image/png
            image/jpeg:
              schema:
                type: string
                contentMediaType: image/jpeg
            image/webp:
              schema:
                type: string
                contentMediaType: image/webp
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /storage:
    get:
      operationId: getStorageStats
      tags:
        - assets
      summary: Storage usage
      description: Disk / quota usage of the content store (replaces /api/v1/content/stats).
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: Stats.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/storage'
              example:
                totalBytes: 4000787030016
                usedBytes: 880787030016
                freeBytes: 3120000000000
                quotaBytes: null
                assets:
                  count: 214
                  bytes: 870000000000
                uploads:
                  count: 1
                  bytes: 209715200
                byType:
                  video:
                    count: 120
                    bytes: 860000000000
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: getAudio
      tags:
        - audio
      summary: Venue audio state
      description: Mode (digital / hybrid / combined), master gain, mute, engine status.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: Audio state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/audio.schema'
              example:
                venueId: default
                mode: combined
                masterGainDb: -6
                mute: false
                engines:
                  anantasound:
                    status: up
                    version: 1.0.0
                  anantadigital:
                    status: up
                    version: 1.0.0
                layoutRevision: 3
                revision: 12
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchAudio
      tags:
        - audio
      summary: Set mode / master gain / mute
      description: Mode change re-routes the active show's audio between the engines (02c A8).
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-8'
            example:
              masterGainDb: -9
              mute: false
      responses:
        '200':
          description: New state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/audio.schema'
              example:
                venueId: default
                mode: combined
                masterGainDb: -6
                mute: false
                engines:
                  anantasound:
                    status: up
                    version: 1.0.0
                  anantadigital:
                    status: up
                    version: 1.0.0
                layoutRevision: 3
                revision: 12
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/layout:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: getSpeakerLayout
      tags:
        - audio
      summary: Speaker layout (.dome audio block)
      description: Ambisonics, decoder, speakers, room correction, devices; ETag = venue revision.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      responses:
        '200':
          description: Layout.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/audio'
              example:
                ambisonics:
                  order: 3
                  normalisation: SN3D
                  channelOrder: ACN
                decoder:
                  kind: allrad
                  dualBand: true
                speakers:
                  - id: s1
                    azimuth: 0
                    elevation: 0
                    distance: 4
                devices:
                  - id: qrd1
                    kind: qrd
                    address: osc://10.0.0.20:9000
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putSpeakerLayout
      tags:
        - audio
      summary: Replace the speaker layout
      description: Written into the venue (`revision`++) and applied to AnantaDigital (decoder rebuilt).
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/audio'
            example:
              ambisonics:
                order: 3
                normalisation: SN3D
                channelOrder: ACN
              decoder:
                kind: allrad
                dualBand: true
              speakers:
                - id: s1
                  azimuth: 0
                  elevation: 0
                  distance: 4
              devices:
                - id: qrd1
                  kind: qrd
                  address: osc://10.0.0.20:9000
      responses:
        '200':
          description: Stored.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/audio'
              example:
                ambisonics:
                  order: 3
                  normalisation: SN3D
                  channelOrder: ACN
                decoder:
                  kind: allrad
                  dualBand: true
                speakers:
                  - id: s1
                    azimuth: 0
                    elevation: 0
                    distance: 4
                devices:
                  - id: qrd1
                    kind: qrd
                    address: osc://10.0.0.20:9000
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/zones:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listAudioZones
      tags:
        - audio
      summary: List audio zones
      description: AnantaDigital zones of the venue.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of zones.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/zonePage'
              example:
                items:
                  - id: front
                    name: Front
                    speakers:
                      - s1
                      - s2
                    gainDb: -3
                    delayMs: 2.5
                    eq:
                      - type: peak
                        frequencyHz: 120
                        gainDb: -2
                        q: 1.4
                    mute: false
                    revision: 2
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/zones/{zoneId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/ZoneId'
    get:
      operationId: getAudioZone
      tags:
        - audio
      summary: Get a zone
      description: One zone.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: The zone.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/zone'
              example:
                id: front
                name: Front
                speakers:
                  - s1
                  - s2
                gainDb: -3
                delayMs: 2.5
                eq:
                  - type: peak
                    frequencyHz: 120
                    gainDb: -2
                    q: 1.4
                mute: false
                revision: 2
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putAudioZone
      tags:
        - audio
      summary: Create or replace a zone
      description: '`If-None-Match: *` creates, `If-Match` replaces.'
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      parameters:
        - $ref: '#/components/parameters/IfMatchOrCreate'
        - $ref: '#/components/parameters/IfNoneMatchCreate'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/zoneInput'
            example:
              name: Front
              speakers:
                - s1
                - s2
              gainDb: -3
              delayMs: 2.5
      responses:
        '200':
          description: Replaced.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/zone'
              example:
                id: front
                name: Front
                speakers:
                  - s1
                  - s2
                gainDb: -3
                delayMs: 2.5
                eq:
                  - type: peak
                    frequencyHz: 120
                    gainDb: -2
                    q: 1.4
                mute: false
                revision: 2
        '201':
          description: Created.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/zone'
              example:
                id: front
                name: Front
                speakers:
                  - s1
                  - s2
                gainDb: -3
                delayMs: 2.5
                eq:
                  - type: peak
                    frequencyHz: 120
                    gainDb: -2
                    q: 1.4
                mute: false
                revision: 2
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchAudioZone
      tags:
        - audio
      summary: Live zone change (gain, delay, EQ, mute)
      description: Last writer wins (OSC `/freedome/v1/venue/{v}/audio/zone/{z}/gain`).
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/zonePatch'
            example:
              gainDb: -6
      responses:
        '200':
          description: Updated zone.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/zone'
              example:
                id: front
                name: Front
                speakers:
                  - s1
                  - s2
                gainDb: -3
                delayMs: 2.5
                eq:
                  - type: peak
                    frequencyHz: 120
                    gainDb: -2
                    q: 1.4
                mute: false
                revision: 2
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteAudioZone
      tags:
        - audio
      summary: Delete a zone
      description: Speakers fall back to the default zone.
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/devices:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listAudioDevices
      tags:
        - audio
      summary: List audio devices with live state
      description: '`?kind=speaker|microphone|qrd|patefon|karmic_cluster|resonator` (replaces get_microphones).'
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - name: kind
          in: query
          description: Device kind filter.
          schema:
            $ref: '#/components/schemas/deviceKind'
      responses:
        '200':
          description: Page of devices.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/devicePage'
              example:
                items:
                  - id: qrd1
                    kind: qrd
                    name: QRD hub
                    config:
                      id: qrd1
                      kind: qrd
                      address: osc://10.0.0.20:9000
                    state:
                      online: true
                      engine: anantasound
                      firmware: 1.2.0
                      lastSeen: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/devices/{deviceId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/AudioDeviceId'
    get:
      operationId: getAudioDevice
      tags:
        - audio
      summary: Get an audio device
      description: Configuration + live state.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: The device.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/device'
              example:
                id: qrd1
                kind: qrd
                name: QRD hub
                config:
                  id: qrd1
                  kind: qrd
                  address: osc://10.0.0.20:9000
                state:
                  online: true
                  engine: anantasound
                  firmware: 1.2.0
                  lastSeen: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchAudioDevice
      tags:
        - audio
      summary: Live device change (gain, mute, name, config)
      description: Gain / mute are live (last writer wins); config changes are written into the venue.
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/devicePatch'
            example:
              gainDb: -3
      responses:
        '200':
          description: Updated device.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/device'
              example:
                id: qrd1
                kind: qrd
                name: QRD hub
                config:
                  id: qrd1
                  kind: qrd
                  address: osc://10.0.0.20:9000
                state:
                  online: true
                  engine: anantasound
                  firmware: 1.2.0
                  lastSeen: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/devices/{deviceId}/test:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/AudioDeviceId'
    post:
      operationId: testAudioDevice
      tags:
        - audio
      summary: Test a device (ping / tone / self-test / strike)
      description: Bounded by the device limits (actuator self-test is refused while a session runs, 409).
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/deviceTest'
            example:
              kind: tone
              frequencyHz: 432
              gainDb: -30
              durationS: 2
      responses:
        '200':
          description: Result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/deviceTestResult'
              example:
                deviceId: qrd1
                ok: true
                latencyMs: 4.2
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/presets:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listAudioPresets
      tags:
        - audio
      summary: List installed presets
      description: L6 programme / pattern / session presets installed on this server.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - name: kind
          in: query
          description: Preset kind.
          schema:
            $ref: '#/components/schemas/presetKind'
      responses:
        '200':
          description: Page of presets.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/presetPage'
              example:
                items:
                  - id: calm-7
                    kind: session
                    title:
                      en: Calm 7
                    revision: 1
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/presets/{presetId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/PresetId'
    get:
      operationId: getAudioPreset
      tags:
        - audio
      summary: Get a preset document
      description: The ananta/1.0 document.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: The preset.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/preset'
              example:
                id: calm-7
                kind: session
                title:
                  en: Calm 7
                revision: 1
                document:
                  format: ananta-session
                  formatVersion: 1.0.0
                  id: calm-7
                  title:
                    en: Calm 7
                  items:
                    - programme:
                        id: tone-432
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putAudioPreset
      tags:
        - audio
      summary: Install or replace a preset
      description: 'Validated against ananta/1.0 (02c); `If-None-Match: *` creates.'
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatchOrCreate'
        - $ref: '#/components/parameters/IfNoneMatchCreate'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/presetInput'
            example:
              kind: session
              document:
                format: ananta-session
                formatVersion: 1.0.0
                id: calm-7
                title:
                  en: Calm 7
                items:
                  - programme:
                      id: tone-432
      responses:
        '200':
          description: Replaced.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/preset'
              example:
                id: calm-7
                kind: session
                title:
                  en: Calm 7
                revision: 1
                document:
                  format: ananta-session
                  formatVersion: 1.0.0
                  id: calm-7
                  title:
                    en: Calm 7
                  items:
                    - programme:
                        id: tone-432
        '201':
          description: Installed.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/preset'
              example:
                id: calm-7
                kind: session
                title:
                  en: Calm 7
                revision: 1
                document:
                  format: ananta-session
                  formatVersion: 1.0.0
                  id: calm-7
                  title:
                    en: Calm 7
                  items:
                    - programme:
                        id: tone-432
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteAudioPreset
      tags:
        - audio
      summary: Remove a preset
      description: Refused (409) while a session runs it.
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/sessions:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listAudioSessions
      tags:
        - audio
      summary: List audio sessions (running and recent)
      description: Running first, then finished ones from the last 24 h.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of sessions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/sessionPage'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789b001
                    venueId: default
                    presetId: calm-7
                    status: running
                    engines:
                      - anantadigital
                      - anantasound
                    position: 42
                    startedAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: startAudioSession
      tags:
        - audio
      summary: Run a preset
      description: Runs a programme / session preset on the engine(s) the audio mode selects; optional scheduled `at`.
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/sessionStart'
            example:
              presetId: calm-7
              gainDb: -6
              targets:
                zones:
                  - front
      responses:
        '201':
          description: Started.
          headers:
            Location:
              $ref: '#/components/headers/Location'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/session'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789b001
                venueId: default
                presetId: calm-7
                status: running
                engines:
                  - anantadigital
                  - anantasound
                position: 42
                startedAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/sessions/{sessionId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/SessionId'
    get:
      operationId: getAudioSession
      tags:
        - audio
      summary: Get an audio session
      description: Status and position.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: The session.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/session'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789b001
                venueId: default
                presetId: calm-7
                status: running
                engines:
                  - anantadigital
                  - anantasound
                position: 42
                startedAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: stopAudioSession
      tags:
        - audio
      summary: Stop an audio session
      description: Fades out (release times of the programme) and stops; idempotent.
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      x-concurrency: none
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/generator:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: getGenerator
      tags:
        - audio
      summary: Generator state
      description: Running test / MULTI generator.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: Generator state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/generatorState'
              example:
                running: true
                settings:
                  signal: pink
                  gainDb: -20
                  targets:
                    zones:
                      - front
                startedAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: setGenerator
      tags:
        - audio
      summary: Start / change the generator
      description: sine | pink | white | sweep | multi (iScillator semantics); replaces generate_test_signal.
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/generator'
            example:
              signal: pink
              gainDb: -20
              targets:
                zones:
                  - front
      responses:
        '200':
          description: Running.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/generatorState'
              example:
                running: true
                settings:
                  signal: pink
                  gainDb: -20
                  targets:
                    zones:
                      - front
                startedAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: stopGenerator
      tags:
        - audio
      summary: Stop the generator
      description: Idempotent.
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      x-concurrency: none
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/sources/{sourceId}/position:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/SourceId'
    put:
      operationId: setSourcePosition
      tags:
        - audio
      summary: Position an interactive source
      description: Azimuth / elevation / distance with an optional ramp; high-rate callers should prefer OSC or WS `call`.
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/sourcePosition'
            example:
              azimuth: 45
              elevation: 30
              distance: 3
              rampMs: 50
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/analysis:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: getAudioAnalysis
      tags:
        - audio
      summary: Live analysis settings and status
      description: Analysed inputs and whether the audio engine is running them. Frames are events, see topic `venues/{venueId}/audio/analysis/{inputId}`.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: Analysis state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/analysisState'
              example:
                venueId: default
                enabled: true
                status: running
                engine: anantadigital@node-1
                sampleRate: 48000
                blockSize: 256
                inputs:
                  - id: vocal
                    label: Lead vocal
                    channels:
                      - 1
                    envelope:
                      attackMs: 8
                      decayMs: 120
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: setAudioAnalysis
      tags:
        - audio
      summary: Set the analysed inputs
      description: Replaces the whole list of inputs and starts or stops the analysis. 503 `engine-unavailable` when the audio engine is not reachable.
      security:
        - bearer:
            - audio:control
        - apiKey:
            - audio:control
      x-scope: audio:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/analysisSettings'
            example:
              enabled: true
              inputs:
                - id: vocal
                  label: Lead vocal
                  channels:
                    - 1
                  envelope:
                    attackMs: 8
                    decayMs: 120
      responses:
        '200':
          description: Analysis state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/analysisState'
              example:
                venueId: default
                enabled: true
                status: starting
                inputs:
                  - id: vocal
                    label: Lead vocal
                    channels:
                      - 1
                    envelope:
                      attackMs: 8
                      decayMs: 120
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/reactive:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: getAudioReactive
      tags:
        - audio
      summary: AudioReactive state
      description: Active scene, master intensity, service status.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: AudioReactive state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/audio-reactive.schema'
              example:
                venueId: default
                enabled: true
                status: running
                activeSceneId: vocal-aura
                intensity: 1
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchAudioReactive
      tags:
        - audio
      summary: Select a scene, enable, set intensity
      description: Merge-patch; takes effect on the next frame. 422 for an unknown scene or a scene whose gateway or input does not exist.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-9'
            example:
              activeSceneId: percussion-wave
      responses:
        '200':
          description: AudioReactive state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/audio-reactive.schema'
              example:
                venueId: default
                enabled: true
                status: running
                activeSceneId: percussion-wave
                intensity: 1
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/reactive/scenes:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listAudioReactiveScenes
      tags:
        - audio
      summary: List AudioReactive scenes
      description: Scenes stored for this venue.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of scenes.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scenePage'
              example:
                items:
                  - id: vocal-aura
                    title:
                      en: Vocal Aura
                    revision: 1
                    mappings:
                      - id: glow
                        mode: continuous
                        source:
                          input: vocal
                          metric: rms
                        target:
                          kind: osc
                          gateway: resolume
                          address: /composition/layers/2/video/opacity
                          type: f
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/audio/reactive/scenes/{sceneId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/SceneId'
    get:
      operationId: getAudioReactiveScene
      tags:
        - audio
      summary: Get a scene
      description: The scene document.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: The scene.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scene'
              example:
                id: vocal-aura
                title:
                  en: Vocal Aura
                revision: 1
                mappings:
                  - id: glow
                    mode: continuous
                    source:
                      input: vocal
                      metric: rms
                    target:
                      kind: osc
                      gateway: resolume
                      address: /composition/layers/2/video/opacity
                      type: f
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    put:
      operationId: putAudioReactiveScene
      tags:
        - audio
      summary: Store or replace a scene
      description: '`If-None-Match: *` creates. Replacing the active scene takes effect on the next frame.'
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatchOrCreate'
        - $ref: '#/components/parameters/IfNoneMatchCreate'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/sceneInput'
            example:
              title:
                en: Vocal Aura
              mappings:
                - id: glow
                  mode: continuous
                  source:
                    input: vocal
                    metric: rms
                  target:
                    kind: osc
                    gateway: resolume
                    address: /composition/layers/2/video/opacity
                    type: f
      responses:
        '200':
          description: Replaced.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scene'
              example:
                id: vocal-aura
                title:
                  en: Vocal Aura
                revision: 2
                mappings:
                  - id: glow
                    mode: continuous
                    source:
                      input: vocal
                      metric: rms
                    target:
                      kind: osc
                      gateway: resolume
                      address: /composition/layers/2/video/opacity
                      type: f
        '201':
          description: Stored.
          headers:
            ETag:
              $ref: '#/components/headers/ETag'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scene'
              example:
                id: vocal-aura
                title:
                  en: Vocal Aura
                revision: 1
                mappings:
                  - id: glow
                    mode: continuous
                    source:
                      input: vocal
                      metric: rms
                    target:
                      kind: osc
                      gateway: resolume
                      address: /composition/layers/2/video/opacity
                      type: f
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteAudioReactiveScene
      tags:
        - audio
      summary: Remove a scene
      description: Refused (409) while the scene is active.
      security:
        - bearer:
            - content:write
        - apiKey:
            - content:write
      x-scope: content:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/texture-sources:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listTextureSources
      tags:
        - assets
      summary: List live texture sources
      description: Spout / Syphon / DMA-BUF / NDI senders the engines of this venue can see. To show one, register a `stream` asset with the matching `src` and play it.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of sources.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/textureSourcePage'
              example:
                items:
                  - id: spout-arena
                    transport: spout
                    sender: Arena
                    status: online
                    width: 3840
                    height: 2160
                    node: node-1
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /engines:
    get:
      operationId: listEngines
      tags:
        - engines
      summary: List engines
      description: visual, anantasound, anantadigital (status, version, protocol, capabilities, metrics).
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of engines.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-9'
              example:
                items:
                  - name: visual
                    status: up
                    version: 1.0.0
                    protocol: freedome.engine.v1
                    endpoint: unix:/run/freedome/engine.sock
                    nodes:
                      - node-1
                    backend: vulkan
                    backends:
                      - vulkan
                      - software
                    capabilities:
                      outputs: 4
                      maxResolution:
                        - 8192
                        - 8192
                    metrics:
                      fps: 60
                      droppedFrames: 0
                      gpuLoad: 0.41
                    since: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        default:
          $ref: '#/components/responses/Problem'
  /engines/{name}:
    parameters:
      - $ref: '#/components/parameters/EngineName'
    get:
      operationId: getEngine
      tags:
        - engines
      summary: Get an engine
      description: Status mirror; also pushed on `engines/{name}`.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      responses:
        '200':
          description: The engine.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/engine.schema'
              example:
                name: visual
                status: up
                version: 1.0.0
                protocol: freedome.engine.v1
                endpoint: unix:/run/freedome/engine.sock
                nodes:
                  - node-1
                backend: vulkan
                backends:
                  - vulkan
                  - software
                capabilities:
                  outputs: 4
                  maxResolution:
                    - 8192
                    - 8192
                metrics:
                  fps: 60
                  droppedFrames: 0
                  gpuLoad: 0.41
                since: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /engines/visual/backend:
    put:
      operationId: setComputeBackend
      tags:
        - engines
      summary: Switch the visual compute / render backend
      description: Applied by restarting the render pipeline of freedome-engined (short blackout). 422 when the backend is not in `backends`.
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/backendRequest'
            example:
              backend: software
      responses:
        '200':
          description: Engine after the switch.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/engine.schema'
              example:
                name: visual
                status: up
                version: 1.0.0
                protocol: freedome.engine.v1
                endpoint: unix:/run/freedome/engine.sock
                nodes:
                  - node-1
                backend: software
                backends:
                  - vulkan
                  - software
                capabilities:
                  outputs: 4
                  maxResolution:
                    - 8192
                    - 8192
                metrics:
                  fps: 60
                  droppedFrames: 0
                  gpuLoad: 0.41
                since: '2026-10-19T12:00:00.000Z'
        '422':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /engines/{name}/restart:
    parameters:
      - $ref: '#/components/parameters/EngineName'
    post:
      operationId: restartEngine
      tags:
        - engines
      summary: Restart an engine
      description: The engine host process is restarted by its supervisor; playback resumes from the last state where the engine supports it. 202 + job.
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '202':
          $ref: '#/components/responses/AcceptedJob'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /gateways:
    get:
      operationId: listGateways
      tags:
        - gateways
      summary: List gateways
      description: Inbound emulators (Screenberry, fulldome.pro / 360dome …), outbound drivers, converters with status.
      security:
        - bearer:
            - gateways:manage
        - apiKey:
            - gateways:manage
      x-scope: gateways:manage
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of gateways.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-10'
              example:
                items:
                  - id: screenberry
                    name: Screenberry JSON API
                    direction: inbound
                    protocol: screenberry-json
                    vendor: Screenberry
                    enabled: true
                    status: up
                    docsOnly: true
                    listeners:
                      - transport: http
                        port: 8100
                      - transport: osc
                        port: 7100
                    allowCidrs:
                      - 10.0.0.0/8
                    stats:
                      requests: 120
                nextCursor: null
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /gateways/{gatewayId}:
    parameters:
      - $ref: '#/components/parameters/GatewayId'
    get:
      operationId: getGateway
      tags:
        - gateways
      summary: Get a gateway
      description: Status, listeners, peers, redacted config.
      security:
        - bearer:
            - gateways:manage
        - apiKey:
            - gateways:manage
      x-scope: gateways:manage
      responses:
        '200':
          description: The gateway.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/gateway.schema'
              example:
                id: screenberry
                name: Screenberry JSON API
                direction: inbound
                protocol: screenberry-json
                vendor: Screenberry
                enabled: true
                status: up
                docsOnly: true
                listeners:
                  - transport: http
                    port: 8100
                  - transport: osc
                    port: 7100
                allowCidrs:
                  - 10.0.0.0/8
                stats:
                  requests: 120
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchGateway
      tags:
        - gateways
      summary: Enable / configure a gateway
      description: Merge-patch of `enabled`, `config`, `allowCidrs`; listeners restart.
      security:
        - bearer:
            - gateways:manage
        - apiKey:
            - gateways:manage
      x-scope: gateways:manage
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-10'
            example:
              enabled: false
      responses:
        '200':
          description: Updated gateway.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/gateway.schema'
              example:
                id: screenberry
                name: Screenberry JSON API
                direction: inbound
                protocol: screenberry-json
                vendor: Screenberry
                enabled: false
                status: disabled
                docsOnly: true
                listeners:
                  - transport: http
                    port: 8100
                  - transport: osc
                    port: 7100
                allowCidrs:
                  - 10.0.0.0/8
                stats:
                  requests: 120
        '404':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /gateways/{gatewayId}/test:
    parameters:
      - $ref: '#/components/parameters/GatewayId'
    post:
      operationId: testGateway
      tags:
        - gateways
      summary: Connectivity test of an outbound driver
      description: Replaces /api/vendor/test.
      security:
        - bearer:
            - gateways:manage
        - apiKey:
            - gateways:manage
      x-scope: gateways:manage
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Result.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/testResult'
              example:
                gatewayId: dome360
                ok: true
                latencyMs: 12
                remoteVersion: 2.4.1
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /integrations/osc/send:
    post:
      operationId: sendOsc
      tags:
        - gateways
      summary: Send an OSC message to a configured target
      description: Only targets configured on the server (never arbitrary hosts); audited.
      security:
        - bearer:
            - gateways:manage
        - apiKey:
            - gateways:manage
      x-scope: gateways:manage
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/sendOsc'
            example:
              target: lights-desk
              address: /cue/1/go
              args:
                - type: i
                  value: 1
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /apps/{app}/payloads:
    parameters:
      - $ref: '#/components/parameters/AppId'
    post:
      operationId: publishAppPayload
      tags:
        - apps
      summary: Publish app content to the dome
      description: '`kind` scene | overlay | state; expires after `ttlSec`; event `app.payload` on `apps/{app}`.'
      security:
        - bearer:
            - apps:publish
        - apiKey:
            - apps:publish
      x-scope: apps:publish
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/app-payload.schema'
            example:
              kind: overlay
              data:
                text: Hello
                colour: '#ffcc00'
              ttlSec: 60
      responses:
        '201':
          description: Published.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/published'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789b101
                app: net.nativemind.starveda
                kind: overlay
                data:
                  text: Hello
                expiresAt: '2026-10-19T12:01:00.000Z'
        '413':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: clearAppPayloads
      tags:
        - apps
      summary: Clear the app content
      description: Removes every payload of this app (event `app.cleared`).
      security:
        - bearer:
            - apps:publish
        - apiKey:
            - apps:publish
      x-scope: apps:publish
      x-concurrency: none
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        default:
          $ref: '#/components/responses/Problem'
  /clients:
    get:
      operationId: listClients
      tags:
        - clients
      summary: List registered clients
      description: Admin view of connected devices.
      security:
        - bearer:
            - system:read
        - apiKey:
            - system:read
      x-scope: system:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of clients.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-11'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789b201
                    app: net.nativemind.churyavzrosliy
                    deviceName: Tablet 3
                    platform: android
                    capabilities:
                      - vibration
                      - sensors.accelerometer
                    commandsTopic: clients/0192f3a4-5b6c-7d8e-9f01-23456789b201/commands
                    registeredAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '403':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: registerClient
      tags:
        - clients
      summary: Register this device
      description: Any principal; the client then subscribes to `clients/{clientId}/commands`.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/input-7'
            example:
              app: net.nativemind.churyavzrosliy
              deviceName: Tablet 3
              platform: android
              capabilities:
                - vibration
                - sensors.accelerometer
      responses:
        '201':
          description: Registered.
          headers:
            Location:
              $ref: '#/components/headers/Location'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/client.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789b201
                app: net.nativemind.churyavzrosliy
                deviceName: Tablet 3
                platform: android
                capabilities:
                  - vibration
                  - sensors.accelerometer
                commandsTopic: clients/0192f3a4-5b6c-7d8e-9f01-23456789b201/commands
                registeredAt: '2026-10-19T12:00:00.000Z'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /clients/{clientId}:
    parameters:
      - $ref: '#/components/parameters/ClientId'
    delete:
      operationId: unregisterClient
      tags:
        - clients
      summary: Unregister a device
      description: The registering principal or `system:read`.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-concurrency: none
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /clients/{clientId}/telemetry:
    parameters:
      - $ref: '#/components/parameters/ClientId'
    post:
      operationId: postClientTelemetry
      tags:
        - clients
      summary: Post sensor samples
      description: Batched (≤ 1000 samples); only the registering principal.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/telemetry'
            example:
              samples:
                - ts: '2026-10-19T12:00:00.000Z'
                  sensor: sensors.accelerometer
                  values:
                    - 0.01
                    - 9.81
                    - 0.2
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '413':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /events:
    get:
      operationId: listEvents
      tags:
        - events
      summary: Event history (resume after reconnect)
      description: 'Events with `seq > sinceSeq` on the given topic filters (wildcards `+`, `#`), oldest first; authorisation per concrete topic. `resync: true` when the gap is older than the buffer (default 10 000 events / 10 min).'
      security:
        - bearer:
            - events:subscribe
        - apiKey:
            - events:subscribe
      x-scope: events:subscribe
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - name: topics
          in: query
          required: true
          description: Comma-separated topic filters.
          schema:
            type: string
            minLength: 1
            maxLength: 4096
        - name: sinceSeq
          in: query
          description: Last seq the client has seen.
          schema:
            type: integer
            minimum: 0
      responses:
        '200':
          description: Page of events.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/eventPage'
              example:
                items:
                  - type: event
                    id: 0192f3a4-5b6c-7d8e-9f01-23456789b301
                    ts: '2026-10-19T12:00:00.123Z'
                    topic: venues/default/output
                    data:
                      seq: 48213
                      kind: output.state
                      resource:
                        brightness: 0.8
                        volume: 0.6
                        blackout: false
                nextCursor: null
                lastSeq: 48213
                resync: false
        '403':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /scenarios:
    get:
      operationId: listScenarios
      tags:
        - scenarios
      summary: List installed scenarios
      description: 'UC-SC1: id, version, title, params JSON Schema, capabilities, enabled.'
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of scenarios.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-12'
              example:
                items:
                  - id: karmic_clusters
                    version: 1.0.0
                    api: '1.0'
                    title:
                      en: Karmic clusters
                      ru: Кармические кластеры
                    enabled: true
                    paramsSchema:
                      type: object
                      properties:
                        durationS:
                          type: number
                          minimum: 60
                    capabilities:
                      - audioHybrid
                      - timers
                      - events
                    minRole: operator
                    sessions: 0
                nextCursor: null
        default:
          $ref: '#/components/responses/Problem'
  /scenarios/{scenarioId}:
    parameters:
      - $ref: '#/components/parameters/ScenarioId'
    get:
      operationId: getScenario
      tags:
        - scenarios
      summary: Get a scenario (with its params schema)
      description: UC-SC1.
      security:
        - bearer:
            - content:read
        - apiKey:
            - content:read
      x-scope: content:read
      responses:
        '200':
          description: The scenario.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scenario.schema'
              example:
                id: karmic_clusters
                version: 1.0.0
                api: '1.0'
                title:
                  en: Karmic clusters
                  ru: Кармические кластеры
                enabled: true
                paramsSchema:
                  type: object
                  properties:
                    durationS:
                      type: number
                      minimum: 60
                capabilities:
                  - audioHybrid
                  - timers
                  - events
                minRole: operator
                sessions: 0
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchScenario
      tags:
        - scenarios
      summary: Enable / disable a scenario plugin
      description: UC-SC5 (admin); disabling stops its running sessions.
      security:
        - bearer:
            - system:admin
        - apiKey:
            - system:admin
      x-scope: system:admin
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/patch-11'
            example:
              enabled: false
      responses:
        '200':
          description: Updated scenario.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scenario.schema'
              example:
                id: karmic_clusters
                version: 1.0.0
                api: '1.0'
                title:
                  en: Karmic clusters
                  ru: Кармические кластеры
                enabled: false
                paramsSchema:
                  type: object
                  properties:
                    durationS:
                      type: number
                      minimum: 60
                capabilities:
                  - audioHybrid
                  - timers
                  - events
                minRole: operator
                sessions: 0
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/scenario-sessions:
    parameters:
      - $ref: '#/components/parameters/VenueId'
    get:
      operationId: listScenarioSessions
      tags:
        - scenarios
      summary: List scenario sessions of the venue
      description: Running first, then recent.
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of sessions.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-13'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789b401
                    venueId: default
                    scenarioId: karmic_clusters
                    scenarioVersion: 1.0.0
                    status: running
                    params:
                      durationS: 600
                    owner: operator
                    progress: 0.1
                    phase: warm-up
                    startedAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: startScenarioSession
      tags:
        - scenarios
      summary: Start a scenario session
      description: 'UC-SC2: params validated against the scenario params schema; the session runs with the caller role (capabilities beyond it are refused, 403).'
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/start-2'
            example:
              scenarioId: karmic_clusters
              params:
                durationS: 600
      responses:
        '201':
          description: Started.
          headers:
            Location:
              $ref: '#/components/headers/Location'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scenario-session.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789b401
                venueId: default
                scenarioId: karmic_clusters
                scenarioVersion: 1.0.0
                status: starting
                params:
                  durationS: 600
                owner: operator
                progress: 0.1
                phase: warm-up
                startedAt: '2026-10-19T12:00:00.000Z'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/scenario-sessions/{sessionId}:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/SessionId'
    get:
      operationId: getScenarioSession
      tags:
        - scenarios
      summary: Get a scenario session
      description: 'UC-SC4: status, progress, phase.'
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      responses:
        '200':
          description: The session.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scenario-session.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789b401
                venueId: default
                scenarioId: karmic_clusters
                scenarioVersion: 1.0.0
                status: running
                params:
                  durationS: 600
                owner: operator
                progress: 0.1
                phase: warm-up
                startedAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/scenario-sessions/{sessionId}/pause:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/SessionId'
    post:
      operationId: pauseScenarioSession
      tags:
        - scenarios
      summary: Pause a scenario session
      description: UC-SC3.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Session after the command.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scenario-session.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789b401
                venueId: default
                scenarioId: karmic_clusters
                scenarioVersion: 1.0.0
                status: paused
                params:
                  durationS: 600
                owner: operator
                progress: 0.1
                phase: warm-up
                startedAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/scenario-sessions/{sessionId}/resume:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/SessionId'
    post:
      operationId: resumeScenarioSession
      tags:
        - scenarios
      summary: Resume a scenario session
      description: UC-SC3.
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Session after the command.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scenario-session.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789b401
                venueId: default
                scenarioId: karmic_clusters
                scenarioVersion: 1.0.0
                status: running
                params:
                  durationS: 600
                owner: operator
                progress: 0.1
                phase: warm-up
                startedAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/scenario-sessions/{sessionId}/stop:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/SessionId'
    post:
      operationId: stopScenarioSession
      tags:
        - scenarios
      summary: Stop a scenario session
      description: 'UC-SC3: the plugin must finish within 5 s, then the session is stopped.'
      security:
        - bearer:
            - playback:control
        - apiKey:
            - playback:control
      x-scope: playback:control
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Session after the command.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/scenario-session.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789b401
                venueId: default
                scenarioId: karmic_clusters
                scenarioVersion: 1.0.0
                status: stopped
                params:
                  durationS: 600
                owner: operator
                progress: 0.1
                phase: warm-up
                startedAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /venues/{venueId}/scenario-sessions/{sessionId}/log:
    parameters:
      - $ref: '#/components/parameters/VenueId'
      - $ref: '#/components/parameters/SessionId'
    get:
      operationId: getScenarioSessionLog
      tags:
        - scenarios
      summary: Scenario session log
      description: 'UC-SC4: log lines (also pushed as `scenarioSession.log`).'
      security:
        - bearer:
            - playback:read
        - apiKey:
            - playback:read
      x-scope: playback:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of log lines.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/logPage-2'
              example:
                items:
                  - seq: 1
                    ts: '2026-10-19T12:00:01.000Z'
                    level: info
                    message: 'phase warm-up: strikers armed'
                nextCursor: null
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /devices:
    get:
      operationId: listDevices
      tags:
        - devices
      summary: List visual device agents
      description: Discovered and paired devices with live state.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: Page of devices.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-14'
              example:
                items:
                  - id: vp-f-0012
                    serial: FDVPF0012
                    product: FD-VP-F
                    variant: fd-vp-f-1
                    hwRevision: B
                    agentVersion: 1.0.0
                    protocolVersion: freedome.device.v1
                    capabilities:
                      - power
                      - identify
                      - telemetry
                    address: 10.0.0.31:50071
                    paired: true
                    displayId: p1
                    state:
                      online: true
                      power: 'on'
                      temperatureC: 41.5
                      fanRpm: 2100
                      lastSeen: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        default:
          $ref: '#/components/responses/Problem'
  /devices/{deviceId}:
    parameters:
      - $ref: '#/components/parameters/DeviceId'
    get:
      operationId: getDevice
      tags:
        - devices
      summary: Get a visual device
      description: Identity, binding, live state.
      security:
        - bearer:
            - info:read
        - apiKey:
            - info:read
      x-scope: info:read
      responses:
        '200':
          description: The device.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/device.schema'
              example:
                id: vp-f-0012
                serial: FDVPF0012
                product: FD-VP-F
                variant: fd-vp-f-1
                hwRevision: B
                agentVersion: 1.0.0
                protocolVersion: freedome.device.v1
                capabilities:
                  - power
                  - identify
                  - telemetry
                address: 10.0.0.31:50071
                paired: true
                displayId: p1
                state:
                  online: true
                  power: 'on'
                  temperatureC: 41.5
                  fanRpm: 2100
                  lastSeen: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /devices/{deviceId}/pairing:
    parameters:
      - $ref: '#/components/parameters/DeviceId'
    post:
      operationId: beginDevicePairing
      tags:
        - devices
      summary: Begin pairing (device shows a code)
      description: The agent displays a short code; confirm it with confirmDevicePairing before `expiresAt`.
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Pairing started.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/pairingStarted'
              example:
                deviceId: vp-f-0012
                expiresAt: '2026-10-19T12:02:00.000Z'
                codeLength: 6
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '503':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /devices/{deviceId}/pairing/confirm:
    parameters:
      - $ref: '#/components/parameters/DeviceId'
    post:
      operationId: confirmDevicePairing
      tags:
        - devices
      summary: Confirm pairing with the shown code
      description: Wrong code → 422; three failures cancel the pairing.
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/pairingConfirm'
            example:
              code: '482913'
      responses:
        '200':
          description: Paired device.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/device.schema'
              example:
                id: vp-f-0012
                serial: FDVPF0012
                product: FD-VP-F
                variant: fd-vp-f-1
                hwRevision: B
                agentVersion: 1.0.0
                protocolVersion: freedome.device.v1
                capabilities:
                  - power
                  - identify
                  - telemetry
                address: 10.0.0.31:50071
                paired: true
                displayId: p1
                state:
                  online: true
                  power: 'on'
                  temperatureC: 41.5
                  fanRpm: 2100
                  lastSeen: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /devices/{deviceId}/unpair:
    parameters:
      - $ref: '#/components/parameters/DeviceId'
    post:
      operationId: unpairDevice
      tags:
        - devices
      summary: Unpair a device
      description: Forgets the device credentials and its display binding.
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /devices/{deviceId}/display:
    parameters:
      - $ref: '#/components/parameters/DeviceId'
    put:
      operationId: bindDevice
      tags:
        - devices
      summary: Bind the device to a display
      description: '`displayId: null` unbinds. Written into the venue (`revision`++).'
      security:
        - bearer:
            - venues:write
        - apiKey:
            - venues:write
      x-scope: venues:write
      parameters:
        - $ref: '#/components/parameters/IfMatch'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/bind'
            example:
              displayId: p1
      responses:
        '200':
          description: Device after binding.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/device.schema'
              example:
                id: vp-f-0012
                serial: FDVPF0012
                product: FD-VP-F
                variant: fd-vp-f-1
                hwRevision: B
                agentVersion: 1.0.0
                protocolVersion: freedome.device.v1
                capabilities:
                  - power
                  - identify
                  - telemetry
                address: 10.0.0.31:50071
                paired: true
                displayId: p1
                state:
                  online: true
                  power: 'on'
                  temperatureC: 41.5
                  fanRpm: 2100
                  lastSeen: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        '409':
          $ref: '#/components/responses/Problem'
        '412':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        '428':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /jobs:
    get:
      operationId: listJobs
      tags:
        - jobs
      summary: List jobs
      description: The caller's own jobs; with `system:read` all jobs. Newest first; filter by `kind` / `status`.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Cursor'
        - name: kind
          in: query
          description: Job kind filter.
          schema:
            type: string
            pattern: ^[a-z0-9][a-z0-9._-]{0,63}$
        - name: status
          in: query
          description: Status filter.
          schema:
            $ref: '#/components/schemas/status'
      responses:
        '200':
          description: Page of jobs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/page-15'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789abce
                    kind: import
                    status: succeeded
                    progress: 1
                    createdAt: '2026-10-19T12:00:00.000Z'
                    result:
                      showId: intro-show
                nextCursor: null
        default:
          $ref: '#/components/responses/Problem'
  /jobs/{jobId}:
    parameters:
      - $ref: '#/components/parameters/JobId'
    get:
      operationId: getJob
      tags:
        - jobs
      summary: Job state
      description: Visible to the principal that started it, and to `system:read`.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      responses:
        '200':
          description: Job.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/job.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789abce
                kind: export
                status: running
                progress: 0.42
                createdAt: '2026-10-19T12:00:00.000Z'
                links:
                  self: /api/v1/jobs/0192f3a4-5b6c-7d8e-9f01-23456789abce
                  cancel: /api/v1/jobs/0192f3a4-5b6c-7d8e-9f01-23456789abce/cancel
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /jobs/{jobId}/cancel:
    parameters:
      - $ref: '#/components/parameters/JobId'
    post:
      operationId: cancelJob
      tags:
        - jobs
      summary: Cancel a job
      description: Requests cancellation; finished jobs are returned unchanged.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Cancelled (or already finished).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/job.schema'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789abce
                kind: export
                status: cancelled
                createdAt: '2026-10-19T12:00:00.000Z'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
components:
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Access JWT from POST /auth/session or /auth/token-exchange (15 min), or an API key `fdk_…`.
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: fdk API key
      description: 'Service / kiosk / gateway API key `fdk_…` sent as `Authorization: Bearer fdk_…` (scoped, revocable, shown once).'
  parameters:
    IfNoneMatch:
      name: If-None-Match
      in: header
      description: ETag of a cached representation (304 when unchanged).
      schema:
        type: string
    Limit:
      name: limit
      in: query
      description: Page size (default 50, max 500).
      schema:
        $ref: '#/components/schemas/limit'
    Cursor:
      name: cursor
      in: query
      description: Opaque cursor from the previous page's `nextCursor`.
      schema:
        $ref: '#/components/schemas/cursor'
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      description: UUID; replays within 24 h return the original response, a different body with the same key → 409.
      schema:
        type: string
        format: uuid
    VenueId:
      name: venueId
      in: path
      required: true
      description: Venue id or the alias `default`.
      schema:
        $ref: '#/components/schemas/venueRef'
    IfMatchOrCreate:
      name: If-Match
      in: header
      description: 'Required to replace an existing resource (428 when missing, 412 when stale). To create with a client-chosen id send `If-None-Match: *` instead.'
      schema:
        type: string
    IfNoneMatchCreate:
      name: If-None-Match
      in: header
      description: '`*` = create only (412 if the id exists).'
      schema:
        type: string
        enum:
          - '*'
    IfMatch:
      name: If-Match
      in: header
      required: true
      description: Strong ETag of the representation being changed (428 when missing, 412 when stale).
      schema:
        type: string
    DisplayId:
      name: displayId
      in: path
      required: true
      description: Display id (`.dome` `displays[].id`).
      schema:
        $ref: '#/components/schemas/docId'
    CalibrationFile:
      name: file
      in: path
      required: true
      description: 'Calibration sidecar: `warp` (FDMESH), `blend` (16-bit PNG), `colour` (.cube 3D LUT).'
      schema:
        type: string
        enum:
          - warp
          - blend
          - colour
    JobId:
      name: jobId
      in: path
      required: true
      description: Job id (UUIDv7).
      schema:
        $ref: '#/components/schemas/uuidv7'
    PairingId:
      name: pairingId
      in: path
      required: true
      description: Pairing id (UUIDv7).
      schema:
        $ref: '#/components/schemas/uuidv7'
    Query:
      name: q
      in: query
      description: Full-text query where supported.
      schema:
        type: string
        maxLength: 256
    UserId:
      name: userId
      in: path
      required: true
      description: User id (UUIDv7).
      schema:
        $ref: '#/components/schemas/uuidv7'
    KeyId:
      name: keyId
      in: path
      required: true
      description: API key id (the `<id>` of `fdk_<id>_<secret>`).
      schema:
        $ref: '#/components/schemas/keyId'
    Since:
      name: since
      in: query
      description: Only entries at or after this time (RFC 3339).
      schema:
        $ref: '#/components/schemas/timestamp'
    NodeId:
      name: nodeId
      in: path
      required: true
      description: Cluster node id.
      schema:
        $ref: '#/components/schemas/docId'
    Sort:
      name: sort
      in: query
      description: Comma-separated fields, `-` prefix = descending.
      schema:
        $ref: '#/components/schemas/sort'
    ShowId:
      name: showId
      in: path
      required: true
      description: Show id (UUIDv7 for server-created shows, or the `.dome` `meta.id` of an imported show).
      schema:
        $ref: '#/components/schemas/docId'
    DocItemId:
      name: itemId
      in: path
      required: true
      description: Document-scoped id of the playlist / timeline / cue / trigger inside the show.
      schema:
        $ref: '#/components/schemas/docId'
    PlayerId:
      name: playerId
      in: path
      required: true
      description: Media player id or the alias `main`.
      schema:
        $ref: '#/components/schemas/playerRef'
    CueId:
      name: cueId
      in: path
      required: true
      description: Cue id (`.dome` `show.cues[].id`).
      schema:
        $ref: '#/components/schemas/docId'
    ParameterPath:
      name: path
      in: path
      required: true
      description: Dotted parameter path, e.g. `master.brightness`, `players.main.opacity`.
      schema:
        type: string
        pattern: ^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+){0,15}$
        maxLength: 256
    TusResumable:
      name: Tus-Resumable
      in: header
      required: true
      description: tus protocol version used by the client (`1.0.0`); other values → 412 with `Tus-Version`.
      schema:
        type: string
        enum:
          - 1.0.0
    UploadChecksum:
      name: Upload-Checksum
      in: header
      description: '`sha256 <base64 digest of this chunk>`; mismatch → 460 and the chunk is discarded.'
      schema:
        type: string
        pattern: ^sha256 [A-Za-z0-9+/]+={0,2}$
    UploadId:
      name: uploadId
      in: path
      required: true
      description: tus upload id (UUIDv7).
      schema:
        $ref: '#/components/schemas/uuidv7'
    UploadOffsetReq:
      name: Upload-Offset
      in: header
      required: true
      description: Offset the chunk starts at; must equal the server offset (409 otherwise).
      schema:
        type: integer
        minimum: 0
    Count:
      name: count
      in: query
      description: Return `count` where cheap.
      schema:
        type: boolean
        default: false
    AssetId:
      name: assetId
      in: path
      required: true
      description: Asset id (UUIDv7, or the `.dome` `assets[].id` of an imported asset).
      schema:
        $ref: '#/components/schemas/docId'
    Force:
      name: force
      in: query
      description: Proceed although the resource is referenced (cues referencing it are reported failed).
      schema:
        type: boolean
        default: false
    Range:
      name: Range
      in: header
      description: Byte range (RFC 9110), e.g. `bytes=0-1048575`.
      schema:
        type: string
    ZoneId:
      name: zoneId
      in: path
      required: true
      description: Audio zone id.
      schema:
        $ref: '#/components/schemas/docId'
    AudioDeviceId:
      name: deviceId
      in: path
      required: true
      description: Audio device id (`.dome` `audio.devices[].id`).
      schema:
        $ref: '#/components/schemas/docId'
    PresetId:
      name: presetId
      in: path
      required: true
      description: Audio preset id.
      schema:
        $ref: '#/components/schemas/docId'
    SessionId:
      name: sessionId
      in: path
      required: true
      description: Session id (UUIDv7).
      schema:
        $ref: '#/components/schemas/uuidv7'
    SourceId:
      name: sourceId
      in: path
      required: true
      description: Interactive / realtime audio source id.
      schema:
        $ref: '#/components/schemas/docId'
    SceneId:
      name: sceneId
      in: path
      required: true
      description: AudioReactive scene id.
      schema:
        $ref: '#/components/schemas/docId'
    EngineName:
      name: name
      in: path
      required: true
      description: 'Engine name: `visual`, `anantasound`, `anantadigital`, or `<kind>@<node>` on multi-node installations.'
      schema:
        anyOf:
          - enum:
              - visual
              - anantasound
              - anantadigital
          - $ref: '#/components/schemas/engineName'
    GatewayId:
      name: gatewayId
      in: path
      required: true
      description: Gateway id (e.g. `screenberry`, `dome360`, `osc-out-1`).
      schema:
        $ref: '#/components/schemas/docId'
    AppId:
      name: app
      in: path
      required: true
      description: Application id (reverse-DNS store id or slug), e.g. `net.nativemind.starveda`.
      schema:
        type: string
        pattern: ^[A-Za-z0-9._-]{1,128}$
    ClientId:
      name: clientId
      in: path
      required: true
      description: Registered client id (UUIDv7).
      schema:
        $ref: '#/components/schemas/uuidv7'
    ScenarioId:
      name: scenarioId
      in: path
      required: true
      description: Scenario plugin id, e.g. `karmic_clusters`.
      schema:
        type: string
        pattern: ^[a-z][a-z0-9_]{0,63}$
    DeviceId:
      name: deviceId
      in: path
      required: true
      description: Visual device agent id (`freedome.device.v1` device, e.g. a projector unit).
      schema:
        $ref: '#/components/schemas/docId'
  schemas:
    code:
      description: 'Stable machine key; also the last path segment of `type`. Open set: see knownCode.'
      type: string
      pattern: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$
    fieldError:
      type: object
      required:
        - pointer
        - detail
      properties:
        pointer:
          type: string
          format: json-pointer
        detail:
          type: string
        code:
          $ref: '#/components/schemas/code'
      additionalProperties: true
    problem.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/common/1.0/problem.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). RFC 9457 problem details with the Freedome `code` catalogue, 02e §3.3. Also the `data` of WS `error`, the socket.io ack error and GraphQL errors[].extensions.problem. The catalogue is open: unknown codes are allowed; known codes are bound to their HTTP status.'
      title: Problem details
      type: object
      required:
        - type
        - title
        - code
      properties:
        type:
          type: string
          format: uri
          description: https://freedome.nativemind.us/problems/<code> for catalogue codes.
        title:
          type: string
          minLength: 1
        status:
          type: integer
          minimum: 100
          maximum: 599
        detail:
          type: string
        instance:
          type: string
          format: uri-reference
        code:
          $ref: '#/components/schemas/code'
        traceId:
          type: string
          pattern: ^[0-9a-f]{16,32}$
        errors:
          type: array
          items:
            $ref: '#/components/schemas/fieldError'
      allOf:
        - if:
            properties:
              code:
                const: validation-failed
            required:
              - code
          then:
            properties:
              status:
                const: 422
        - if:
            properties:
              code:
                const: not-found
            required:
              - code
          then:
            properties:
              status:
                const: 404
        - if:
            properties:
              code:
                const: conflict
            required:
              - code
          then:
            properties:
              status:
                const: 409
        - if:
            properties:
              code:
                const: precondition-failed
            required:
              - code
          then:
            properties:
              status:
                const: 412
        - if:
            properties:
              code:
                const: precondition-required
            required:
              - code
          then:
            properties:
              status:
                const: 428
        - if:
            properties:
              code:
                const: unauthenticated
            required:
              - code
          then:
            properties:
              status:
                const: 401
        - if:
            properties:
              code:
                const: forbidden
            required:
              - code
          then:
            properties:
              status:
                const: 403
        - if:
            properties:
              code:
                const: rate-limited
            required:
              - code
          then:
            properties:
              status:
                const: 429
        - if:
            properties:
              code:
                const: payload-too-large
            required:
              - code
          then:
            properties:
              status:
                const: 413
        - if:
            properties:
              code:
                const: unsupported-format
            required:
              - code
          then:
            properties:
              status:
                const: 415
        - if:
            properties:
              code:
                const: engine-unavailable
            required:
              - code
          then:
            properties:
              status:
                const: 503
        - if:
            properties:
              code:
                const: engine-incompatible
            required:
              - code
          then:
            properties:
              status:
                const: 503
        - if:
            properties:
              code:
                const: degraded
            required:
              - code
          then:
            properties:
              status:
                const: 503
        - if:
            properties:
              code:
                const: upload-checksum-mismatch
            required:
              - code
          then:
            properties:
              status:
                const: 422
        - if:
            properties:
              code:
                const: legacy-unsupported
            required:
              - code
          then:
            properties:
              status:
                const: 501
        - if:
            properties:
              code:
                const: internal
            required:
              - code
          then:
            properties:
              status:
                const: 500
        - if:
            properties:
              code:
                const: clock-unlocked
            required:
              - code
          then:
            properties:
              status:
                const: 409
        - if:
            properties:
              code:
                const: not-configured
            required:
              - code
          then:
            properties:
              status:
                const: 503
        - if:
            properties:
              code:
                const: authorization-pending
            required:
              - code
          then:
            properties:
              status:
                const: 400
        - if:
            properties:
              code:
                const: slow-down
            required:
              - code
          then:
            properties:
              status:
                const: 400
        - if:
            properties:
              code:
                const: access-denied
            required:
              - code
          then:
            properties:
              status:
                const: 403
        - if:
            properties:
              code:
                const: expired-token
            required:
              - code
          then:
            properties:
              status:
                const: 400
        - if:
            properties:
              code:
                const: token-reused
            required:
              - code
          then:
            properties:
              status:
                const: 401
        - if:
            properties:
              code:
                const: idempotency-key-reused
            required:
              - code
          then:
            properties:
              status:
                const: 409
        - if:
            properties:
              code:
                const: idempotency-in-progress
            required:
              - code
          then:
            properties:
              status:
                const: 409
      additionalProperties: true
      examples:
        - type: https://freedome.nativemind.us/problems/engine-unavailable
          title: Visual engine unavailable
          status: 503
          detail: freedome-engined not reachable at unix:/run/freedome/engine.sock
          instance: /api/v1/venues/default/players/main/play
          code: engine-unavailable
          traceId: 4bf92f3577b34da6a3ce929d0e0e4736
      $defs:
        knownCode:
          enum:
            - validation-failed
            - not-found
            - conflict
            - precondition-failed
            - precondition-required
            - unauthenticated
            - forbidden
            - rate-limited
            - payload-too-large
            - unsupported-format
            - engine-unavailable
            - engine-incompatible
            - degraded
            - job-failed
            - upload-checksum-mismatch
            - legacy-unsupported
            - internal
            - clock-unlocked
            - not-configured
            - authorization-pending
            - slow-down
            - access-denied
            - expired-token
            - token-reused
            - idempotency-key-reused
            - idempotency-in-progress
        code:
          description: 'Stable machine key; also the last path segment of `type`. Open set: see knownCode.'
          type: string
          pattern: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$
        fieldError:
          type: object
          required:
            - pointer
            - detail
          properties:
            pointer:
              type: string
              format: json-pointer
            detail:
              type: string
            code:
              $ref: '#/components/schemas/code'
          additionalProperties: true
    semver:
      description: Semantic version 2.0.0.
      type: string
      pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$
    uuidv7:
      description: 'Server-owned resource id: UUIDv7, lowercase, sortable by creation time.'
      type: string
      pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      examples:
        - 0192f3a4-5b6c-7d8e-9f01-23456789abcd
    docId:
      description: Document-scoped id (.dome displays[].id, assets[].id, playlist / cue ids); unique within its parent.
      type: string
      pattern: ^[A-Za-z0-9._-]{1,64}$
      examples:
        - p1
        - intro.mp4
        - cue-01
    i18n-text.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/common/1.0/i18n-text.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Localised text map {"en":…,"ru":…}, 02e §3.1 (as .dome meta.title).
      title: Localised text
      type: object
      minProperties: 1
      propertyNames:
        pattern: ^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$
      additionalProperties:
        type: string
      examples:
        - en: Intro
          ru: Вступление
      $defs:
        textOrI18n:
          description: Plain string (treated as `en`) or a localised map. (map branch = the root schema, inlined for tools that mis-handle self references)
          anyOf:
            - type: string
            - type: object
              minProperties: 1
              propertyNames:
                pattern: ^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$
              additionalProperties:
                type: string
              examples:
                - en: Intro
                  ru: Вступление
    info.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/info.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). GET /api/v1/info (02e §5.1) and the unversioned /health, /ready bodies ($defs/health).
      title: Server info
      type: object
      required:
        - product
        - serverVersion
        - instanceId
        - apiVersions
      properties:
        product:
          anyOf:
            - enum:
                - freedome-mediaserver
                - freedome-cloud
            - type: string
              minLength: 1
        serverVersion:
          $ref: '#/components/schemas/semver'
        contractVersion:
          $ref: '#/components/schemas/semver'
          description: freedome_contract release the server implements
        instanceId:
          $ref: '#/components/schemas/uuidv7'
        name:
          type: string
        apiVersions:
          type: array
          minItems: 1
          items:
            type: string
            pattern: ^v\d+$
        legacyProfile:
          type: boolean
          description: legacy (flutter_freedome 3.0 / razvivalka) listener enabled
        transports:
          type: object
          properties:
            rest:
              type: object
              properties:
                basePath:
                  type: string
              additionalProperties: true
            graphql:
              type: object
              properties:
                enabled:
                  type: boolean
                path:
                  type: string
              additionalProperties: true
            ws:
              type: object
              properties:
                path:
                  type: string
                subprotocol:
                  type: string
              additionalProperties: true
            socketio:
              type: object
              properties:
                path:
                  type: string
                namespace:
                  type: string
              additionalProperties: true
            osc:
              type: object
              properties:
                udpIn:
                  type: integer
                  minimum: 1
                  maximum: 65535
                udpOut:
                  type: integer
                  minimum: 1
                  maximum: 65535
                tcp:
                  type: integer
                  minimum: 1
                  maximum: 65535
              additionalProperties: true
          additionalProperties: true
        auth:
          type: object
          properties:
            methods:
              type: array
              items:
                anyOf:
                  - enum:
                      - password
                      - apiKey
                      - pairing
                      - cloud
                      - anonymous
                  - type: string
          additionalProperties: true
        venues:
          type: array
          items:
            type: object
            required:
              - id
            properties:
              id:
                $ref: '#/components/schemas/docId'
              title:
                $ref: '#/components/schemas/i18n-text.schema'
              default:
                type: boolean
              status:
                type: string
            additionalProperties: true
        capabilities:
          description: Engines, audio modes, display types, test patterns, gateways … (open object; clients ignore unknown keys).
          type: object
          additionalProperties: true
        links:
          type: object
          additionalProperties:
            type: string
            format: uri-reference
      additionalProperties: true
      $defs:
        health:
          description: GET /health and /ready (unversioned, anonymous).
          type: object
          required:
            - status
          properties:
            status:
              enum:
                - ok
                - degraded
                - down
            checks:
              type: object
              additionalProperties:
                type: object
                required:
                  - status
                properties:
                  status:
                    enum:
                      - ok
                      - degraded
                      - down
                  detail:
                    type: string
                additionalProperties: false
          additionalProperties: false
    health:
      description: GET /health and /ready (unversioned, anonymous).
      type: object
      required:
        - status
      properties:
        status:
          enum:
            - ok
            - degraded
            - down
        checks:
          type: object
          additionalProperties:
            type: object
            required:
              - status
            properties:
              status:
                enum:
                  - ok
                  - degraded
                  - down
              detail:
                type: string
            additionalProperties: false
      additionalProperties: false
    limit:
      type: integer
      minimum: 1
      maximum: 500
      default: 50
    cursor:
      description: Opaque continuation token.
      type: string
      minLength: 1
      maxLength: 1024
    pagination.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/common/1.0/pagination.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Cursor pagination, 02e §3.4. Root = collection page; specialise with allOf + items.
      title: Collection page
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
        nextCursor:
          anyOf:
            - $ref: '#/components/schemas/cursor'
            - type: 'null'
        count:
          type: integer
          minimum: 0
          description: Only with ?count=true where cheap.
      additionalProperties: true
      $defs:
        limit:
          type: integer
          minimum: 1
          maximum: 500
          default: 50
        cursor:
          description: Opaque continuation token.
          type: string
          minLength: 1
          maxLength: 1024
        sort:
          description: Comma-separated fields, `-` prefix = descending.
          type: string
          pattern: ^-?[a-zA-Z][a-zA-Z0-9]*(,-?[a-zA-Z][a-zA-Z0-9]*)*$
    uuid:
      description: Any RFC 9562 UUID, lowercase (e.g. .dome meta.id written by older tools).
      type: string
      pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
    meta:
      type: object
      required:
        - id
        - title
      properties:
        id:
          description: Stable document id (UUID, lowercase); the venue / show id on import (02e §3.2).
          $ref: '#/components/schemas/uuid'
        title:
          $ref: '#/components/schemas/i18n-text.schema'
        description:
          $ref: '#/components/schemas/i18n-text.schema'
        authors:
          type: array
          items:
            type: object
            required:
              - name
            properties:
              name:
                type: string
                minLength: 1
              email:
                type: string
                format: email
              url:
                type: string
                format: uri
              role:
                type: string
            patternProperties:
              ^x-: true
            additionalProperties: false
        created:
          type: string
          format: date-time
        modified:
          type: string
          format: date-time
        generator:
          type: object
          required:
            - name
          properties:
            name:
              type: string
              minLength: 1
            version:
              type: string
          patternProperties:
            ^x-: true
          additionalProperties: false
        license:
          type: string
          minLength: 1
          default: NativeMindNONC
        tags:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 64
          uniqueItems: true
      patternProperties:
        ^x-: true
      additionalProperties: false
    token:
      description: 'Value of an open enum: one of the listed values or any other token (clients tolerate unknown values).'
      type: string
      pattern: ^[A-Za-z0-9][A-Za-z0-9._:+-]{0,63}$
    vec3:
      type: array
      items:
        type: number
      minItems: 3
      maxItems: 3
      description: '[x, y, z], metres, dome frame (01b §3).'
    dome:
      description: Dome geometry (01b §4.1). Right-handed, metres, degrees; +Y zenith, -Z front.
      type: object
      required:
        - mode
        - shape
        - radius
      properties:
        mode:
          anyOf:
            - enum:
                - classical
                - quantum
            - $ref: '#/components/schemas/token'
        shape:
          anyOf:
            - enum:
                - hemisphere
                - sphere
                - truncated
                - cylinder
                - cylinder_outer
                - cone
                - pyramid
                - geodesic
                - flat
                - multiwall
                - cave
                - volumetric
            - $ref: '#/components/schemas/token'
        radius:
          type: number
          exclusiveMinimum: 0
          description: m
        tilt:
          type: number
          minimum: -90
          maximum: 90
          description: ° forward tilt of the dome axis
        truncation:
          type: number
          minimum: -90
          maximum: 90
          description: ° below the spring line projected (negative = cut off)
        orientation:
          anyOf:
            - enum:
                - upright
                - inverted
                - tilted
            - $ref: '#/components/schemas/token'
        springLineHeight:
          type: number
          description: m above floor
        surface:
          type: object
          properties:
            gain:
              type: number
              minimum: 0
            material:
              type: string
          patternProperties:
            ^x-: true
          additionalProperties: false
        seating:
          anyOf:
            - enum:
                - unidirectional
                - concentric
                - free
            - $ref: '#/components/schemas/token'
        sweetSpot:
          $ref: '#/components/schemas/vec3'
        angularBase:
          type: integer
          minimum: 1
          description: 'Quantum mode only: presentation base of the circle (e.g. 108); values stay in degrees.'
      patternProperties:
        ^x-: true
      additionalProperties: false
      if:
        required:
          - angularBase
      then:
        properties:
          mode:
            const: quantum
    id:
      description: Document-scoped id (02e §3.2).
      $ref: '#/components/schemas/docId'
    size2:
      type: array
      items:
        type: integer
        minimum: 1
      minItems: 2
      maxItems: 2
      description: '[width, height] or [cols, rows].'
    vec2:
      type: array
      items:
        type: number
      minItems: 2
      maxItems: 2
    lens:
      type: object
      required:
        - kind
      properties:
        kind:
          anyOf:
            - enum:
                - fisheye
                - standard
                - pyramid
                - prism
                - equirect
            - $ref: '#/components/schemas/token'
        fov:
          type: number
          exclusiveMinimum: 0
          maximum: 360
          description: °
        throwRatio:
          type:
            - number
            - 'null'
          exclusiveMinimum: 0
        shift:
          $ref: '#/components/schemas/vec2'
          description: lens shift [x, y] as a fraction of the image size
        distortion:
          description: '1.1 (02b E4): lens distortion model.'
          type: object
          required:
            - model
          properties:
            model:
              anyOf:
                - enum:
                    - brown-conrady
                    - fisheye-equidistant
                    - fisheye-equisolid
                    - fisheye-poly
                    - radial
                - $ref: '#/components/schemas/token'
            coefficients:
              type: array
              items:
                type: number
              maxItems: 16
              description: brown-conrady [k1,k2,k3,p1,p2]; fisheye-poly [k1..k4]; radial [k1,k2,k3] (01b §4.2.1)
            centre:
              type: array
              items:
                type: number
                minimum: 0
                maximum: 1
              minItems: 2
              maxItems: 2
              description: distortion centre, normalised image coordinates
          patternProperties:
            ^x-: true
          additionalProperties: false
        profileRef:
          description: '1.1 (02b E4): lens profile id shipped as data (engine data/lens_profiles/<id>.json), e.g. sh001.'
          type: string
          pattern: ^[a-z0-9][a-z0-9._-]{0,63}$
        curvature:
          description: '1.1 (EN, curved LED walls): the display surface is a cylinder (axis vertical = curved horizontally, the usual curved wall) or a sphere section; absent = flat.'
          type: object
          required:
            - radius
          properties:
            radius:
              type: number
              exclusiveMinimum: 0
              description: m, curvature radius of the panel surface
            axis:
              anyOf:
                - enum:
                    - vertical
                    - horizontal
                    - spherical
                - $ref: '#/components/schemas/token'
              default: vertical
            concave:
              type: boolean
              default: true
              description: true = curved towards the audience
          patternProperties:
            ^x-: true
          additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
    quat:
      type: array
      items:
        type: number
        minimum: -1
        maximum: 1
      minItems: 4
      maxItems: 4
      description: Rotation quaternion [x, y, z, w].
    pose:
      type: object
      properties:
        position:
          $ref: '#/components/schemas/vec3'
        rotation:
          $ref: '#/components/schemas/quat'
      patternProperties:
        ^x-: true
      additionalProperties: false
    rect:
      description: Normalised [x, y, w, h] of the display frame (0..1, origin top-left, y down; 01b §3).
      type: array
      items:
        type: number
        minimum: 0
        maximum: 1
      minItems: 4
      maxItems: 4
    optics:
      description: '1.1 (02a V12, replaces x-freedome-optics): per-face layout of prism / pyramid optics on the display frame.'
      type: object
      required:
        - faces
      properties:
        faces:
          enum:
            - 1
            - 3
            - 4
            - 6
        faceAngle:
          type: number
          exclusiveMinimum: 0
          exclusiveMaximum: 90
          description: ° between a face and the screen plane
        viewports:
          type: array
          maxItems: 6
          items:
            type: object
            required:
              - face
              - rect
            properties:
              face:
                type: integer
                minimum: 0
                maximum: 5
                description: 0 = front, clockwise from above
              rect:
                $ref: '#/components/schemas/rect'
              rotation:
                type: number
                minimum: -360
                maximum: 360
                description: ° clockwise
              mirror:
                enum:
                  - none
                  - horizontal
                  - vertical
            patternProperties:
              ^x-: true
            additionalProperties: false
        activeRegion:
          $ref: '#/components/schemas/rect'
          description: lit region; outside is black
      patternProperties:
        ^x-: true
      additionalProperties: false
    prism:
      description: '1.1 (02b E4): physical prism / pyramid geometry (engine PrismGeometry).'
      type: object
      required:
        - faces
      properties:
        faces:
          type: integer
          minimum: 1
          maximum: 12
        baseMm:
          type: number
          exclusiveMinimum: 0
        heightMm:
          type: number
          exclusiveMinimum: 0
        faceAngle:
          type: number
          exclusiveMinimum: 0
          exclusiveMaximum: 90
        refractiveIndex:
          type: number
          minimum: 1
        solid:
          type: boolean
      patternProperties:
        ^x-: true
      additionalProperties: false
    display:
      description: 'Visual device (01b §4.2): projector (incl. fisheye), LED, prism unit, smartphone + lens, NDI output.'
      type: object
      required:
        - id
        - type
      properties:
        id:
          $ref: '#/components/schemas/id'
        name:
          type: string
        type:
          anyOf:
            - enum:
                - projector
                - led
                - prism_unit
                - smartphone_lens
                - ndi_output
            - $ref: '#/components/schemas/token'
        model:
          type: object
          properties:
            vendor:
              type: string
            product:
              type: string
            variant:
              type: string
          patternProperties:
            ^x-: true
          additionalProperties: false
        resolution:
          $ref: '#/components/schemas/size2'
        refreshRate:
          type: number
          exclusiveMinimum: 0
          description: Hz
        lens:
          $ref: '#/components/schemas/lens'
        pose:
          $ref: '#/components/schemas/pose'
        output:
          type: object
          properties:
            host:
              type: string
              minLength: 1
            connector:
              type: string
              minLength: 1
              description: e.g. DP-1, HDMI-1, stream:<displayId>
          patternProperties:
            ^x-: true
          additionalProperties: false
        colour:
          type: object
          properties:
            gamut:
              anyOf:
                - enum:
                    - rec709
                    - dci-p3
                    - display-p3
                    - rec2020
                    - srgb
                - $ref: '#/components/schemas/token'
            gamma:
              type: number
              exclusiveMinimum: 0
            whitePoint:
              type: string
          patternProperties:
            ^x-: true
          additionalProperties: false
        stereo:
          oneOf:
            - type: 'null'
            - type: object
              required:
                - eye
              properties:
                eye:
                  enum:
                    - left
                    - right
              patternProperties:
                ^x-: true
              additionalProperties: false
        optics:
          $ref: '#/components/schemas/optics'
        prism:
          $ref: '#/components/schemas/prism'
      patternProperties:
        ^x-: true
      additionalProperties: false
    path:
      description: 'Entry path inside the container: relative, `/`-separated, no `..` segment, no backslash, no NUL.'
      type: string
      minLength: 1
      maxLength: 1024
      pattern: ^(?![/.]/?$)(?!/)(?!.*(?:^|/)\.\.(?:/|$))[^\\\u0000:]+$
    unit:
      type: number
      minimum: 0
      maximum: 1
    displayCalibration:
      type: object
      properties:
        warp:
          type: object
          required:
            - file
          properties:
            file:
              $ref: '#/components/schemas/path'
              description: FDMESH file (01b §5)
            kind:
              anyOf:
                - enum:
                    - uv-grid
                - $ref: '#/components/schemas/token'
            size:
              $ref: '#/components/schemas/size2'
              description: '[cols, rows]; must match the FDMESH header'
          patternProperties:
            ^x-: true
          additionalProperties: false
        blend:
          type: object
          required:
            - file
          properties:
            file:
              $ref: '#/components/schemas/path'
              description: 16-bit greyscale PNG
            bitDepth:
              enum:
                - 8
                - 16
            gamma:
              type: number
              exclusiveMinimum: 0
          patternProperties:
            ^x-: true
          additionalProperties: false
        colour:
          type: object
          properties:
            lut:
              $ref: '#/components/schemas/path'
              description: Adobe / Resolve .cube 3D LUT
            blackLevel:
              $ref: '#/components/schemas/unit'
          patternProperties:
            ^x-: true
          additionalProperties: false
        report:
          type: object
          properties:
            method:
              anyOf:
                - enum:
                    - camera-homography
                    - manual
                    - imported
                    - model
                - $ref: '#/components/schemas/token'
            date:
              type: string
              format: date-time
            meanError:
              type: number
              minimum: 0
            maxError:
              type: number
              minimum: 0
            unit:
              anyOf:
                - enum:
                    - px
                    - deg
                    - mm
                - $ref: '#/components/schemas/token'
            file:
              $ref: '#/components/schemas/path'
          patternProperties:
            ^x-: true
          additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
    calibration:
      description: Per-display calibration (01b §4.3); keys of `displays` are display ids (checked by the codecs).
      type: object
      properties:
        displays:
          type: object
          propertyNames:
            pattern: ^[A-Za-z0-9._-]{1,64}$
          additionalProperties:
            $ref: '#/components/schemas/displayCalibration'
      patternProperties:
        ^x-: true
      additionalProperties: false
    gainDb:
      type: number
      minimum: -144
      maximum: 24
    speaker:
      type: object
      required:
        - id
        - azimuth
        - elevation
      properties:
        id:
          $ref: '#/components/schemas/id'
        azimuth:
          type: number
          minimum: -360
          maximum: 360
          description: ° (0 = front, clockwise from above)
        elevation:
          type: number
          minimum: -90
          maximum: 90
          description: ° (0 = spring line)
        distance:
          type: number
          exclusiveMinimum: 0
          description: m
        channel:
          type: integer
          minimum: 1
        delayMs:
          type: number
          minimum: 0
        gainDb:
          $ref: '#/components/schemas/gainDb'
        kind:
          anyOf:
            - enum:
                - fullrange
                - subwoofer
                - satellite
            - $ref: '#/components/schemas/token'
        lfe:
          type: boolean
      patternProperties:
        ^x-: true
      additionalProperties: false
    audioDevice:
      type: object
      required:
        - id
        - kind
      properties:
        id:
          $ref: '#/components/schemas/id'
        name:
          type: string
        kind:
          anyOf:
            - enum:
                - qrd
                - patefon
                - karmic_cluster
                - speaker_amp
                - microphone
            - $ref: '#/components/schemas/token'
        address:
          type: string
          minLength: 1
          description: e.g. osc://10.0.0.20:9000
      patternProperties:
        ^x-: true
      additionalProperties: false
    audio:
      description: AnantaSound layout (01b §4.4).
      type: object
      properties:
        ambisonics:
          type: object
          properties:
            order:
              type: integer
              minimum: 0
              maximum: 7
            normalisation:
              anyOf:
                - enum:
                    - SN3D
                    - N3D
                    - FuMa
                - $ref: '#/components/schemas/token'
            channelOrder:
              anyOf:
                - enum:
                    - ACN
                    - FuMa
                - $ref: '#/components/schemas/token'
          patternProperties:
            ^x-: true
          additionalProperties: false
        decoder:
          type: object
          properties:
            kind:
              anyOf:
                - enum:
                    - allrad
                    - sad
                    - mmd
                    - epad
                - $ref: '#/components/schemas/token'
            dualBand:
              type: boolean
          patternProperties:
            ^x-: true
          additionalProperties: false
        speakers:
          type: array
          items:
            $ref: '#/components/schemas/speaker'
        roomCorrection:
          type: object
          required:
            - file
          properties:
            file:
              $ref: '#/components/schemas/path'
            kind:
              anyOf:
                - enum:
                    - fir
                    - iir
                    - peq
                - $ref: '#/components/schemas/token'
          patternProperties:
            ^x-: true
          additionalProperties: false
        devices:
          type: array
          items:
            $ref: '#/components/schemas/audioDevice'
      patternProperties:
        ^x-: true
      additionalProperties: false
    compat:
      description: Declarative mappings for the A6 gateways (01b §4.7); never required for playback.
      type: object
      propertyNames:
        pattern: ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$
      additionalProperties:
        type: object
    body:
      description: 'Venue content shared by GET / PUT / POST: the .dome venue sections.'
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/meta'
        dome:
          $ref: '#/components/schemas/dome'
        displays:
          type: array
          items:
            $ref: '#/components/schemas/display'
        calibration:
          $ref: '#/components/schemas/calibration'
        audio:
          $ref: '#/components/schemas/audio'
        compat:
          $ref: '#/components/schemas/compat'
      patternProperties:
        ^x-: true
    revision:
      description: Monotonic resource revision; the strong ETag is derived from it.
      type: integer
      minimum: 0
    timestamp:
      description: RFC 3339 UTC with milliseconds, e.g. 2026-10-19T12:00:00.000Z.
      type: string
      format: date-time
      pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$
    venue:
      type: object
      allOf:
        - $ref: '#/components/schemas/body'
      required:
        - id
        - revision
        - status
        - meta
        - dome
        - displays
      properties:
        id:
          $ref: '#/components/schemas/docId'
          description: venue id (= meta.id for venues created from a .dome)
        revision:
          $ref: '#/components/schemas/revision'
        status:
          anyOf:
            - enum:
                - idle
                - loading
                - ready
                - playing
                - degraded
                - error
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        default:
          type: boolean
          description: true for the venue the alias `default` resolves to
        activeShowId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        createdAt:
          $ref: '#/components/schemas/timestamp'
        updatedAt:
          $ref: '#/components/schemas/timestamp'
      unevaluatedProperties: false
    page:
      description: GET /venues page.
      type: object
      allOf:
        - $ref: '#/components/schemas/pagination.schema'
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/venue'
    input:
      description: POST /venues and PUT /venues/{venueId} body.
      type: object
      allOf:
        - $ref: '#/components/schemas/body'
      required:
        - meta
        - dome
        - displays
      unevaluatedProperties: false
    venue.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/venue.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Venue resource (02e §4, §5.2) = .dome 1.1 venue manifest without format / kind ($defs reused from dome/1.1) + server fields. Root = GET representation.
      title: Venue
      allOf:
        - $ref: '#/components/schemas/venue'
      $defs:
        venue:
          type: object
          allOf:
            - $ref: '#/components/schemas/body'
          required:
            - id
            - revision
            - status
            - meta
            - dome
            - displays
          properties:
            id:
              $ref: '#/components/schemas/docId'
              description: venue id (= meta.id for venues created from a .dome)
            revision:
              $ref: '#/components/schemas/revision'
            status:
              anyOf:
                - enum:
                    - idle
                    - loading
                    - ready
                    - playing
                    - degraded
                    - error
                - type: string
                  pattern: ^[a-z][a-z_-]{0,31}$
            default:
              type: boolean
              description: true for the venue the alias `default` resolves to
            activeShowId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            createdAt:
              $ref: '#/components/schemas/timestamp'
            updatedAt:
              $ref: '#/components/schemas/timestamp'
          unevaluatedProperties: false
        body:
          description: 'Venue content shared by GET / PUT / POST: the .dome venue sections.'
          type: object
          properties:
            meta:
              $ref: '#/components/schemas/meta'
            dome:
              $ref: '#/components/schemas/dome'
            displays:
              type: array
              items:
                $ref: '#/components/schemas/display'
            calibration:
              $ref: '#/components/schemas/calibration'
            audio:
              $ref: '#/components/schemas/audio'
            compat:
              $ref: '#/components/schemas/compat'
          patternProperties:
            ^x-: true
        input:
          description: POST /venues and PUT /venues/{venueId} body.
          type: object
          allOf:
            - $ref: '#/components/schemas/body'
          required:
            - meta
            - dome
            - displays
          unevaluatedProperties: false
        patch:
          description: PATCH body (application/merge-patch+json, RFC 7396) over the venue sections; the merged result must be a valid input.
          type: object
          minProperties: 1
          propertyNames:
            pattern: ^(meta|dome|displays|calibration|audio|compat|x-.+)$
        page:
          description: GET /venues page.
          type: object
          allOf:
            - $ref: '#/components/schemas/pagination.schema'
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/venue'
    venueRef:
      description: 'Venue id in a path: a stored venue id or the alias `default`.'
      anyOf:
        - $ref: '#/components/schemas/docId'
        - const: default
    patch:
      description: PATCH body (application/merge-patch+json, RFC 7396) over the venue sections; the merged result must be a valid input.
      type: object
      minProperties: 1
      propertyNames:
        pattern: ^(meta|dome|displays|calibration|audio|compat|x-.+)$
    testPattern:
      description: PUT /venues/{venueId}/displays/{displayId}/pattern body (engine freedome_pattern_*).
      type: object
      required:
        - kind
      properties:
        kind:
          anyOf:
            - enum:
                - 'off'
                - grid
                - checker
                - gray-ramp
                - colour-bars
                - dots
                - crosshair
                - solid
                - structured-light
            - type: string
              pattern: ^[a-z][a-z0-9_-]{0,31}$
        params:
          description: 'Kind-specific: grid {spacingDeg, lineWidthPx, colour}, checker {cells}, solid {colour}, structured-light {sequence, index} …'
          type: object
        face:
          type: integer
          minimum: 0
          maximum: 5
          description: 'prism / pyramid: limit to one optics face'
      additionalProperties: false
    state:
      type: object
      required:
        - online
      properties:
        online:
          type: boolean
          description: engine output / device agent reachable
        power:
          anyOf:
            - enum:
                - 'on'
                - 'off'
                - standby
                - warming
                - cooling
                - unknown
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        shutter:
          enum:
            - open
            - closed
            - unknown
        signal:
          type: boolean
        temperatureC:
          type:
            - number
            - 'null'
        pattern:
          anyOf:
            - $ref: '#/components/schemas/testPattern'
            - type: 'null'
        lastSeen:
          $ref: '#/components/schemas/timestamp'
        capabilities:
          type: array
          items:
            anyOf:
              - enum:
                  - power
                  - shutter
                  - identify
                  - temperature
                  - pjlink
              - type: string
        faults:
          type: array
          items:
            type: object
            required:
              - code
            properties:
              code:
                type: string
              detail:
                type: string
            additionalProperties: false
      additionalProperties: false
    display-2:
      type: object
      required:
        - id
        - venueId
        - config
        - state
      properties:
        id:
          $ref: '#/components/schemas/docId'
        venueId:
          $ref: '#/components/schemas/docId'
        revision:
          $ref: '#/components/schemas/revision'
          description: venue revision the config belongs to
        config:
          $ref: '#/components/schemas/display'
        state:
          $ref: '#/components/schemas/state'
      additionalProperties: false
    page-2:
      type: object
      allOf:
        - $ref: '#/components/schemas/pagination.schema'
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/display-2'
    display.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/display.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). Display resource (02e §4, §5.2): `config` = the .dome 1.1 display entry, `state` = live device state. Root = GET representation.'
      title: Display
      allOf:
        - $ref: '#/components/schemas/display-2'
      $defs:
        display:
          type: object
          required:
            - id
            - venueId
            - config
            - state
          properties:
            id:
              $ref: '#/components/schemas/docId'
            venueId:
              $ref: '#/components/schemas/docId'
            revision:
              $ref: '#/components/schemas/revision'
              description: venue revision the config belongs to
            config:
              $ref: '#/components/schemas/display'
            state:
              $ref: '#/components/schemas/state'
          additionalProperties: false
        state:
          type: object
          required:
            - online
          properties:
            online:
              type: boolean
              description: engine output / device agent reachable
            power:
              anyOf:
                - enum:
                    - 'on'
                    - 'off'
                    - standby
                    - warming
                    - cooling
                    - unknown
                - type: string
                  pattern: ^[a-z][a-z_-]{0,31}$
            shutter:
              enum:
                - open
                - closed
                - unknown
            signal:
              type: boolean
            temperatureC:
              type:
                - number
                - 'null'
            pattern:
              anyOf:
                - $ref: '#/components/schemas/testPattern'
                - type: 'null'
            lastSeen:
              $ref: '#/components/schemas/timestamp'
            capabilities:
              type: array
              items:
                anyOf:
                  - enum:
                      - power
                      - shutter
                      - identify
                      - temperature
                      - pjlink
                  - type: string
            faults:
              type: array
              items:
                type: object
                required:
                  - code
                properties:
                  code:
                    type: string
                  detail:
                    type: string
                additionalProperties: false
          additionalProperties: false
        testPattern:
          description: PUT /venues/{venueId}/displays/{displayId}/pattern body (engine freedome_pattern_*).
          type: object
          required:
            - kind
          properties:
            kind:
              anyOf:
                - enum:
                    - 'off'
                    - grid
                    - checker
                    - gray-ramp
                    - colour-bars
                    - dots
                    - crosshair
                    - solid
                    - structured-light
                - type: string
                  pattern: ^[a-z][a-z0-9_-]{0,31}$
            params:
              description: 'Kind-specific: grid {spacingDeg, lineWidthPx, colour}, checker {cells}, solid {colour}, structured-light {sequence, index} …'
              type: object
            face:
              type: integer
              minimum: 0
              maximum: 5
              description: 'prism / pyramid: limit to one optics face'
          additionalProperties: false
        power:
          description: POST …/power body; only what the device supports (state.capabilities).
          type: object
          minProperties: 1
          properties:
            'on':
              type: boolean
            shutter:
              enum:
                - open
                - closed
          additionalProperties: false
        identify:
          description: 'POST …/identify body (optional): how long to show the id overlay.'
          type: object
          properties:
            durationS:
              type: number
              minimum: 0.5
              maximum: 60
              default: 5
          additionalProperties: false
        patch:
          description: PATCH body (application/merge-patch+json) over `config`; `id` cannot change.
          type: object
          minProperties: 1
          not:
            required:
              - id
        page:
          type: object
          allOf:
            - $ref: '#/components/schemas/pagination.schema'
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/display-2'
    patch-2:
      description: PATCH body (application/merge-patch+json) over `config`; `id` cannot change.
      type: object
      minProperties: 1
      not:
        required:
          - id
    identify:
      description: 'POST …/identify body (optional): how long to show the id overlay.'
      type: object
      properties:
        durationS:
          type: number
          minimum: 0.5
          maximum: 60
          default: 5
      additionalProperties: false
    power:
      description: POST …/power body; only what the device supports (state.capabilities).
      type: object
      minProperties: 1
      properties:
        'on':
          type: boolean
        shutter:
          enum:
            - open
            - closed
      additionalProperties: false
    unitInterval:
      description: 0..1 (brightness, linear volume gain, progress).
      type: number
      minimum: 0
      maximum: 1
    gainDb-2:
      description: 'Gain in dB (C5: optional companion of linear `volume`).'
      type: number
      minimum: -144
      maximum: 24
    output.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/output.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Venue output state (02e §5.2 GET/PATCH /venues/{venueId}/output; fulldome /@1/* coverage).
      title: Venue output
      type: object
      required:
        - brightness
        - volume
        - blackout
      properties:
        brightness:
          $ref: '#/components/schemas/unitInterval'
          description: master brightness 0..1
        volume:
          $ref: '#/components/schemas/unitInterval'
          description: master volume, linear 0..1 (C5)
        gainDb:
          $ref: '#/components/schemas/gainDb-2'
        mute:
          type: boolean
        blackout:
          type: boolean
        rotation:
          type: number
          minimum: -360
          maximum: 360
          description: ° content rotation about the dome axis
        lighting:
          type: boolean
          description: stage / house light relay
        ndi:
          type: object
          properties:
            enabled:
              type: boolean
            name:
              type: string
          additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
      $defs:
        patch:
          description: 'PATCH body (application/merge-patch+json): any subset of the output fields.'
          type: object
          minProperties: 1
          properties:
            brightness:
              $ref: '#/components/schemas/unitInterval'
            volume:
              $ref: '#/components/schemas/unitInterval'
            gainDb:
              $ref: '#/components/schemas/gainDb-2'
            mute:
              type: boolean
            blackout:
              type: boolean
            rotation:
              type: number
              minimum: -360
              maximum: 360
            lighting:
              type: boolean
            ndi:
              type: object
              properties:
                enabled:
                  type: boolean
                name:
                  type: string
              additionalProperties: false
          patternProperties:
            ^x-: true
          additionalProperties: false
    patch-3:
      description: 'PATCH body (application/merge-patch+json): any subset of the output fields.'
      type: object
      minProperties: 1
      properties:
        brightness:
          $ref: '#/components/schemas/unitInterval'
        volume:
          $ref: '#/components/schemas/unitInterval'
        gainDb:
          $ref: '#/components/schemas/gainDb-2'
        mute:
          type: boolean
        blackout:
          type: boolean
        rotation:
          type: number
          minimum: -360
          maximum: 360
        lighting:
          type: boolean
        ndi:
          type: object
          properties:
            enabled:
              type: boolean
            name:
              type: string
          additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
    status:
      description: queued → running ⇄ awaiting_input → succeeded | failed | cancelled.
      enum:
        - queued
        - running
        - awaiting_input
        - succeeded
        - failed
        - cancelled
    kind:
      anyOf:
        - enum:
            - visual-camera
            - visual-manual
            - visual-import
            - audio-room
            - audio-speaker-check
        - type: string
          pattern: ^[a-z][a-z0-9-]{0,63}$
    awaiting:
      type: object
      required:
        - kind
      properties:
        kind:
          anyOf:
            - enum:
                - camera-capture
                - manual-adjust
                - confirm
            - type: string
        stepId:
          $ref: '#/components/schemas/docId'
        displayId:
          $ref: '#/components/schemas/docId'
        pattern:
          $ref: '#/components/schemas/testPattern'
        instructions:
          $ref: '#/components/schemas/i18n-text.schema'
      additionalProperties: false
    calibrationJob:
      type: object
      required:
        - id
        - venueId
        - kind
        - status
        - steps
        - createdAt
      properties:
        id:
          $ref: '#/components/schemas/uuidv7'
        venueId:
          $ref: '#/components/schemas/docId'
        kind:
          $ref: '#/components/schemas/kind'
        status:
          $ref: '#/components/schemas/status'
        progress:
          type: number
          minimum: 0
          maximum: 1
        displays:
          type: array
          items:
            $ref: '#/components/schemas/docId'
        speakers:
          type: array
          items:
            $ref: '#/components/schemas/docId'
        steps:
          type: array
          items:
            type: object
            required:
              - id
              - status
            properties:
              id:
                $ref: '#/components/schemas/docId'
              name:
                type: string
              displayId:
                $ref: '#/components/schemas/docId'
              status:
                enum:
                  - pending
                  - running
                  - awaiting_input
                  - done
                  - failed
                  - skipped
            additionalProperties: false
        currentStep:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        awaiting:
          description: 'Set while status = awaiting_input: what the operator / camera must provide.'
          anyOf:
            - type: 'null'
            - $ref: '#/components/schemas/awaiting'
        errors:
          description: Per-display residual error after solving.
          type: array
          items:
            type: object
            required:
              - displayId
            properties:
              displayId:
                $ref: '#/components/schemas/docId'
              meanError:
                type: number
                minimum: 0
              maxError:
                type: number
                minimum: 0
              unit:
                type: string
            additionalProperties: false
        result:
          description: Proposed calibration (applied to the venue by POST …/apply).
          anyOf:
            - type: 'null'
            - $ref: '#/components/schemas/calibration'
        problem:
          $ref: '#/components/schemas/problem.schema'
        createdAt:
          $ref: '#/components/schemas/timestamp'
        updatedAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    page-3:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/calibrationJob'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    start:
      description: POST /venues/{venueId}/calibration/jobs body.
      type: object
      required:
        - kind
      properties:
        kind:
          $ref: '#/components/schemas/kind'
        displays:
          type: array
          items:
            $ref: '#/components/schemas/docId'
          uniqueItems: true
        speakers:
          type: array
          items:
            $ref: '#/components/schemas/docId'
          uniqueItems: true
        options:
          type: object
          description: kind-specific (grid density, camera model, import format …)
      additionalProperties: false
    calibration-job.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/calibration-job.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Calibration job (02e §5.2, 02a §2.5 / §4.7, 02b §4.5). Root = GET …/calibration/jobs/{jobId}.
      title: Calibration job
      allOf:
        - $ref: '#/components/schemas/calibrationJob'
      $defs:
        calibrationJob:
          type: object
          required:
            - id
            - venueId
            - kind
            - status
            - steps
            - createdAt
          properties:
            id:
              $ref: '#/components/schemas/uuidv7'
            venueId:
              $ref: '#/components/schemas/docId'
            kind:
              $ref: '#/components/schemas/kind'
            status:
              $ref: '#/components/schemas/status'
            progress:
              type: number
              minimum: 0
              maximum: 1
            displays:
              type: array
              items:
                $ref: '#/components/schemas/docId'
            speakers:
              type: array
              items:
                $ref: '#/components/schemas/docId'
            steps:
              type: array
              items:
                type: object
                required:
                  - id
                  - status
                properties:
                  id:
                    $ref: '#/components/schemas/docId'
                  name:
                    type: string
                  displayId:
                    $ref: '#/components/schemas/docId'
                  status:
                    enum:
                      - pending
                      - running
                      - awaiting_input
                      - done
                      - failed
                      - skipped
                additionalProperties: false
            currentStep:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            awaiting:
              description: 'Set while status = awaiting_input: what the operator / camera must provide.'
              anyOf:
                - type: 'null'
                - $ref: '#/components/schemas/awaiting'
            errors:
              description: Per-display residual error after solving.
              type: array
              items:
                type: object
                required:
                  - displayId
                properties:
                  displayId:
                    $ref: '#/components/schemas/docId'
                  meanError:
                    type: number
                    minimum: 0
                  maxError:
                    type: number
                    minimum: 0
                  unit:
                    type: string
                additionalProperties: false
            result:
              description: Proposed calibration (applied to the venue by POST …/apply).
              anyOf:
                - type: 'null'
                - $ref: '#/components/schemas/calibration'
            problem:
              $ref: '#/components/schemas/problem.schema'
            createdAt:
              $ref: '#/components/schemas/timestamp'
            updatedAt:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
        kind:
          anyOf:
            - enum:
                - visual-camera
                - visual-manual
                - visual-import
                - audio-room
                - audio-speaker-check
            - type: string
              pattern: ^[a-z][a-z0-9-]{0,63}$
        start:
          description: POST /venues/{venueId}/calibration/jobs body.
          type: object
          required:
            - kind
          properties:
            kind:
              $ref: '#/components/schemas/kind'
            displays:
              type: array
              items:
                $ref: '#/components/schemas/docId'
              uniqueItems: true
            speakers:
              type: array
              items:
                $ref: '#/components/schemas/docId'
              uniqueItems: true
            options:
              type: object
              description: kind-specific (grid density, camera model, import format …)
          additionalProperties: false
        capture:
          description: 'POST …/captures JSON body: a camera frame uploaded before (tus upload id); multipart/form-data with an `image` part is the alternative (≤ 32 MB).'
          type: object
          required:
            - uploadId
          properties:
            uploadId:
              $ref: '#/components/schemas/uuidv7'
            stepId:
              $ref: '#/components/schemas/docId'
          additionalProperties: false
        awaiting:
          type: object
          required:
            - kind
          properties:
            kind:
              anyOf:
                - enum:
                    - camera-capture
                    - manual-adjust
                    - confirm
                - type: string
            stepId:
              $ref: '#/components/schemas/docId'
            displayId:
              $ref: '#/components/schemas/docId'
            pattern:
              $ref: '#/components/schemas/testPattern'
            instructions:
              $ref: '#/components/schemas/i18n-text.schema'
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/calibrationJob'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
    capture:
      description: 'POST …/captures JSON body: a camera frame uploaded before (tus upload id); multipart/form-data with an `image` part is the alternative (≤ 32 MB).'
      type: object
      required:
        - uploadId
      properties:
        uploadId:
          $ref: '#/components/schemas/uuidv7'
        stepId:
          $ref: '#/components/schemas/docId'
      additionalProperties: false
    clientInfo:
      type: object
      properties:
        app:
          type: string
          minLength: 1
          maxLength: 128
        appVersion:
          type: string
          maxLength: 64
        deviceName:
          type: string
          maxLength: 128
      additionalProperties: false
    sessionRequest:
      description: POST /auth/session (local account).
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
          minLength: 1
          maxLength: 128
        password:
          type: string
          minLength: 1
          maxLength: 1024
          writeOnly: true
        client:
          $ref: '#/components/schemas/clientInfo'
      additionalProperties: false
    scope:
      description: Authorisation scope (02e §3.6), `<area>:<action>`.
      type: string
      pattern: ^[a-z]+:[a-z]+$
      examples:
        - info:read
        - playback:control
        - system:admin
    scopes:
      type: array
      items:
        $ref: '#/components/schemas/scope'
      uniqueItems: true
    role:
      description: 'Role = scope bundle (02e §3.6). Open enum: clients map unknown roles to UNKNOWN.'
      anyOf:
        - enum:
            - guest
            - operator
            - admin
            - superadmin
        - type: string
          pattern: ^[a-z][a-z0-9_-]{0,31}$
    principal:
      description: 'GET /auth/me: who is calling and what they may do.'
      type: object
      required:
        - subject
        - kind
        - role
        - scopes
      properties:
        subject:
          type: string
          minLength: 1
          description: user id, api key id or pairing id
        kind:
          anyOf:
            - enum:
                - user
                - apiKey
                - pairing
                - cloud
                - anonymous
            - type: string
              pattern: ^[a-zA-Z]{1,32}$
        name:
          type: string
        role:
          $ref: '#/components/schemas/role'
        scopes:
          $ref: '#/components/schemas/scopes'
        origin:
          type: string
          description: transport the request came in on (rest, graphql, ws, socketio, osc, grpc)
        expiresAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    tokens:
      type: object
      required:
        - accessToken
        - tokenType
        - expiresIn
      properties:
        accessToken:
          type: string
          minLength: 16
          description: access JWT (15 min)
        tokenType:
          const: Bearer
        expiresIn:
          type: integer
          minimum: 1
          description: access token lifetime in seconds
        refreshToken:
          type: string
          minLength: 16
          description: rotating refresh token; reuse of a rotated one revokes the whole family
        refreshExpiresIn:
          type: integer
          minimum: 1
        scopes:
          $ref: '#/components/schemas/scopes'
        principal:
          $ref: '#/components/schemas/principal'
      patternProperties:
        ^x-: true
      additionalProperties: false
    refreshRequest:
      description: POST /auth/refresh.
      type: object
      required:
        - refreshToken
      properties:
        refreshToken:
          type: string
          minLength: 16
      additionalProperties: false
    tokenExchangeRequest:
      description: 'POST /auth/token-exchange: an L7 (freedome.nativemind.us) access JWT for a local session; linked installations only (JWKS of the cloud).'
      type: object
      required:
        - subjectToken
      properties:
        subjectToken:
          type: string
          minLength: 16
        subjectTokenType:
          const: urn:ietf:params:oauth:token-type:jwt
          default: urn:ietf:params:oauth:token-type:jwt
        client:
          $ref: '#/components/schemas/clientInfo'
      additionalProperties: false
    pairing:
      description: Pairing state. `code` (6 digits) is returned only to the requester on creation and shown on the dome / in Manager; `tokens` only once, to the requester's first poll after approval.
      type: object
      required:
        - pairingId
        - status
        - expiresAt
      properties:
        pairingId:
          $ref: '#/components/schemas/uuidv7'
        status:
          anyOf:
            - enum:
                - pending
                - approved
                - denied
                - expired
            - type: string
              pattern: ^[a-z_]{1,32}$
        code:
          type: string
          pattern: ^[0-9]{6}$
        qr:
          type: string
          pattern: ^freedome://pair\?
          description: freedome://pair?host=…&code=…
        app:
          type: string
        deviceName:
          type: string
        role:
          $ref: '#/components/schemas/role'
        expiresAt:
          $ref: '#/components/schemas/timestamp'
        tokens:
          $ref: '#/components/schemas/tokens'
      patternProperties:
        ^x-: true
      additionalProperties: false
    pairingRequest:
      description: POST /auth/pairings (anonymous; ecosystem app asks for a guest token).
      type: object
      required:
        - app
        - deviceName
      properties:
        app:
          type: string
          minLength: 1
          maxLength: 128
          description: application id, e.g. net.nativemind.starveda
        deviceName:
          type: string
          minLength: 1
          maxLength: 128
        requestedRole:
          $ref: '#/components/schemas/role'
      additionalProperties: false
    pairingApproval:
      description: POST /auth/pairings/{pairingId}/approve body (operator side).
      type: object
      properties:
        approve:
          type: boolean
          default: true
          description: false = deny
        role:
          $ref: '#/components/schemas/role'
          description: granted role (default guest)
        ttlSec:
          type: integer
          minimum: 60
          maximum: 31536000
      additionalProperties: false
    username:
      type: string
      pattern: ^[a-z0-9][a-z0-9._-]{1,63}$
    user:
      type: object
      required:
        - id
        - username
        - role
        - revision
        - createdAt
      properties:
        id:
          $ref: '#/components/schemas/uuidv7'
        username:
          $ref: '#/components/schemas/username'
        displayName:
          type: string
          maxLength: 128
        role:
          $ref: '#/components/schemas/role'
        disabled:
          type: boolean
        revision:
          $ref: '#/components/schemas/revision'
        createdAt:
          $ref: '#/components/schemas/timestamp'
        updatedAt:
          $ref: '#/components/schemas/timestamp'
        lastLoginAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    page-4:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/user'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
        count:
          type: integer
          minimum: 0
      additionalProperties: false
    input-2:
      description: POST /users.
      type: object
      required:
        - username
        - password
        - role
      properties:
        username:
          $ref: '#/components/schemas/username'
        displayName:
          type: string
          maxLength: 128
        password:
          type: string
          minLength: 12
          maxLength: 1024
          writeOnly: true
        role:
          $ref: '#/components/schemas/role'
        disabled:
          type: boolean
      additionalProperties: false
    user.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/user.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Local user account of a media server (02e §5.1 /users, scope users:manage). Root = GET representation.
      title: User
      allOf:
        - $ref: '#/components/schemas/user'
      $defs:
        user:
          type: object
          required:
            - id
            - username
            - role
            - revision
            - createdAt
          properties:
            id:
              $ref: '#/components/schemas/uuidv7'
            username:
              $ref: '#/components/schemas/username'
            displayName:
              type: string
              maxLength: 128
            role:
              $ref: '#/components/schemas/role'
            disabled:
              type: boolean
            revision:
              $ref: '#/components/schemas/revision'
            createdAt:
              $ref: '#/components/schemas/timestamp'
            updatedAt:
              $ref: '#/components/schemas/timestamp'
            lastLoginAt:
              $ref: '#/components/schemas/timestamp'
          patternProperties:
            ^x-: true
          additionalProperties: false
        username:
          type: string
          pattern: ^[a-z0-9][a-z0-9._-]{1,63}$
        input:
          description: POST /users.
          type: object
          required:
            - username
            - password
            - role
          properties:
            username:
              $ref: '#/components/schemas/username'
            displayName:
              type: string
              maxLength: 128
            password:
              type: string
              minLength: 12
              maxLength: 1024
              writeOnly: true
            role:
              $ref: '#/components/schemas/role'
            disabled:
              type: boolean
          additionalProperties: false
        patch:
          description: PATCH /users/{userId} (merge-patch). Changing the password revokes the user's sessions.
          type: object
          minProperties: 1
          properties:
            displayName:
              type: string
              maxLength: 128
            password:
              type: string
              minLength: 12
              maxLength: 1024
              writeOnly: true
            role:
              $ref: '#/components/schemas/role'
            disabled:
              type: boolean
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/user'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
            count:
              type: integer
              minimum: 0
          additionalProperties: false
    patch-4:
      description: PATCH /users/{userId} (merge-patch). Changing the password revokes the user's sessions.
      type: object
      minProperties: 1
      properties:
        displayName:
          type: string
          maxLength: 128
        password:
          type: string
          minLength: 12
          maxLength: 1024
          writeOnly: true
        role:
          $ref: '#/components/schemas/role'
        disabled:
          type: boolean
      additionalProperties: false
    keyId:
      type: string
      pattern: ^[a-z0-9]{8,32}$
    apiKey:
      type: object
      required:
        - id
        - name
        - role
        - scopes
        - createdAt
      properties:
        id:
          $ref: '#/components/schemas/keyId'
        name:
          type: string
          minLength: 1
          maxLength: 128
        role:
          $ref: '#/components/schemas/role'
        scopes:
          $ref: '#/components/schemas/scopes'
        prefix:
          type: string
          pattern: ^fdk_[a-z0-9]{8,32}$
          description: public part of the token (fdk_<id>) for recognition in logs
        createdBy:
          type: string
        createdAt:
          $ref: '#/components/schemas/timestamp'
        expiresAt:
          anyOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
        lastUsedAt:
          anyOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
        revokedAt:
          anyOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
      patternProperties:
        ^x-: true
      additionalProperties: false
    page-5:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/apiKey'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
        count:
          type: integer
          minimum: 0
      additionalProperties: false
    input-3:
      description: POST /api-keys. A key never gets more than the caller's own role.
      type: object
      required:
        - name
        - role
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 128
        role:
          $ref: '#/components/schemas/role'
        scopes:
          $ref: '#/components/schemas/scopes'
          description: optional narrowing of the role's scopes
        expiresAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    created:
      description: '201 body of POST /api-keys: the key plus its token, shown once.'
      type: object
      required:
        - key
        - token
      properties:
        key:
          $ref: '#/components/schemas/apiKey'
        token:
          type: string
          pattern: ^fdk_[a-z0-9]{8,32}_[A-Za-z0-9]{32,128}$
      additionalProperties: false
    api-key.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/api-key.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). API key `fdk_<id>_<secret>` (02e §3.6, §5.1 /api-keys, scope keys:manage). The secret is returned once (#/$defs/created); stored as a hash only.
      title: API key
      allOf:
        - $ref: '#/components/schemas/apiKey'
      $defs:
        apiKey:
          type: object
          required:
            - id
            - name
            - role
            - scopes
            - createdAt
          properties:
            id:
              $ref: '#/components/schemas/keyId'
            name:
              type: string
              minLength: 1
              maxLength: 128
            role:
              $ref: '#/components/schemas/role'
            scopes:
              $ref: '#/components/schemas/scopes'
            prefix:
              type: string
              pattern: ^fdk_[a-z0-9]{8,32}$
              description: public part of the token (fdk_<id>) for recognition in logs
            createdBy:
              type: string
            createdAt:
              $ref: '#/components/schemas/timestamp'
            expiresAt:
              anyOf:
                - $ref: '#/components/schemas/timestamp'
                - type: 'null'
            lastUsedAt:
              anyOf:
                - $ref: '#/components/schemas/timestamp'
                - type: 'null'
            revokedAt:
              anyOf:
                - $ref: '#/components/schemas/timestamp'
                - type: 'null'
          patternProperties:
            ^x-: true
          additionalProperties: false
        keyId:
          type: string
          pattern: ^[a-z0-9]{8,32}$
        input:
          description: POST /api-keys. A key never gets more than the caller's own role.
          type: object
          required:
            - name
            - role
          properties:
            name:
              type: string
              minLength: 1
              maxLength: 128
            role:
              $ref: '#/components/schemas/role'
            scopes:
              $ref: '#/components/schemas/scopes'
              description: optional narrowing of the role's scopes
            expiresAt:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
        created:
          description: '201 body of POST /api-keys: the key plus its token, shown once.'
          type: object
          required:
            - key
            - token
          properties:
            key:
              $ref: '#/components/schemas/apiKey'
            token:
              type: string
              pattern: ^fdk_[a-z0-9]{8,32}_[A-Za-z0-9]{32,128}$
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/apiKey'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
            count:
              type: integer
              minimum: 0
          additionalProperties: false
    logLevel:
      enum:
        - trace
        - debug
        - info
        - warn
        - error
    logEntry:
      type: object
      required:
        - ts
        - level
        - message
      properties:
        seq:
          type: integer
          minimum: 0
        ts:
          $ref: '#/components/schemas/timestamp'
        level:
          $ref: '#/components/schemas/logLevel'
        target:
          type: string
          description: module / component
        message:
          type: string
        fields:
          type: object
          additionalProperties: true
        traceId:
          type: string
      additionalProperties: false
    clock:
      description: Clock status of a node (02d ClockStatus).
      type: object
      required:
        - source
        - locked
      properties:
        source:
          anyOf:
            - enum:
                - ptp
                - ntp
                - local
            - type: string
        offsetMs:
          type: number
        jitterMs:
          type: number
          minimum: 0
        masterId:
          type: string
        locked:
          type: boolean
      additionalProperties: false
    node:
      description: 'Cluster node (02d UC-K1–K3): this server and the engine hosts it drives.'
      type: object
      required:
        - id
        - role
        - status
      properties:
        id:
          $ref: '#/components/schemas/docId'
        name:
          type: string
        role:
          anyOf:
            - enum:
                - primary
                - engine
                - peer
            - type: string
        status:
          anyOf:
            - enum:
                - up
                - degraded
                - down
            - type: string
        address:
          type: string
        engines:
          type: array
          items:
            type: string
        clock:
          $ref: '#/components/schemas/clock'
        lastSeen:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    system.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/system.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). Host / service status and system operations (02e §5.1 /system/*): status, logs, config (redacted), diagnostics, backup / restore, update, reboot / shutdown; cluster nodes + clock (02d UC-K1–K3). Root = GET /system.'
      title: System status
      type: object
      required:
        - host
        - uptimeSec
        - versions
      properties:
        host:
          type: object
          required:
            - hostname
            - os
            - arch
          properties:
            hostname:
              type: string
            os:
              type: string
              description: e.g. Debian 12, Astra Linux 1.8, Windows 11, macOS 15
            kernel:
              type: string
            arch:
              anyOf:
                - enum:
                    - x86_64
                    - aarch64
                    - riscv64
                    - loongarch64
                    - e2k
                    - mips64el
                - type: string
            cpu:
              type: object
              properties:
                model:
                  type: string
                cores:
                  type: integer
                  minimum: 1
                loadPercent:
                  type: number
                  minimum: 0
              additionalProperties: true
            memory:
              type: object
              properties:
                totalBytes:
                  type: integer
                  minimum: 0
                usedBytes:
                  type: integer
                  minimum: 0
              additionalProperties: true
            gpus:
              type: array
              items:
                type: object
                properties:
                  name:
                    type: string
                  driver:
                    type: string
                  loadPercent:
                    type: number
                    minimum: 0
                  memoryBytes:
                    type: integer
                    minimum: 0
                additionalProperties: true
          additionalProperties: true
        temperatures:
          description: Sensor name → °C (replaces /api/system/temperature).
          type: object
          additionalProperties:
            type: number
        disks:
          type: array
          items:
            type: object
            required:
              - mount
              - totalBytes
              - freeBytes
            properties:
              mount:
                type: string
              totalBytes:
                type: integer
                minimum: 0
              freeBytes:
                type: integer
                minimum: 0
            additionalProperties: false
        uptimeSec:
          type: number
          minimum: 0
        serviceUptimeSec:
          type: number
          minimum: 0
        versions:
          description: Component → version (server, contract, engines, OS packages).
          type: object
          additionalProperties:
            type: string
      patternProperties:
        ^x-: true
      additionalProperties: false
      $defs:
        logLevel:
          enum:
            - trace
            - debug
            - info
            - warn
            - error
        logEntry:
          type: object
          required:
            - ts
            - level
            - message
          properties:
            seq:
              type: integer
              minimum: 0
            ts:
              $ref: '#/components/schemas/timestamp'
            level:
              $ref: '#/components/schemas/logLevel'
            target:
              type: string
              description: module / component
            message:
              type: string
            fields:
              type: object
              additionalProperties: true
            traceId:
              type: string
          additionalProperties: false
        logPage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/logEntry'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        config:
          description: Effective server configuration (TOML sections as JSON). Secret values are always returned as "***" and a PUT that sends "***" keeps the stored secret (redaction rule, 01a §3).
          type: object
          required:
            - revision
            - values
          properties:
            revision:
              $ref: '#/components/schemas/revision'
            values:
              type: object
              additionalProperties: true
            redacted:
              type: array
              items:
                type: string
              description: JSON pointers of redacted secret values
            restartRequired:
              type: array
              items:
                type: string
              description: pointers whose change takes effect only after a restart
          additionalProperties: false
        configInput:
          description: PUT /system/config body (If-Match = config revision).
          type: object
          required:
            - values
          properties:
            values:
              type: object
              additionalProperties: true
          additionalProperties: false
        diagnosticsRequest:
          type: object
          properties:
            checks:
              type: array
              items:
                anyOf:
                  - enum:
                      - engines
                      - displays
                      - audio
                      - network
                      - storage
                      - sync
                      - gateways
                  - type: string
              uniqueItems: true
          additionalProperties: false
        backupRequest:
          type: object
          properties:
            include:
              type: array
              items:
                anyOf:
                  - enum:
                      - config
                      - database
                      - venues
                      - shows
                      - keys
                      - assets
                  - type: string
              uniqueItems: true
            note:
              type: string
              maxLength: 512
          additionalProperties: false
        restoreRequest:
          type: object
          required:
            - uploadId
            - confirm
          properties:
            uploadId:
              $ref: '#/components/schemas/uuidv7'
              description: tus upload (purpose restore) holding the backup archive
            confirm:
              const: true
          additionalProperties: false
        updateRequest:
          type: object
          properties:
            channel:
              anyOf:
                - enum:
                    - stable
                    - beta
                - type: string
            version:
              $ref: '#/components/schemas/semver'
            uploadId:
              $ref: '#/components/schemas/uuidv7'
              description: offline update package (purpose update)
            components:
              type: array
              items:
                type: string
              uniqueItems: true
              description: server, engines, firmware:<deviceId> …; absent = all
          additionalProperties: false
        updateStatus:
          type: object
          required:
            - status
          properties:
            status:
              anyOf:
                - enum:
                    - idle
                    - checking
                    - available
                    - downloading
                    - installing
                    - restartPending
                    - failed
                - type: string
            current:
              type: object
              additionalProperties:
                type: string
            available:
              type: object
              additionalProperties:
                type: string
            jobId:
              $ref: '#/components/schemas/uuidv7'
            checkedAt:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
        power:
          description: POST /system/reboot and /system/shutdown body.
          type: object
          required:
            - confirm
          properties:
            confirm:
              const: true
            delaySec:
              type: integer
              minimum: 0
              maximum: 3600
              default: 5
            reason:
              type: string
              maxLength: 256
          additionalProperties: false
        powerAccepted:
          type: object
          required:
            - action
            - at
          properties:
            action:
              enum:
                - reboot
                - shutdown
            at:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
        clock:
          description: Clock status of a node (02d ClockStatus).
          type: object
          required:
            - source
            - locked
          properties:
            source:
              anyOf:
                - enum:
                    - ptp
                    - ntp
                    - local
                - type: string
            offsetMs:
              type: number
            jitterMs:
              type: number
              minimum: 0
            masterId:
              type: string
            locked:
              type: boolean
          additionalProperties: false
        node:
          description: 'Cluster node (02d UC-K1–K3): this server and the engine hosts it drives.'
          type: object
          required:
            - id
            - role
            - status
          properties:
            id:
              $ref: '#/components/schemas/docId'
            name:
              type: string
            role:
              anyOf:
                - enum:
                    - primary
                    - engine
                    - peer
                - type: string
            status:
              anyOf:
                - enum:
                    - up
                    - degraded
                    - down
                - type: string
            address:
              type: string
            engines:
              type: array
              items:
                type: string
            clock:
              $ref: '#/components/schemas/clock'
            lastSeen:
              $ref: '#/components/schemas/timestamp'
          patternProperties:
            ^x-: true
          additionalProperties: false
        nodePage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/node'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
    logPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/logEntry'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    config:
      description: Effective server configuration (TOML sections as JSON). Secret values are always returned as "***" and a PUT that sends "***" keeps the stored secret (redaction rule, 01a §3).
      type: object
      required:
        - revision
        - values
      properties:
        revision:
          $ref: '#/components/schemas/revision'
        values:
          type: object
          additionalProperties: true
        redacted:
          type: array
          items:
            type: string
          description: JSON pointers of redacted secret values
        restartRequired:
          type: array
          items:
            type: string
          description: pointers whose change takes effect only after a restart
      additionalProperties: false
    configInput:
      description: PUT /system/config body (If-Match = config revision).
      type: object
      required:
        - values
      properties:
        values:
          type: object
          additionalProperties: true
      additionalProperties: false
    diagnosticsRequest:
      type: object
      properties:
        checks:
          type: array
          items:
            anyOf:
              - enum:
                  - engines
                  - displays
                  - audio
                  - network
                  - storage
                  - sync
                  - gateways
              - type: string
          uniqueItems: true
      additionalProperties: false
    token-2:
      type: string
      pattern: ^[a-z0-9][a-z0-9._-]{0,63}$
    job:
      type: object
      required:
        - id
        - kind
        - status
        - createdAt
      properties:
        id:
          $ref: '#/components/schemas/uuidv7'
        kind:
          anyOf:
            - enum:
                - calibration
                - import
                - export
                - backup
                - restore
                - diagnostics
                - update
                - probe
            - $ref: '#/components/schemas/token-2'
        status:
          $ref: '#/components/schemas/status'
        progress:
          type: number
          minimum: 0
          maximum: 1
        message:
          type: string
        createdAt:
          $ref: '#/components/schemas/timestamp'
        updatedAt:
          $ref: '#/components/schemas/timestamp'
        finishedAt:
          $ref: '#/components/schemas/timestamp'
        result:
          description: Kind-specific result (e.g. { venueId } of an import).
        problem:
          $ref: '#/components/schemas/problem.schema'
        links:
          type: object
          properties:
            self:
              type: string
              format: uri-reference
            cancel:
              type: string
              format: uri-reference
            resource:
              type: string
              format: uri-reference
          additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
      allOf:
        - if:
            properties:
              status:
                const: failed
            required:
              - status
          then:
            required:
              - problem
    job.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/job.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Generic long-running operation (02e §3.1, §5.1 GET /jobs/{jobId}).
      title: Job
      allOf:
        - $ref: '#/components/schemas/job'
      $defs:
        job:
          type: object
          required:
            - id
            - kind
            - status
            - createdAt
          properties:
            id:
              $ref: '#/components/schemas/uuidv7'
            kind:
              anyOf:
                - enum:
                    - calibration
                    - import
                    - export
                    - backup
                    - restore
                    - diagnostics
                    - update
                    - probe
                - $ref: '#/components/schemas/token-2'
            status:
              $ref: '#/components/schemas/status'
            progress:
              type: number
              minimum: 0
              maximum: 1
            message:
              type: string
            createdAt:
              $ref: '#/components/schemas/timestamp'
            updatedAt:
              $ref: '#/components/schemas/timestamp'
            finishedAt:
              $ref: '#/components/schemas/timestamp'
            result:
              description: Kind-specific result (e.g. { venueId } of an import).
            problem:
              $ref: '#/components/schemas/problem.schema'
            links:
              type: object
              properties:
                self:
                  type: string
                  format: uri-reference
                cancel:
                  type: string
                  format: uri-reference
                resource:
                  type: string
                  format: uri-reference
              additionalProperties: false
          patternProperties:
            ^x-: true
          additionalProperties: false
          allOf:
            - if:
                properties:
                  status:
                    const: failed
                required:
                  - status
              then:
                required:
                  - problem
        token:
          type: string
          pattern: ^[a-z0-9][a-z0-9._-]{0,63}$
        status:
          description: queued → running ⇄ awaiting_input → succeeded | failed | cancelled.
          enum:
            - queued
            - running
            - awaiting_input
            - succeeded
            - failed
            - cancelled
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/job'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
    backupRequest:
      type: object
      properties:
        include:
          type: array
          items:
            anyOf:
              - enum:
                  - config
                  - database
                  - venues
                  - shows
                  - keys
                  - assets
              - type: string
          uniqueItems: true
        note:
          type: string
          maxLength: 512
      additionalProperties: false
    restoreRequest:
      type: object
      required:
        - uploadId
        - confirm
      properties:
        uploadId:
          $ref: '#/components/schemas/uuidv7'
          description: tus upload (purpose restore) holding the backup archive
        confirm:
          const: true
      additionalProperties: false
    updateStatus:
      type: object
      required:
        - status
      properties:
        status:
          anyOf:
            - enum:
                - idle
                - checking
                - available
                - downloading
                - installing
                - restartPending
                - failed
            - type: string
        current:
          type: object
          additionalProperties:
            type: string
        available:
          type: object
          additionalProperties:
            type: string
        jobId:
          $ref: '#/components/schemas/uuidv7'
        checkedAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    updateRequest:
      type: object
      properties:
        channel:
          anyOf:
            - enum:
                - stable
                - beta
            - type: string
        version:
          $ref: '#/components/schemas/semver'
        uploadId:
          $ref: '#/components/schemas/uuidv7'
          description: offline update package (purpose update)
        components:
          type: array
          items:
            type: string
          uniqueItems: true
          description: server, engines, firmware:<deviceId> …; absent = all
      additionalProperties: false
    power-2:
      description: POST /system/reboot and /system/shutdown body.
      type: object
      required:
        - confirm
      properties:
        confirm:
          const: true
        delaySec:
          type: integer
          minimum: 0
          maximum: 3600
          default: 5
        reason:
          type: string
          maxLength: 256
      additionalProperties: false
    powerAccepted:
      type: object
      required:
        - action
        - at
      properties:
        action:
          enum:
            - reboot
            - shutdown
        at:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    nodePage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/node'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    sort:
      description: Comma-separated fields, `-` prefix = descending.
      type: string
      pattern: ^-?[a-zA-Z][a-zA-Z0-9]*(,-?[a-zA-Z][a-zA-Z0-9]*)*$
    seconds:
      description: Duration in seconds.
      type: number
      minimum: 0
    summary:
      description: 'Collection item: no assets / show body.'
      type: object
      required:
        - id
        - revision
        - meta
      properties:
        id:
          $ref: '#/components/schemas/docId'
        revision:
          $ref: '#/components/schemas/revision'
        meta:
          $ref: '#/components/schemas/meta'
        status:
          type: string
        assetCount:
          type: integer
          minimum: 0
        duration:
          $ref: '#/components/schemas/seconds'
        updatedAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    page-6:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/summary'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
        count:
          type: integer
          minimum: 0
      additionalProperties: false
    uriOrPath:
      description: An entry path (see path) or an absolute URI (`https:`, `http:`, `file:`, `ndi:`, `srt:` …).
      anyOf:
        - $ref: '#/components/schemas/path'
        - type: string
          format: uri
          pattern: '^[A-Za-z][A-Za-z0-9+.-]*:'
    sha256:
      description: Lowercase hex SHA-256 digest.
      type: string
      pattern: ^[0-9a-f]{64}$
    mapping:
      description: Content mapping onto the dome (01b §4.5, 02b §4.4).
      type: object
      required:
        - projection
      properties:
        projection:
          anyOf:
            - enum:
                - domemaster
                - equirect
                - cubemap
                - flat
                - pyramid_views
            - $ref: '#/components/schemas/token'
        fov:
          type: number
          exclusiveMinimum: 0
          maximum: 360
          description: ° (domemaster 180–230)
        tilt:
          type: number
          minimum: -90
          maximum: 90
        azimuth:
          type: number
          minimum: -360
          maximum: 360
          description: 'flat: ° centre azimuth'
        elevation:
          type: number
          minimum: -90
          maximum: 90
          description: 'flat: ° centre elevation'
        width:
          type: number
          exclusiveMinimum: 0
          maximum: 360
          description: 'flat: ° angular width'
        faces:
          enum:
            - 3
            - 4
            - 6
          description: '1.1 pyramid_views: number of views'
        layout:
          description: '1.1: cubemap 3x2 | 6x1 | cross; pyramid_views cross | row | grid'
          anyOf:
            - enum:
                - 3x2
                - 6x1
                - cross
                - row
                - grid
            - $ref: '#/components/schemas/token'
      patternProperties:
        ^x-: true
      additionalProperties: false
      if:
        properties:
          projection:
            const: pyramid_views
        required:
          - projection
      then:
        required:
          - faces
    seconds-2:
      type: number
      minimum: 0
    asset:
      description: Media / scene (01b §4.5). External assets (absolute URI) carry sha256 + size.
      type: object
      required:
        - id
        - src
        - type
      properties:
        id:
          $ref: '#/components/schemas/id'
        name:
          type: string
        src:
          $ref: '#/components/schemas/uriOrPath'
        sha256:
          $ref: '#/components/schemas/sha256'
        size:
          type: integer
          minimum: 0
        type:
          anyOf:
            - enum:
                - video
                - image
                - audio
                - zelim
                - boranko
                - comics
                - realtime
                - stream
            - $ref: '#/components/schemas/token'
        mapping:
          $ref: '#/components/schemas/mapping'
        video:
          type: object
          properties:
            resolution:
              $ref: '#/components/schemas/size2'
            fps:
              type: number
              exclusiveMinimum: 0
            codec:
              anyOf:
                - enum:
                    - hap
                    - hap-q
                    - hap-alpha
                    - h264
                    - h265
                    - av1
                    - vp9
                    - prores
                    - png-sequence
                    - exr-sequence
                - $ref: '#/components/schemas/token'
            stereo:
              oneOf:
                - type: 'null'
                - enum:
                    - top-bottom
                    - side-by-side
          patternProperties:
            ^x-: true
          additionalProperties: false
        audio:
          type: object
          required:
            - kind
          properties:
            kind:
              anyOf:
                - enum:
                    - ambisonic
                    - channels
                    - stereo
                    - mono
                - $ref: '#/components/schemas/token'
            order:
              type: integer
              minimum: 0
              maximum: 7
            layout:
              type: string
              minLength: 1
              description: 'channels: e.g. 5.1, 7.1, 7.1.4'
          patternProperties:
            ^x-: true
          additionalProperties: false
        duration:
          $ref: '#/components/schemas/seconds-2'
        thumbnail:
          $ref: '#/components/schemas/path'
        zelim:
          description: '1.1: hint for type zelim (project = JSON variant J, binary = variant B); content sniffing wins. A J project may carry quantum.scene (02e §9.3).'
          type: object
          properties:
            variant:
              anyOf:
                - enum:
                    - project
                    - binary
                - $ref: '#/components/schemas/token'
          patternProperties:
            ^x-: true
          additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
      if:
        properties:
          src:
            type: string
            pattern: '^(?!asset:)[A-Za-z][A-Za-z0-9+.-]*:'
            description: 'absolute URI other than `asset:<id>` (an asset stored on the server: sha256 / size known there)'
        required:
          - src
      then:
        if:
          properties:
            type:
              const: stream
          required:
            - type
        else:
          required:
            - sha256
            - size
    transition:
      type: object
      required:
        - kind
      properties:
        kind:
          anyOf:
            - enum:
                - cut
                - fade
                - dissolve
                - wipe
            - $ref: '#/components/schemas/token'
        duration:
          $ref: '#/components/schemas/seconds-2'
      patternProperties:
        ^x-: true
      additionalProperties: false
    playlistItem:
      type: object
      required:
        - asset
      properties:
        asset:
          $ref: '#/components/schemas/id'
        in:
          $ref: '#/components/schemas/seconds-2'
        out:
          $ref: '#/components/schemas/seconds-2'
        loop:
          type: boolean
        transition:
          $ref: '#/components/schemas/transition'
      patternProperties:
        ^x-: true
      additionalProperties: false
      description: 'x-screenberry: Screenberry item fields without a .dome equivalent, round-trip only (02d MD8).'
    playlist:
      type: object
      required:
        - id
        - items
      properties:
        id:
          $ref: '#/components/schemas/id'
        name:
          type: string
        loop:
          type: boolean
        items:
          type: array
          items:
            $ref: '#/components/schemas/playlistItem'
      patternProperties:
        ^x-: true
      additionalProperties: false
    clip:
      type: object
      required:
        - asset
        - start
      properties:
        asset:
          $ref: '#/components/schemas/id'
        start:
          $ref: '#/components/schemas/seconds-2'
        in:
          $ref: '#/components/schemas/seconds-2'
        out:
          $ref: '#/components/schemas/seconds-2'
        gainDb:
          $ref: '#/components/schemas/gainDb'
        opacity:
          $ref: '#/components/schemas/unit'
      patternProperties:
        ^x-: true
      additionalProperties: false
      description: 'x-screenberry: Screenberry item fields without a .dome equivalent, round-trip only (02d MD8).'
    timeline:
      type: object
      required:
        - id
        - tracks
      properties:
        id:
          $ref: '#/components/schemas/id'
        name:
          type: string
        duration:
          $ref: '#/components/schemas/seconds-2'
        tracks:
          type: array
          items:
            type: object
            required:
              - kind
              - clips
            properties:
              id:
                $ref: '#/components/schemas/id'
              kind:
                anyOf:
                  - enum:
                      - video
                      - audio
                      - control
                  - $ref: '#/components/schemas/token'
              clips:
                type: array
                items:
                  $ref: '#/components/schemas/clip'
            patternProperties:
              ^x-: true
            additionalProperties: false
        markers:
          type: array
          items:
            type: object
            required:
              - time
            properties:
              time:
                $ref: '#/components/schemas/seconds-2'
              name:
                type: string
              id:
                $ref: '#/components/schemas/id'
              kind:
                anyOf:
                  - enum:
                      - marker
                      - pause
                      - jump
                      - timecode
                  - $ref: '#/components/schemas/token'
            patternProperties:
              ^x-: true
            additionalProperties: false
        fps:
          type: number
          exclusiveMinimum: 0
          description: 1.1 (02d)
      patternProperties:
        ^x-: true
      additionalProperties: false
    matrixAxis:
      type: array
      items:
        anyOf:
          - $ref: '#/components/schemas/id'
          - type: object
            required:
              - id
            properties:
              id:
                $ref: '#/components/schemas/id'
              name:
                type: string
            patternProperties:
              ^x-: true
            additionalProperties: false
    matrix:
      description: Screenberry-style matrix (compatibility); cells reference row / column ids.
      type: object
      required:
        - rows
        - columns
        - cells
      properties:
        id:
          $ref: '#/components/schemas/id'
        rows:
          $ref: '#/components/schemas/matrixAxis'
        columns:
          $ref: '#/components/schemas/matrixAxis'
        cells:
          type: array
          items:
            type: object
            required:
              - row
              - column
              - asset
            properties:
              row:
                $ref: '#/components/schemas/id'
              column:
                $ref: '#/components/schemas/id'
              asset:
                $ref: '#/components/schemas/id'
            patternProperties:
              ^x-: true
            additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
    action:
      type: object
      required:
        - do
      properties:
        do:
          anyOf:
            - enum:
                - play
                - pause
                - stop
                - seek
                - setVolume
                - setBrightness
                - setParameter
                - osc
                - http
                - scenario
            - $ref: '#/components/schemas/token'
        target:
          type: string
          minLength: 1
          description: e.g. player id, playlist:<id>, param path, osc://host:port/address, URL
        args:
          description: Action arguments (object or positional array).
      patternProperties:
        ^x-: true
      additionalProperties: false
      if:
        properties:
          do:
            const: scenario
        required:
          - do
      then:
        required:
          - target
        properties:
          target:
            description: scenario id (02d §4a)
          args:
            type: object
            description: scenario parameters
    cue:
      type: object
      required:
        - id
        - actions
      properties:
        id:
          $ref: '#/components/schemas/id'
        name:
          type: string
        at:
          oneOf:
            - type: object
              required:
                - timeline
                - time
              properties:
                timeline:
                  $ref: '#/components/schemas/id'
                time:
                  $ref: '#/components/schemas/seconds-2'
              patternProperties:
                ^x-: true
              additionalProperties: false
            - type: object
              required:
                - trigger
              properties:
                trigger:
                  $ref: '#/components/schemas/id'
              patternProperties:
                ^x-: true
              additionalProperties: false
        actions:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/action'
      patternProperties:
        ^x-: true
      additionalProperties: false
    trigger:
      type: object
      required:
        - id
        - source
      properties:
        id:
          $ref: '#/components/schemas/id'
        source:
          anyOf:
            - enum:
                - osc
                - midi
                - time
                - api
                - dmx
            - $ref: '#/components/schemas/token'
        match:
          description: Source-specific match (OSC address pattern, MIDI note, cron / time, DMX channel …).
      patternProperties:
        ^x-: true
      additionalProperties: false
    showRef:
      description: Exactly one of playlist / timeline / matrix / asset id.
      type: object
      properties:
        playlist:
          $ref: '#/components/schemas/id'
        timeline:
          $ref: '#/components/schemas/id'
        matrix:
          $ref: '#/components/schemas/id'
        asset:
          $ref: '#/components/schemas/id'
      minProperties: 1
      patternProperties:
        ^x-: true
      additionalProperties: false
      oneOf:
        - required:
            - playlist
        - required:
            - timeline
        - required:
            - matrix
        - required:
            - asset
    mediaPlayer:
      description: '1.1 (02d MD8): named media player. Player `main` always exists (implicit: all displays, source = startup).'
      type: object
      required:
        - id
      properties:
        id:
          $ref: '#/components/schemas/id'
        name:
          type: string
        target:
          description: absent = all displays
          type: object
          properties:
            displays:
              type: array
              items:
                $ref: '#/components/schemas/id'
              uniqueItems: true
              minItems: 1
            layer:
              type: integer
              minimum: 0
          patternProperties:
            ^x-: true
          additionalProperties: false
        source:
          $ref: '#/components/schemas/showRef'
        volume:
          $ref: '#/components/schemas/unit'
        brightness:
          $ref: '#/components/schemas/unit'
        rotation:
          type: number
          minimum: -360
          maximum: 360
          description: ° content rotation about the dome axis
        speed:
          type: number
          exclusiveMinimum: 0
          maximum: 16
          description: playback rate, 1 = normal
      patternProperties:
        ^x-: true
      additionalProperties: false
    show:
      description: Playlists, timelines, matrix, cues, triggers, startup (01b §4.6).
      type: object
      properties:
        playlists:
          type: array
          items:
            $ref: '#/components/schemas/playlist'
        timelines:
          type: array
          items:
            $ref: '#/components/schemas/timeline'
        matrix:
          $ref: '#/components/schemas/matrix'
        cues:
          type: array
          items:
            $ref: '#/components/schemas/cue'
        triggers:
          type: array
          items:
            $ref: '#/components/schemas/trigger'
        mediaPlayers:
          type: array
          items:
            $ref: '#/components/schemas/mediaPlayer'
        startup:
          $ref: '#/components/schemas/showRef'
          description: What plays when the venue powers on.
        audioMode:
          description: '1.1 (02c AQ4, replaces x-ananta.mode): audio engines used by the show; absent = digital.'
          anyOf:
            - enum:
                - digital
                - hybrid
                - combined
            - $ref: '#/components/schemas/token'
      patternProperties:
        ^x-: true
      additionalProperties: false
    body-2:
      description: 'Show content shared by GET / PUT / POST: the .dome show sections.'
      type: object
      properties:
        meta:
          $ref: '#/components/schemas/meta'
        assets:
          type: array
          items:
            $ref: '#/components/schemas/asset'
        show:
          $ref: '#/components/schemas/show'
        compat:
          $ref: '#/components/schemas/compat'
      patternProperties:
        ^x-: true
    input-4:
      description: POST /shows and PUT /shows/{showId} body. Assets are referenced by .dome asset entries; an entry whose `src` is `asset:<assetId>` points to an asset already stored on the server.
      type: object
      allOf:
        - $ref: '#/components/schemas/body-2'
      required:
        - meta
        - assets
        - show
      unevaluatedProperties: false
    warning:
      type: object
      required:
        - code
        - detail
      properties:
        code:
          type: string
          pattern: ^[a-z][a-z0-9-]{0,63}$
        detail:
          type: string
        pointer:
          type: string
      additionalProperties: false
    show-2:
      type: object
      allOf:
        - $ref: '#/components/schemas/body-2'
      required:
        - id
        - revision
        - meta
        - assets
        - show
      properties:
        id:
          $ref: '#/components/schemas/docId'
          description: show id (= meta.id for shows created from a .dome)
        revision:
          $ref: '#/components/schemas/revision'
        status:
          anyOf:
            - enum:
                - ready
                - incomplete
                - invalid
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
          description: incomplete = referenced assets not ready yet
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/warning'
          description: UC-S6 validation against the active venue (remapping, missing assets)
        createdAt:
          $ref: '#/components/schemas/timestamp'
        updatedAt:
          $ref: '#/components/schemas/timestamp'
      unevaluatedProperties: false
    show.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/show.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Show resource (02e §4, §5.3) = .dome 1.1 show manifest without format / kind ($defs reused from dome/1.1, never duplicated) + server fields. Root = GET representation.
      title: Show
      allOf:
        - $ref: '#/components/schemas/show-2'
      $defs:
        show:
          type: object
          allOf:
            - $ref: '#/components/schemas/body-2'
          required:
            - id
            - revision
            - meta
            - assets
            - show
          properties:
            id:
              $ref: '#/components/schemas/docId'
              description: show id (= meta.id for shows created from a .dome)
            revision:
              $ref: '#/components/schemas/revision'
            status:
              anyOf:
                - enum:
                    - ready
                    - incomplete
                    - invalid
                - type: string
                  pattern: ^[a-z][a-z_-]{0,31}$
              description: incomplete = referenced assets not ready yet
            warnings:
              type: array
              items:
                $ref: '#/components/schemas/warning'
              description: UC-S6 validation against the active venue (remapping, missing assets)
            createdAt:
              $ref: '#/components/schemas/timestamp'
            updatedAt:
              $ref: '#/components/schemas/timestamp'
          unevaluatedProperties: false
        body:
          description: 'Show content shared by GET / PUT / POST: the .dome show sections.'
          type: object
          properties:
            meta:
              $ref: '#/components/schemas/meta'
            assets:
              type: array
              items:
                $ref: '#/components/schemas/asset'
            show:
              $ref: '#/components/schemas/show'
            compat:
              $ref: '#/components/schemas/compat'
          patternProperties:
            ^x-: true
        input:
          description: POST /shows and PUT /shows/{showId} body. Assets are referenced by .dome asset entries; an entry whose `src` is `asset:<assetId>` points to an asset already stored on the server.
          type: object
          allOf:
            - $ref: '#/components/schemas/body-2'
          required:
            - meta
            - assets
            - show
          unevaluatedProperties: false
        patch:
          description: 'PATCH /shows/{showId} (application/merge-patch+json): any subset of the sections; arrays are replaced whole (RFC 7396).'
          type: object
          minProperties: 1
          properties:
            meta:
              type: object
            assets:
              type: array
              items:
                $ref: '#/components/schemas/asset'
            show:
              type: object
            compat:
              anyOf:
                - type: object
                - type: 'null'
          patternProperties:
            ^x-: true
          additionalProperties: false
        warning:
          type: object
          required:
            - code
            - detail
          properties:
            code:
              type: string
              pattern: ^[a-z][a-z0-9-]{0,63}$
            detail:
              type: string
            pointer:
              type: string
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/summary'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
            count:
              type: integer
              minimum: 0
          additionalProperties: false
        summary:
          description: 'Collection item: no assets / show body.'
          type: object
          required:
            - id
            - revision
            - meta
          properties:
            id:
              $ref: '#/components/schemas/docId'
            revision:
              $ref: '#/components/schemas/revision'
            meta:
              $ref: '#/components/schemas/meta'
            status:
              type: string
            assetCount:
              type: integer
              minimum: 0
            duration:
              $ref: '#/components/schemas/seconds'
            updatedAt:
              $ref: '#/components/schemas/timestamp'
          patternProperties:
            ^x-: true
          additionalProperties: false
    patch-5:
      description: 'PATCH /shows/{showId} (application/merge-patch+json): any subset of the sections; arrays are replaced whole (RFC 7396).'
      type: object
      minProperties: 1
      properties:
        meta:
          type: object
        assets:
          type: array
          items:
            $ref: '#/components/schemas/asset'
        show:
          type: object
        compat:
          anyOf:
            - type: object
            - type: 'null'
      patternProperties:
        ^x-: true
      additionalProperties: false
    format:
      anyOf:
        - enum:
            - dome
            - domemaster-folder
            - screenberry-project
            - dome360-playlist
            - zelim
            - boranko
            - comics
            - mpcdi
        - type: string
          pattern: ^[a-z0-9][a-z0-9.-]{0,63}$
    import.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/import.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). POST /imports body (02e §5.3) and the result of a finished import job. Root = request.
      title: Import request
      type: object
      required:
        - format
      properties:
        uploadId:
          $ref: '#/components/schemas/uuidv7'
          description: tus upload (purpose import)
        url:
          type: string
          format: uri
          pattern: ^https?://
          description: server-side fetch (allow-listed hosts only)
        assetId:
          $ref: '#/components/schemas/docId'
          description: an already stored asset (e.g. a .zelim uploaded as asset)
        format:
          $ref: '#/components/schemas/format'
        target:
          type: object
          properties:
            kind:
              enum:
                - auto
                - show
                - venue
                - assets
              default: auto
            id:
              $ref: '#/components/schemas/docId'
              description: 'id to create (default: meta.id / generated)'
            replace:
              type: boolean
              default: false
              description: replace an existing show / venue with the same id (409 otherwise)
            activate:
              type: boolean
              default: false
              description: 'venue: make it the active venue; show: set as the active show'
          additionalProperties: false
        options:
          type: object
          additionalProperties: true
          description: 'format-specific (e.g. domemaster-folder: fps, loop)'
      patternProperties:
        ^x-: true
      additionalProperties: false
      oneOf:
        - required:
            - uploadId
        - required:
            - url
        - required:
            - assetId
      $defs:
        format:
          anyOf:
            - enum:
                - dome
                - domemaster-folder
                - screenberry-project
                - dome360-playlist
                - zelim
                - boranko
                - comics
                - mpcdi
            - type: string
              pattern: ^[a-z0-9][a-z0-9.-]{0,63}$
        result:
          description: '`result` of a succeeded import job.'
          type: object
          properties:
            format:
              $ref: '#/components/schemas/format'
            showId:
              $ref: '#/components/schemas/docId'
            venueId:
              $ref: '#/components/schemas/docId'
            assetIds:
              type: array
              items:
                $ref: '#/components/schemas/docId'
            warnings:
              type: array
              items:
                $ref: '#/components/schemas/warning'
          additionalProperties: false
    status-2:
      anyOf:
        - enum:
            - idle
            - loading
            - playing
            - paused
            - stopped
            - error
        - type: string
          pattern: ^[a-z][a-z_-]{0,31}$
    matrixCell:
      type: object
      required:
        - row
        - column
      properties:
        matrixId:
          $ref: '#/components/schemas/docId'
        row:
          $ref: '#/components/schemas/docId'
        column:
          $ref: '#/components/schemas/docId'
      additionalProperties: false
    position:
      description: Media position in seconds from the start.
      type: number
      minimum: 0
    rate:
      type: number
      exclusiveMinimum: 0
      maximum: 16
      description: playback rate, 1 = normal
    player:
      type: object
      required:
        - id
        - venueId
        - status
        - position
        - revision
      properties:
        id:
          $ref: '#/components/schemas/docId'
        venueId:
          $ref: '#/components/schemas/docId'
        name:
          type: string
        status:
          $ref: '#/components/schemas/status-2'
        showId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        playlistId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        timelineId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        matrixCell:
          anyOf:
            - $ref: '#/components/schemas/matrixCell'
            - type: 'null'
        itemIndex:
          anyOf:
            - type: integer
              minimum: 0
            - type: 'null'
        assetId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        position:
          $ref: '#/components/schemas/position'
        duration:
          anyOf:
            - $ref: '#/components/schemas/seconds'
            - type: 'null'
        rate:
          $ref: '#/components/schemas/rate'
        loop:
          type: boolean
        volume:
          $ref: '#/components/schemas/unitInterval'
        opacity:
          $ref: '#/components/schemas/unitInterval'
        brightness:
          $ref: '#/components/schemas/unitInterval'
        rotation:
          type: number
          minimum: -360
          maximum: 360
        target:
          type: object
          properties:
            displays:
              type: array
              items:
                $ref: '#/components/schemas/docId'
            layer:
              type: integer
              minimum: 0
          additionalProperties: false
        pending:
          type: boolean
          description: last command not yet acknowledged by the engine (2 s timeout); a later player.state resolves it
        scheduledAt:
          anyOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
          description: pending scheduled start (play.at)
        error:
          anyOf:
            - $ref: '#/components/schemas/problem.schema'
            - type: 'null'
        revision:
          $ref: '#/components/schemas/revision'
        updatedAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    page-7:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/player'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    playerRef:
      description: 'Media player id in a path: a player id or the alias `main`.'
      anyOf:
        - $ref: '#/components/schemas/docId'
        - const: main
    parameter:
      description: A node / layer parameter (02d ParameterService UC-N1–N3; Screenberry-compatible surface).
      type: object
      required:
        - path
        - type
        - value
      properties:
        path:
          type: string
          pattern: ^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+){0,15}$
        type:
          enum:
            - float
            - int
            - bool
            - string
            - enum
            - colour
        value:
          anyOf:
            - type: number
            - type: boolean
            - type: string
        min:
          type: number
        max:
          type: number
        unit:
          type: string
        values:
          type: array
          items:
            type: string
          description: allowed values of an enum parameter
        readOnly:
          type: boolean
        description:
          type: string
      additionalProperties: false
    player.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/player.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). Media player state and playback command bodies (02e §5.3: players, play / pause / resume / stop / next / previous / seek, fireCue, setActiveShow, parameters). Root = GET /venues/{venueId}/players/{playerId}.'
      title: Media player
      allOf:
        - $ref: '#/components/schemas/player'
      $defs:
        player:
          type: object
          required:
            - id
            - venueId
            - status
            - position
            - revision
          properties:
            id:
              $ref: '#/components/schemas/docId'
            venueId:
              $ref: '#/components/schemas/docId'
            name:
              type: string
            status:
              $ref: '#/components/schemas/status-2'
            showId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            playlistId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            timelineId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            matrixCell:
              anyOf:
                - $ref: '#/components/schemas/matrixCell'
                - type: 'null'
            itemIndex:
              anyOf:
                - type: integer
                  minimum: 0
                - type: 'null'
            assetId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            position:
              $ref: '#/components/schemas/position'
            duration:
              anyOf:
                - $ref: '#/components/schemas/seconds'
                - type: 'null'
            rate:
              $ref: '#/components/schemas/rate'
            loop:
              type: boolean
            volume:
              $ref: '#/components/schemas/unitInterval'
            opacity:
              $ref: '#/components/schemas/unitInterval'
            brightness:
              $ref: '#/components/schemas/unitInterval'
            rotation:
              type: number
              minimum: -360
              maximum: 360
            target:
              type: object
              properties:
                displays:
                  type: array
                  items:
                    $ref: '#/components/schemas/docId'
                layer:
                  type: integer
                  minimum: 0
              additionalProperties: false
            pending:
              type: boolean
              description: last command not yet acknowledged by the engine (2 s timeout); a later player.state resolves it
            scheduledAt:
              anyOf:
                - $ref: '#/components/schemas/timestamp'
                - type: 'null'
              description: pending scheduled start (play.at)
            error:
              anyOf:
                - $ref: '#/components/schemas/problem.schema'
                - type: 'null'
            revision:
              $ref: '#/components/schemas/revision'
            updatedAt:
              $ref: '#/components/schemas/timestamp'
          patternProperties:
            ^x-: true
          additionalProperties: false
        status:
          anyOf:
            - enum:
                - idle
                - loading
                - playing
                - paused
                - stopped
                - error
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        rate:
          type: number
          exclusiveMinimum: 0
          maximum: 16
          description: playback rate, 1 = normal
        matrixCell:
          type: object
          required:
            - row
            - column
          properties:
            matrixId:
              $ref: '#/components/schemas/docId'
            row:
              $ref: '#/components/schemas/docId'
            column:
              $ref: '#/components/schemas/docId'
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/player'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        patch:
          description: PATCH /venues/{venueId}/players/{playerId} (merge-patch; playback is last-writer-wins, no If-Match).
          type: object
          minProperties: 1
          properties:
            rate:
              $ref: '#/components/schemas/rate'
            loop:
              type: boolean
            volume:
              $ref: '#/components/schemas/unitInterval'
            opacity:
              $ref: '#/components/schemas/unitInterval'
            brightness:
              $ref: '#/components/schemas/unitInterval'
            rotation:
              type: number
              minimum: -360
              maximum: 360
          patternProperties:
            ^x-: true
          additionalProperties: false
        play:
          description: 'POST …/play: what to play (absent = resume the loaded source / startup) and when. `at` = scheduled start on the server clock (multi-node sync, 02d UC-K5).'
          type: object
          properties:
            showId:
              $ref: '#/components/schemas/docId'
            playlistId:
              $ref: '#/components/schemas/docId'
            itemIndex:
              type: integer
              minimum: 0
            timelineId:
              $ref: '#/components/schemas/docId'
            matrixCell:
              $ref: '#/components/schemas/matrixCell'
            assetId:
              $ref: '#/components/schemas/docId'
            position:
              $ref: '#/components/schemas/position'
            at:
              $ref: '#/components/schemas/timestamp'
            loop:
              type: boolean
          patternProperties:
            ^x-: true
          additionalProperties: false
          not:
            anyOf:
              - required:
                  - playlistId
                  - timelineId
              - required:
                  - playlistId
                  - assetId
              - required:
                  - timelineId
                  - assetId
              - required:
                  - matrixCell
                  - playlistId
              - required:
                  - matrixCell
                  - timelineId
              - required:
                  - matrixCell
                  - assetId
        seek:
          description: 'POST …/seek: exactly one of position (s), marker (timeline marker id) or normalized (0..1).'
          type: object
          properties:
            position:
              $ref: '#/components/schemas/position'
            marker:
              $ref: '#/components/schemas/docId'
            normalized:
              $ref: '#/components/schemas/unitInterval'
            at:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
          oneOf:
            - required:
                - position
            - required:
                - marker
            - required:
                - normalized
        activeShow:
          description: 'PUT /venues/{venueId}/active-show: load a show onto the venue (mapped onto its geometry); showId null unloads.'
          type: object
          required:
            - showId
          properties:
            showId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            autoplay:
              type: boolean
              default: false
              description: start `show.startup` on player main
          additionalProperties: false
        activeShowState:
          type: object
          required:
            - venueId
            - showId
          properties:
            venueId:
              $ref: '#/components/schemas/docId'
            showId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            warnings:
              type: array
              items:
                $ref: '#/components/schemas/warning'
          additionalProperties: false
        fireCue:
          type: object
          properties:
            showId:
              $ref: '#/components/schemas/docId'
              description: default = active show
          additionalProperties: false
        cueFired:
          type: object
          required:
            - cueId
            - showId
            - firedAt
          properties:
            cueId:
              $ref: '#/components/schemas/docId'
            showId:
              $ref: '#/components/schemas/docId'
            firedAt:
              $ref: '#/components/schemas/timestamp'
            actions:
              type: array
              items:
                type: object
                required:
                  - do
                  - status
                properties:
                  do:
                    type: string
                  status:
                    enum:
                      - ok
                      - failed
                      - skipped
                  problem:
                    $ref: '#/components/schemas/problem.schema'
                additionalProperties: false
          additionalProperties: false
        parameterValue:
          description: PUT /venues/{venueId}/parameters/{path} body.
          type: object
          required:
            - value
          properties:
            value:
              anyOf:
                - type: number
                - type: boolean
                - type: string
            fadeSec:
              type: number
              minimum: 0
              maximum: 3600
          additionalProperties: false
        parameter:
          description: A node / layer parameter (02d ParameterService UC-N1–N3; Screenberry-compatible surface).
          type: object
          required:
            - path
            - type
            - value
          properties:
            path:
              type: string
              pattern: ^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+){0,15}$
            type:
              enum:
                - float
                - int
                - bool
                - string
                - enum
                - colour
            value:
              anyOf:
                - type: number
                - type: boolean
                - type: string
            min:
              type: number
            max:
              type: number
            unit:
              type: string
            values:
              type: array
              items:
                type: string
              description: allowed values of an enum parameter
            readOnly:
              type: boolean
            description:
              type: string
          additionalProperties: false
        parameterPage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/parameter'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
    patch-6:
      description: PATCH /venues/{venueId}/players/{playerId} (merge-patch; playback is last-writer-wins, no If-Match).
      type: object
      minProperties: 1
      properties:
        rate:
          $ref: '#/components/schemas/rate'
        loop:
          type: boolean
        volume:
          $ref: '#/components/schemas/unitInterval'
        opacity:
          $ref: '#/components/schemas/unitInterval'
        brightness:
          $ref: '#/components/schemas/unitInterval'
        rotation:
          type: number
          minimum: -360
          maximum: 360
      patternProperties:
        ^x-: true
      additionalProperties: false
    play:
      description: 'POST …/play: what to play (absent = resume the loaded source / startup) and when. `at` = scheduled start on the server clock (multi-node sync, 02d UC-K5).'
      type: object
      properties:
        showId:
          $ref: '#/components/schemas/docId'
        playlistId:
          $ref: '#/components/schemas/docId'
        itemIndex:
          type: integer
          minimum: 0
        timelineId:
          $ref: '#/components/schemas/docId'
        matrixCell:
          $ref: '#/components/schemas/matrixCell'
        assetId:
          $ref: '#/components/schemas/docId'
        position:
          $ref: '#/components/schemas/position'
        at:
          $ref: '#/components/schemas/timestamp'
        loop:
          type: boolean
      patternProperties:
        ^x-: true
      additionalProperties: false
      not:
        anyOf:
          - required:
              - playlistId
              - timelineId
          - required:
              - playlistId
              - assetId
          - required:
              - timelineId
              - assetId
          - required:
              - matrixCell
              - playlistId
          - required:
              - matrixCell
              - timelineId
          - required:
              - matrixCell
              - assetId
    seek:
      description: 'POST …/seek: exactly one of position (s), marker (timeline marker id) or normalized (0..1).'
      type: object
      properties:
        position:
          $ref: '#/components/schemas/position'
        marker:
          $ref: '#/components/schemas/docId'
        normalized:
          $ref: '#/components/schemas/unitInterval'
        at:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
      oneOf:
        - required:
            - position
        - required:
            - marker
        - required:
            - normalized
    fireCue:
      type: object
      properties:
        showId:
          $ref: '#/components/schemas/docId'
          description: default = active show
      additionalProperties: false
    cueFired:
      type: object
      required:
        - cueId
        - showId
        - firedAt
      properties:
        cueId:
          $ref: '#/components/schemas/docId'
        showId:
          $ref: '#/components/schemas/docId'
        firedAt:
          $ref: '#/components/schemas/timestamp'
        actions:
          type: array
          items:
            type: object
            required:
              - do
              - status
            properties:
              do:
                type: string
              status:
                enum:
                  - ok
                  - failed
                  - skipped
              problem:
                $ref: '#/components/schemas/problem.schema'
            additionalProperties: false
      additionalProperties: false
    activeShowState:
      type: object
      required:
        - venueId
        - showId
      properties:
        venueId:
          $ref: '#/components/schemas/docId'
        showId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/warning'
      additionalProperties: false
    activeShow:
      description: 'PUT /venues/{venueId}/active-show: load a show onto the venue (mapped onto its geometry); showId null unloads.'
      type: object
      required:
        - showId
      properties:
        showId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        autoplay:
          type: boolean
          default: false
          description: start `show.startup` on player main
      additionalProperties: false
    parameterPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/parameter'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    parameterValue:
      description: PUT /venues/{venueId}/parameters/{path} body.
      type: object
      required:
        - value
      properties:
        value:
          anyOf:
            - type: number
            - type: boolean
            - type: string
        fadeSec:
          type: number
          minimum: 0
          maximum: 3600
      additionalProperties: false
    mediaType:
      description: IANA media type.
      type: string
      pattern: ^[a-z]+/[A-Za-z0-9.+-]+$
    metadata:
      description: 'Upload-Metadata (tus: comma-separated `key base64(value)` pairs), decoded.'
      type: object
      required:
        - filename
        - purpose
      properties:
        filename:
          type: string
          minLength: 1
          maxLength: 512
        contentType:
          $ref: '#/components/schemas/mediaType'
        sha256:
          $ref: '#/components/schemas/sha256'
          description: digest of the whole file, verified on completion (upload-checksum-mismatch)
        purpose:
          anyOf:
            - enum:
                - asset
                - import
                - restore
                - capture
                - update
            - type: string
              pattern: ^[a-z][a-z0-9_-]{0,31}$
        assetId:
          $ref: '#/components/schemas/docId'
          description: 'purpose asset: create the asset with this id (or replace the content of an existing one)'
      additionalProperties: true
    upload.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/upload.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). tus 1.0.0 upload (02e §5.4, C12): JSON status of GET /uploads/{uploadId} for non-tus clients; the decoded Upload-Metadata. Root = upload status.'
      title: Upload
      type: object
      required:
        - id
        - length
        - offset
        - status
        - metadata
        - createdAt
      properties:
        id:
          $ref: '#/components/schemas/uuidv7'
        length:
          type: integer
          minimum: 0
          description: Upload-Length
        offset:
          type: integer
          minimum: 0
          description: Upload-Offset (bytes received and persisted)
        status:
          anyOf:
            - enum:
                - uploading
                - complete
                - failed
                - expired
            - type: string
              pattern: ^[a-z_]{1,32}$
        metadata:
          $ref: '#/components/schemas/metadata'
        assetId:
          $ref: '#/components/schemas/docId'
          description: asset created / updated on completion (purpose asset)
        problem:
          $ref: '#/components/schemas/problem.schema'
        expiresAt:
          $ref: '#/components/schemas/timestamp'
        createdAt:
          $ref: '#/components/schemas/timestamp'
        links:
          type: object
          properties:
            self:
              type: string
              format: uri-reference
            asset:
              type: string
              format: uri-reference
          additionalProperties: false
      additionalProperties: false
      $defs:
        metadata:
          description: 'Upload-Metadata (tus: comma-separated `key base64(value)` pairs), decoded.'
          type: object
          required:
            - filename
            - purpose
          properties:
            filename:
              type: string
              minLength: 1
              maxLength: 512
            contentType:
              $ref: '#/components/schemas/mediaType'
            sha256:
              $ref: '#/components/schemas/sha256'
              description: digest of the whole file, verified on completion (upload-checksum-mismatch)
            purpose:
              anyOf:
                - enum:
                    - asset
                    - import
                    - restore
                    - capture
                    - update
                - type: string
                  pattern: ^[a-z][a-z0-9_-]{0,31}$
            assetId:
              $ref: '#/components/schemas/docId'
              description: 'purpose asset: create the asset with this id (or replace the content of an existing one)'
          additionalProperties: true
        capabilities:
          description: Informative mirror of the OPTIONS /uploads headers (Tus-Version, Tus-Extension, Tus-Max-Size, Tus-Checksum-Algorithm).
          type: object
          required:
            - version
            - extensions
          properties:
            version:
              type: array
              items:
                type: string
            extensions:
              type: array
              items:
                anyOf:
                  - enum:
                      - creation
                      - creation-with-upload
                      - termination
                      - checksum
                      - expiration
                  - type: string
            maxSize:
              type: integer
              minimum: 0
            checksumAlgorithms:
              type: array
              items:
                type: string
            maxChunkBytes:
              type: integer
              minimum: 1
              description: server-recommended PATCH size (≤ 95 MB behind Cloudflare)
          additionalProperties: false
    type:
      anyOf:
        - enum:
            - video
            - image
            - audio
            - zelim
            - boranko
            - comics
            - realtime
            - stream
            - dome
            - other
        - type: string
          pattern: ^[a-z][a-z0-9._-]{0,63}$
    status-3:
      anyOf:
        - enum:
            - uploading
            - processing
            - ready
            - failed
        - type: string
          pattern: ^[a-z_]{1,32}$
    textureTransport:
      description: Shared GPU texture or spare network transport of a live source. Open list.
      anyOf:
        - enum:
            - spout
            - syphon
            - dmabuf
            - capture
            - ndi
        - type: string
          pattern: ^[a-z][a-z0-9_-]{0,31}$
    texture:
      description: Stream asset fed by another application on the engine's machine (derived by the server from `src`, read-only).
      type: object
      required:
        - transport
        - sender
      properties:
        transport:
          $ref: '#/components/schemas/textureTransport'
        sender:
          type: string
          minLength: 1
          maxLength: 256
          description: Spout sender, Syphon server, PipeWire node, capture device or NDI source name
        application:
          type: string
          maxLength: 256
          description: Syphon application name
        fallbackUrl:
          type: string
          format: uri
      additionalProperties: false
    media:
      description: 'Probe result (ffprobe): container, streams, duration. `probeUnavailable` when the server has no prober (asset still ready).'
      type: object
      properties:
        container:
          type: string
        duration:
          $ref: '#/components/schemas/seconds'
        video:
          type: object
          properties:
            codec:
              type: string
            resolution:
              type: array
              items:
                type: integer
                minimum: 1
              minItems: 2
              maxItems: 2
            fps:
              type: number
              exclusiveMinimum: 0
            bitDepth:
              type: integer
              minimum: 1
            pixelFormat:
              type: string
          additionalProperties: true
        audio:
          type: object
          properties:
            codec:
              type: string
            channels:
              type: integer
              minimum: 1
            layout:
              type: string
            sampleRate:
              type: integer
              minimum: 1
            ambisonicOrder:
              type: integer
              minimum: 0
              maximum: 7
          additionalProperties: true
        probeUnavailable:
          type: boolean
      additionalProperties: true
    asset-2:
      type: object
      required:
        - id
        - name
        - type
        - status
        - revision
        - createdAt
      properties:
        id:
          $ref: '#/components/schemas/docId'
        name:
          type: string
          minLength: 1
          maxLength: 512
          description: original file name (with extension) or a title
        type:
          $ref: '#/components/schemas/type'
        src:
          type: string
          description: 'external URI for registered (not stored) assets; absent for stored blobs. Type stream: also spout://<sender>, syphon://<app>/<server>, dmabuf://<pipewire-node>, v4l2:///dev/<device>, ndi://<source> (percent-encoded, docs/audio-reactive.md)'
        texture:
          $ref: '#/components/schemas/texture'
        mediaType:
          $ref: '#/components/schemas/mediaType'
        sha256:
          $ref: '#/components/schemas/sha256'
        size:
          type: integer
          minimum: 0
        status:
          $ref: '#/components/schemas/status-3'
        problem:
          $ref: '#/components/schemas/problem.schema'
          description: why the asset failed (e.g. upload-checksum-mismatch)
        progress:
          $ref: '#/components/schemas/unitInterval'
          description: upload / processing progress
        mapping:
          $ref: '#/components/schemas/mapping'
        media:
          $ref: '#/components/schemas/media'
        thumbnails:
          type: array
          items:
            type: object
            required:
              - size
              - url
            properties:
              size:
                type: integer
                minimum: 16
              url:
                type: string
                format: uri-reference
            additionalProperties: false
        tags:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 64
          uniqueItems: true
        usedBy:
          type: array
          items:
            $ref: '#/components/schemas/docId'
          description: ids of shows referencing the asset
        revision:
          $ref: '#/components/schemas/revision'
        createdAt:
          $ref: '#/components/schemas/timestamp'
        updatedAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
      allOf:
        - if:
            properties:
              status:
                const: failed
            required:
              - status
          then:
            required:
              - problem
    page-8:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/asset-2'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
        count:
          type: integer
          minimum: 0
      additionalProperties: false
    input-5:
      description: 'POST /assets JSON body: register a finished tus upload or an external URL (streams, CDN).'
      type: object
      properties:
        uploadId:
          $ref: '#/components/schemas/uuidv7'
        url:
          type: string
          format: uri
        id:
          $ref: '#/components/schemas/docId'
          description: client-chosen id (e.g. the .dome asset id); default UUIDv7
        name:
          type: string
          minLength: 1
          maxLength: 512
        type:
          $ref: '#/components/schemas/type'
        mapping:
          $ref: '#/components/schemas/mapping'
        tags:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 64
          uniqueItems: true
        sha256:
          $ref: '#/components/schemas/sha256'
          description: expected digest of a URL asset (verified on fetch)
        fallbackUrl:
          type: string
          format: uri
          description: 'stream assets: source to use while `url` is unavailable, e.g. ndi://…'
      additionalProperties: false
      oneOf:
        - required:
            - uploadId
        - required:
            - url
    multipart:
      description: 'POST /assets multipart/form-data body (simple upload ≤ 100 MB): part `file` + optional JSON fields.'
      type: object
      required:
        - file
      properties:
        file:
          type: string
          contentMediaType: application/octet-stream
        id:
          $ref: '#/components/schemas/docId'
        type:
          $ref: '#/components/schemas/type'
        tags:
          type: string
          description: comma-separated
    textureSource:
      description: A live source seen by the engine (GET /venues/{venueId}/texture-sources, event textureSource.state).
      type: object
      required:
        - id
        - transport
        - sender
        - status
      properties:
        id:
          $ref: '#/components/schemas/docId'
          description: server-assigned, stable while the sender keeps its name
        transport:
          $ref: '#/components/schemas/textureTransport'
        sender:
          type: string
          minLength: 1
          maxLength: 256
        application:
          type: string
          maxLength: 256
        status:
          anyOf:
            - enum:
                - online
                - offline
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        width:
          type: integer
          minimum: 1
        height:
          type: integer
          minimum: 1
        fps:
          type: number
          minimum: 0
          description: frames per second measured by the engine
        pixelFormat:
          type: string
          maxLength: 32
        assetId:
          $ref: '#/components/schemas/docId'
          description: the stream asset bound to this source, if any
        node:
          type: string
          maxLength: 64
          description: node whose engine sees the source
        lastSeen:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    countBytes:
      type: object
      required:
        - count
        - bytes
      properties:
        count:
          type: integer
          minimum: 0
        bytes:
          type: integer
          minimum: 0
      additionalProperties: false
    asset.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/asset.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Stored asset (02e §4, §5.4) = .dome asset fields + storage (sha256, size, status, probe data, thumbnails). Root = GET /assets/{assetId}.
      title: Asset
      allOf:
        - $ref: '#/components/schemas/asset-2'
      $defs:
        asset:
          type: object
          required:
            - id
            - name
            - type
            - status
            - revision
            - createdAt
          properties:
            id:
              $ref: '#/components/schemas/docId'
            name:
              type: string
              minLength: 1
              maxLength: 512
              description: original file name (with extension) or a title
            type:
              $ref: '#/components/schemas/type'
            src:
              type: string
              description: 'external URI for registered (not stored) assets; absent for stored blobs. Type stream: also spout://<sender>, syphon://<app>/<server>, dmabuf://<pipewire-node>, v4l2:///dev/<device>, ndi://<source> (percent-encoded, docs/audio-reactive.md)'
            texture:
              $ref: '#/components/schemas/texture'
            mediaType:
              $ref: '#/components/schemas/mediaType'
            sha256:
              $ref: '#/components/schemas/sha256'
            size:
              type: integer
              minimum: 0
            status:
              $ref: '#/components/schemas/status-3'
            problem:
              $ref: '#/components/schemas/problem.schema'
              description: why the asset failed (e.g. upload-checksum-mismatch)
            progress:
              $ref: '#/components/schemas/unitInterval'
              description: upload / processing progress
            mapping:
              $ref: '#/components/schemas/mapping'
            media:
              $ref: '#/components/schemas/media'
            thumbnails:
              type: array
              items:
                type: object
                required:
                  - size
                  - url
                properties:
                  size:
                    type: integer
                    minimum: 16
                  url:
                    type: string
                    format: uri-reference
                additionalProperties: false
            tags:
              type: array
              items:
                type: string
                minLength: 1
                maxLength: 64
              uniqueItems: true
            usedBy:
              type: array
              items:
                $ref: '#/components/schemas/docId'
              description: ids of shows referencing the asset
            revision:
              $ref: '#/components/schemas/revision'
            createdAt:
              $ref: '#/components/schemas/timestamp'
            updatedAt:
              $ref: '#/components/schemas/timestamp'
          patternProperties:
            ^x-: true
          additionalProperties: false
          allOf:
            - if:
                properties:
                  status:
                    const: failed
                required:
                  - status
              then:
                required:
                  - problem
        textureTransport:
          description: Shared GPU texture or spare network transport of a live source. Open list.
          anyOf:
            - enum:
                - spout
                - syphon
                - dmabuf
                - capture
                - ndi
            - type: string
              pattern: ^[a-z][a-z0-9_-]{0,31}$
        texture:
          description: Stream asset fed by another application on the engine's machine (derived by the server from `src`, read-only).
          type: object
          required:
            - transport
            - sender
          properties:
            transport:
              $ref: '#/components/schemas/textureTransport'
            sender:
              type: string
              minLength: 1
              maxLength: 256
              description: Spout sender, Syphon server, PipeWire node, capture device or NDI source name
            application:
              type: string
              maxLength: 256
              description: Syphon application name
            fallbackUrl:
              type: string
              format: uri
          additionalProperties: false
        textureSource:
          description: A live source seen by the engine (GET /venues/{venueId}/texture-sources, event textureSource.state).
          type: object
          required:
            - id
            - transport
            - sender
            - status
          properties:
            id:
              $ref: '#/components/schemas/docId'
              description: server-assigned, stable while the sender keeps its name
            transport:
              $ref: '#/components/schemas/textureTransport'
            sender:
              type: string
              minLength: 1
              maxLength: 256
            application:
              type: string
              maxLength: 256
            status:
              anyOf:
                - enum:
                    - online
                    - offline
                - type: string
                  pattern: ^[a-z][a-z_-]{0,31}$
            width:
              type: integer
              minimum: 1
            height:
              type: integer
              minimum: 1
            fps:
              type: number
              minimum: 0
              description: frames per second measured by the engine
            pixelFormat:
              type: string
              maxLength: 32
            assetId:
              $ref: '#/components/schemas/docId'
              description: the stream asset bound to this source, if any
            node:
              type: string
              maxLength: 64
              description: node whose engine sees the source
            lastSeen:
              $ref: '#/components/schemas/timestamp'
          patternProperties:
            ^x-: true
          additionalProperties: false
        textureSourcePage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/textureSource'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        type:
          anyOf:
            - enum:
                - video
                - image
                - audio
                - zelim
                - boranko
                - comics
                - realtime
                - stream
                - dome
                - other
            - type: string
              pattern: ^[a-z][a-z0-9._-]{0,63}$
        status:
          anyOf:
            - enum:
                - uploading
                - processing
                - ready
                - failed
            - type: string
              pattern: ^[a-z_]{1,32}$
        media:
          description: 'Probe result (ffprobe): container, streams, duration. `probeUnavailable` when the server has no prober (asset still ready).'
          type: object
          properties:
            container:
              type: string
            duration:
              $ref: '#/components/schemas/seconds'
            video:
              type: object
              properties:
                codec:
                  type: string
                resolution:
                  type: array
                  items:
                    type: integer
                    minimum: 1
                  minItems: 2
                  maxItems: 2
                fps:
                  type: number
                  exclusiveMinimum: 0
                bitDepth:
                  type: integer
                  minimum: 1
                pixelFormat:
                  type: string
              additionalProperties: true
            audio:
              type: object
              properties:
                codec:
                  type: string
                channels:
                  type: integer
                  minimum: 1
                layout:
                  type: string
                sampleRate:
                  type: integer
                  minimum: 1
                ambisonicOrder:
                  type: integer
                  minimum: 0
                  maximum: 7
              additionalProperties: true
            probeUnavailable:
              type: boolean
          additionalProperties: true
        input:
          description: 'POST /assets JSON body: register a finished tus upload or an external URL (streams, CDN).'
          type: object
          properties:
            uploadId:
              $ref: '#/components/schemas/uuidv7'
            url:
              type: string
              format: uri
            id:
              $ref: '#/components/schemas/docId'
              description: client-chosen id (e.g. the .dome asset id); default UUIDv7
            name:
              type: string
              minLength: 1
              maxLength: 512
            type:
              $ref: '#/components/schemas/type'
            mapping:
              $ref: '#/components/schemas/mapping'
            tags:
              type: array
              items:
                type: string
                minLength: 1
                maxLength: 64
              uniqueItems: true
            sha256:
              $ref: '#/components/schemas/sha256'
              description: expected digest of a URL asset (verified on fetch)
            fallbackUrl:
              type: string
              format: uri
              description: 'stream assets: source to use while `url` is unavailable, e.g. ndi://…'
          additionalProperties: false
          oneOf:
            - required:
                - uploadId
            - required:
                - url
        multipart:
          description: 'POST /assets multipart/form-data body (simple upload ≤ 100 MB): part `file` + optional JSON fields.'
          type: object
          required:
            - file
          properties:
            file:
              type: string
              contentMediaType: application/octet-stream
            id:
              $ref: '#/components/schemas/docId'
            type:
              $ref: '#/components/schemas/type'
            tags:
              type: string
              description: comma-separated
        patch:
          type: object
          minProperties: 1
          properties:
            name:
              type: string
              minLength: 1
              maxLength: 512
            type:
              $ref: '#/components/schemas/type'
            mapping:
              $ref: '#/components/schemas/mapping'
            tags:
              type: array
              items:
                type: string
                minLength: 1
                maxLength: 64
              uniqueItems: true
          patternProperties:
            ^x-: true
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/asset-2'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
            count:
              type: integer
              minimum: 0
          additionalProperties: false
        storage:
          description: GET /storage (replaces /api/v1/content/stats).
          type: object
          required:
            - totalBytes
            - usedBytes
            - freeBytes
          properties:
            totalBytes:
              type: integer
              minimum: 0
            usedBytes:
              type: integer
              minimum: 0
            freeBytes:
              type: integer
              minimum: 0
            quotaBytes:
              anyOf:
                - type: integer
                  minimum: 0
                - type: 'null'
            assets:
              $ref: '#/components/schemas/countBytes'
            uploads:
              $ref: '#/components/schemas/countBytes'
            byType:
              type: object
              additionalProperties:
                $ref: '#/components/schemas/countBytes'
          additionalProperties: false
        countBytes:
          type: object
          required:
            - count
            - bytes
          properties:
            count:
              type: integer
              minimum: 0
            bytes:
              type: integer
              minimum: 0
          additionalProperties: false
    patch-7:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 512
        type:
          $ref: '#/components/schemas/type'
        mapping:
          $ref: '#/components/schemas/mapping'
        tags:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 64
          uniqueItems: true
      patternProperties:
        ^x-: true
      additionalProperties: false
    storage:
      description: GET /storage (replaces /api/v1/content/stats).
      type: object
      required:
        - totalBytes
        - usedBytes
        - freeBytes
      properties:
        totalBytes:
          type: integer
          minimum: 0
        usedBytes:
          type: integer
          minimum: 0
        freeBytes:
          type: integer
          minimum: 0
        quotaBytes:
          anyOf:
            - type: integer
              minimum: 0
            - type: 'null'
        assets:
          $ref: '#/components/schemas/countBytes'
        uploads:
          $ref: '#/components/schemas/countBytes'
        byType:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/countBytes'
      additionalProperties: false
    mode:
      description: 'A8: digital = anAntaDigital only; hybrid = anAntaSound (mechanical devices); combined = both.'
      anyOf:
        - enum:
            - digital
            - hybrid
            - combined
        - type: string
          pattern: ^[a-z][a-z0-9_-]{0,31}$
    hertz:
      type: number
      exclusiveMinimum: 0
    eqBand:
      type: object
      required:
        - type
        - frequencyHz
      properties:
        type:
          anyOf:
            - enum:
                - peak
                - lowShelf
                - highShelf
                - lowPass
                - highPass
                - notch
                - allPass
            - type: string
        frequencyHz:
          $ref: '#/components/schemas/hertz'
        gainDb:
          type: number
          minimum: -48
          maximum: 24
        q:
          type: number
          exclusiveMinimum: 0
          maximum: 100
        enabled:
          type: boolean
          default: true
      additionalProperties: false
    zoneBody:
      type: object
      properties:
        name:
          type: string
          maxLength: 128
        speakers:
          type: array
          items:
            $ref: '#/components/schemas/docId'
          uniqueItems: true
        gainDb:
          $ref: '#/components/schemas/gainDb-2'
        delayMs:
          type: number
          minimum: 0
          maximum: 2000
        eq:
          type: array
          items:
            $ref: '#/components/schemas/eqBand'
          maxItems: 32
        mute:
          type: boolean
      patternProperties:
        ^x-: true
    zone:
      description: 'anAntaDigital zone: a subset of speakers with gain, delay, EQ.'
      type: object
      allOf:
        - $ref: '#/components/schemas/zoneBody'
      required:
        - id
        - speakers
        - gainDb
        - mute
        - revision
      properties:
        id:
          $ref: '#/components/schemas/docId'
        revision:
          $ref: '#/components/schemas/revision'
      unevaluatedProperties: false
    deviceKind:
      anyOf:
        - enum:
            - speaker
            - speaker_amp
            - microphone
            - qrd
            - patefon
            - karmic_cluster
            - resonator
        - type: string
          pattern: ^[a-z][a-z0-9_-]{0,31}$
    device:
      description: Audio device = .dome audio.devices[] / speakers[] entry + live state (02c §6).
      type: object
      required:
        - id
        - kind
        - state
      properties:
        id:
          $ref: '#/components/schemas/docId'
        kind:
          $ref: '#/components/schemas/deviceKind'
        name:
          type: string
        config:
          type: object
          description: the .dome audio device / speaker entry
          additionalProperties: true
        state:
          type: object
          required:
            - online
          properties:
            online:
              type: boolean
            engine:
              anyOf:
                - enum:
                    - anantasound
                    - anantadigital
                - type: string
            gainDb:
              $ref: '#/components/schemas/gainDb-2'
            mute:
              type: boolean
            levelDb:
              type: number
              maximum: 24
              description: last meter value (dBFS)
            temperatureC:
              type: number
            firmware:
              type: string
            faults:
              type: array
              items:
                type: string
            lastSeen:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: true
      patternProperties:
        ^x-: true
      additionalProperties: false
    id-2:
      type: string
      pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
    modulation:
      type: object
      required:
        - kind
        - rateHz
      properties:
        kind:
          enum:
            - am
            - fm
            - pm
            - binaural_beat
        rateHz:
          type: number
          minimum: 0.01
          maximum: 100
        depth:
          type: number
          minimum: 0
          maximum: 1
      patternProperties:
        ^x-: true
      additionalProperties: false
    multi:
      type: object
      description: MULTI generator parameters (NmSoundParams names, 02c §5.6)
      properties:
        program:
          type: string
          description: MULTI programme JSON text {"target":"f1,f2,…","autonext":s} (NmSoundParams.program); empty = single target
        target:
          type: number
          minimum: 0
          maximum: 20000
        enviroment:
          type: number
          minimum: 0
        modulation:
          type: number
        multi:
          type: number
        channel:
          type: number
          description: 'stereo phase angle code: 0, 1, 2, 60, 90, 120, 137, 180'
        audio:
          type: number
        compatJava:
          type: boolean
          default: false
      patternProperties:
        ^x-: true
      additionalProperties: false
    direction:
      type: object
      description: '.dome convention: azimuth 0 = front, clockwise; elevation 0 = spring line'
      properties:
        azimuthDeg:
          type: number
          minimum: -360
          maximum: 360
        elevationDeg:
          type: number
          minimum: -90
          maximum: 90
        distanceM:
          type: number
          minimum: 0
      patternProperties:
        ^x-: true
      additionalProperties: false
    segment:
      type: object
      required:
        - startS
        - durationS
      properties:
        id:
          $ref: '#/components/schemas/id-2'
        startS:
          type: number
          minimum: 0
        durationS:
          type: number
          exclusiveMinimum: 0
        carrierHz:
          type: number
          minimum: 20
          maximum: 20000
        harmonicsHz:
          type: array
          items:
            type: number
            minimum: 20
            maximum: 20000
        amplitude:
          type: number
          minimum: 0
          maximum: 1
        attackS:
          type: number
          minimum: 0
        releaseS:
          type: number
          minimum: 0
        modulation:
          $ref: '#/components/schemas/modulation'
        multi:
          $ref: '#/components/schemas/multi'
      patternProperties:
        ^x-: true
      additionalProperties: false
    strike:
      type: object
      required:
        - striker
        - target
        - beat
        - velocity
      properties:
        striker:
          type: string
          minLength: 1
        target:
          type: string
          minLength: 1
        beat:
          type: number
          minimum: 0
        velocity:
          type: number
          minimum: 0
          maximum: 1
        durationMs:
          type: integer
          minimum: 1
          maximum: 200
        accent:
          enum:
            - none
            - soft
            - medium
            - strong
            - crescendo
            - decrescendo
      patternProperties:
        ^x-: true
      additionalProperties: false
    pattern:
      type: object
      required:
        - tempoBpm
        - strikes
      properties:
        tempoBpm:
          type: number
          exclusiveMinimum: 0
          maximum: 600
        beatsPerLoop:
          type: number
          exclusiveMinimum: 0
        loops:
          type:
            - integer
            - 'null'
          minimum: 1
          description: null = until stopped
        velocityCurve:
          enum:
            - linear
            - exponential
            - logarithmic
            - sigmoid
        strikes:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/strike'
      patternProperties:
        ^x-: true
      additionalProperties: false
    formatVersion:
      type: string
      pattern: ^1\.\d+\.\d+$
    i18n:
      type: object
      minProperties: 1
      propertyNames:
        pattern: ^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$
      additionalProperties:
        type: string
    role-2:
      type: object
      required:
        - class
      properties:
        class:
          enum:
            - striker_array
            - resonator
            - phonograph
            - initiator
            - signal_generator
            - tactile
            - speakers
        min:
          type: integer
          minimum: 1
          default: 1
        optional:
          type: boolean
          default: false
        description:
          type: string
      patternProperties:
        ^x-: true
      additionalProperties: false
    toneTrack:
      type: object
      required:
        - kind
        - role
        - segments
      properties:
        kind:
          const: tone
        id:
          $ref: '#/components/schemas/id-2'
        role:
          type: string
        direction:
          $ref: '#/components/schemas/direction'
        transition:
          enum:
            - linear
            - exponential
            - stepped
            - smooth
        gainDb:
          type: number
          maximum: 12
        segments:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/segment'
      patternProperties:
        ^x-: true
      additionalProperties: false
    strikeTrack:
      type: object
      required:
        - kind
        - role
        - pattern
      properties:
        kind:
          const: strike
        id:
          $ref: '#/components/schemas/id-2'
        role:
          type: string
        startS:
          type: number
          minimum: 0
        pattern:
          $ref: '#/components/schemas/pattern'
      patternProperties:
        ^x-: true
      additionalProperties: false
    mediaTrack:
      type: object
      required:
        - kind
        - src
      properties:
        kind:
          const: media
        id:
          $ref: '#/components/schemas/id-2'
        role:
          type: string
          default: speakers
        src:
          type: string
          minLength: 1
        startS:
          type: number
          minimum: 0
        gainDb:
          type: number
          maximum: 12
        loop:
          type: boolean
        sha256:
          type: string
          pattern: ^[0-9a-f]{64}$
        direction:
          $ref: '#/components/schemas/direction'
      patternProperties:
        ^x-: true
      additionalProperties: false
    motorTrack:
      type: object
      required:
        - kind
        - role
        - events
      properties:
        kind:
          const: motor
        id:
          $ref: '#/components/schemas/id-2'
        role:
          type: string
        events:
          type: array
          minItems: 1
          items:
            type: object
            required:
              - atS
              - 'on'
            properties:
              atS:
                type: number
                minimum: 0
              'on':
                type: boolean
              rpm:
                enum:
                  - 33.333
                  - 45
            patternProperties:
              ^x-: true
            additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
    programme.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/ananta/1.0/programme.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Content owned by 02c (AU, ananta_core 2.AU.2); mirrored here per PR5 / RC2 (CR-AU-2), contract copy is the published one.
      title: Ananta programme
      description: L6 programme preset (*.ananta.json), 02c §10. Played by generic L1 players; claims only in x-meta.
      type: object
      required:
        - format
        - formatVersion
        - id
        - title
        - tracks
      properties:
        $schema:
          type: string
        format:
          const: ananta-programme
        formatVersion:
          $ref: '#/components/schemas/formatVersion'
        id:
          $ref: '#/components/schemas/id-2'
        title:
          $ref: '#/components/schemas/i18n'
        description:
          $ref: '#/components/schemas/i18n'
        tags:
          type: array
          items:
            type: string
        license:
          type: string
          default: NativeMindNONC
        durationS:
          type: number
          minimum: 0
        modes:
          type: array
          items:
            enum:
              - digital
              - hybrid
              - combined
          uniqueItems: true
        roles:
          type: object
          description: role name → required device class / capabilities
          propertyNames:
            $ref: '#/components/schemas/id-2'
          additionalProperties:
            $ref: '#/components/schemas/role-2'
        tracks:
          type: array
          minItems: 1
          items:
            oneOf:
              - $ref: '#/components/schemas/toneTrack'
              - $ref: '#/components/schemas/strikeTrack'
              - $ref: '#/components/schemas/mediaTrack'
              - $ref: '#/components/schemas/motorTrack'
        x-meta:
          type: object
          description: app-specific (e.g. intended effect labels); ignored by engines
      patternProperties:
        ^x-: true
      additionalProperties: false
      $defs:
        i18n:
          type: object
          minProperties: 1
          propertyNames:
            pattern: ^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$
          additionalProperties:
            type: string
        id:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
        formatVersion:
          type: string
          pattern: ^1\.\d+\.\d+$
        direction:
          type: object
          description: '.dome convention: azimuth 0 = front, clockwise; elevation 0 = spring line'
          properties:
            azimuthDeg:
              type: number
              minimum: -360
              maximum: 360
            elevationDeg:
              type: number
              minimum: -90
              maximum: 90
            distanceM:
              type: number
              minimum: 0
          patternProperties:
            ^x-: true
          additionalProperties: false
        multi:
          type: object
          description: MULTI generator parameters (NmSoundParams names, 02c §5.6)
          properties:
            program:
              type: string
              description: MULTI programme JSON text {"target":"f1,f2,…","autonext":s} (NmSoundParams.program); empty = single target
            target:
              type: number
              minimum: 0
              maximum: 20000
            enviroment:
              type: number
              minimum: 0
            modulation:
              type: number
            multi:
              type: number
            channel:
              type: number
              description: 'stereo phase angle code: 0, 1, 2, 60, 90, 120, 137, 180'
            audio:
              type: number
            compatJava:
              type: boolean
              default: false
          patternProperties:
            ^x-: true
          additionalProperties: false
        modulation:
          type: object
          required:
            - kind
            - rateHz
          properties:
            kind:
              enum:
                - am
                - fm
                - pm
                - binaural_beat
            rateHz:
              type: number
              minimum: 0.01
              maximum: 100
            depth:
              type: number
              minimum: 0
              maximum: 1
          patternProperties:
            ^x-: true
          additionalProperties: false
        segment:
          type: object
          required:
            - startS
            - durationS
          properties:
            id:
              $ref: '#/components/schemas/id-2'
            startS:
              type: number
              minimum: 0
            durationS:
              type: number
              exclusiveMinimum: 0
            carrierHz:
              type: number
              minimum: 20
              maximum: 20000
            harmonicsHz:
              type: array
              items:
                type: number
                minimum: 20
                maximum: 20000
            amplitude:
              type: number
              minimum: 0
              maximum: 1
            attackS:
              type: number
              minimum: 0
            releaseS:
              type: number
              minimum: 0
            modulation:
              $ref: '#/components/schemas/modulation'
            multi:
              $ref: '#/components/schemas/multi'
          patternProperties:
            ^x-: true
          additionalProperties: false
        role:
          type: object
          required:
            - class
          properties:
            class:
              enum:
                - striker_array
                - resonator
                - phonograph
                - initiator
                - signal_generator
                - tactile
                - speakers
            min:
              type: integer
              minimum: 1
              default: 1
            optional:
              type: boolean
              default: false
            description:
              type: string
          patternProperties:
            ^x-: true
          additionalProperties: false
        toneTrack:
          type: object
          required:
            - kind
            - role
            - segments
          properties:
            kind:
              const: tone
            id:
              $ref: '#/components/schemas/id-2'
            role:
              type: string
            direction:
              $ref: '#/components/schemas/direction'
            transition:
              enum:
                - linear
                - exponential
                - stepped
                - smooth
            gainDb:
              type: number
              maximum: 12
            segments:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/segment'
          patternProperties:
            ^x-: true
          additionalProperties: false
        strikeTrack:
          type: object
          required:
            - kind
            - role
            - pattern
          properties:
            kind:
              const: strike
            id:
              $ref: '#/components/schemas/id-2'
            role:
              type: string
            startS:
              type: number
              minimum: 0
            pattern:
              $ref: '#/components/schemas/pattern'
          patternProperties:
            ^x-: true
          additionalProperties: false
        mediaTrack:
          type: object
          required:
            - kind
            - src
          properties:
            kind:
              const: media
            id:
              $ref: '#/components/schemas/id-2'
            role:
              type: string
              default: speakers
            src:
              type: string
              minLength: 1
            startS:
              type: number
              minimum: 0
            gainDb:
              type: number
              maximum: 12
            loop:
              type: boolean
            sha256:
              type: string
              pattern: ^[0-9a-f]{64}$
            direction:
              $ref: '#/components/schemas/direction'
          patternProperties:
            ^x-: true
          additionalProperties: false
        motorTrack:
          type: object
          required:
            - kind
            - role
            - events
          properties:
            kind:
              const: motor
            id:
              $ref: '#/components/schemas/id-2'
            role:
              type: string
            events:
              type: array
              minItems: 1
              items:
                type: object
                required:
                  - atS
                  - 'on'
                properties:
                  atS:
                    type: number
                    minimum: 0
                  'on':
                    type: boolean
                  rpm:
                    enum:
                      - 33.333
                      - 45
                patternProperties:
                  ^x-: true
                additionalProperties: false
          patternProperties:
            ^x-: true
          additionalProperties: false
    pattern.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/ananta/1.0/pattern.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Content owned by 02c (AU, ananta_core 2.AU.2); mirrored here per PR5 / RC2 (CR-AU-2), contract copy is the published one.
      title: Ananta strike pattern
      description: Reusable strike pattern for striker arrays (ananta-pattern), 02c §10.
      type: object
      required:
        - format
        - formatVersion
        - id
        - title
        - pattern
      properties:
        $schema:
          type: string
        format:
          const: ananta-pattern
        formatVersion:
          $ref: '#/components/schemas/formatVersion'
        id:
          $ref: '#/components/schemas/id-2'
        title:
          $ref: '#/components/schemas/i18n'
        description:
          $ref: '#/components/schemas/i18n'
        tags:
          type: array
          items:
            type: string
        license:
          type: string
          default: NativeMindNONC
        pattern:
          $ref: '#/components/schemas/pattern'
        x-meta:
          type: object
      patternProperties:
        ^x-: true
      additionalProperties: false
      $defs:
        i18n:
          type: object
          minProperties: 1
          propertyNames:
            pattern: ^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$
          additionalProperties:
            type: string
        id:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
        formatVersion:
          type: string
          pattern: ^1\.\d+\.\d+$
        strike:
          type: object
          required:
            - striker
            - target
            - beat
            - velocity
          properties:
            striker:
              type: string
              minLength: 1
            target:
              type: string
              minLength: 1
            beat:
              type: number
              minimum: 0
            velocity:
              type: number
              minimum: 0
              maximum: 1
            durationMs:
              type: integer
              minimum: 1
              maximum: 200
            accent:
              enum:
                - none
                - soft
                - medium
                - strong
                - crescendo
                - decrescendo
          patternProperties:
            ^x-: true
          additionalProperties: false
        pattern:
          type: object
          required:
            - tempoBpm
            - strikes
          properties:
            tempoBpm:
              type: number
              exclusiveMinimum: 0
              maximum: 600
            beatsPerLoop:
              type: number
              exclusiveMinimum: 0
            loops:
              type:
                - integer
                - 'null'
              minimum: 1
              description: null = until stopped
            velocityCurve:
              enum:
                - linear
                - exponential
                - logarithmic
                - sigmoid
            strikes:
              type: array
              minItems: 1
              items:
                $ref: '#/components/schemas/strike'
          patternProperties:
            ^x-: true
          additionalProperties: false
    item:
      type: object
      required:
        - programme
      properties:
        programme:
          type: object
          description: reference by content URI or by programme id
          properties:
            uri:
              type: string
              minLength: 1
            id:
              $ref: '#/components/schemas/id-2'
            sha256:
              type: string
              pattern: ^[0-9a-f]{64}$
          patternProperties:
            ^x-: true
          additionalProperties: false
          anyOf:
            - required:
                - uri
            - required:
                - id
        gapS:
          type: number
          minimum: 0
          default: 0
        repeat:
          type: integer
          minimum: 1
          default: 1
        gainDb:
          type: number
          maximum: 12
      patternProperties:
        ^x-: true
      additionalProperties: false
    session.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/ananta/1.0/session.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Content owned by 02c (AU, ananta_core 2.AU.2); mirrored here per PR5 / RC2 (CR-AU-2), contract copy is the published one.
      title: Ananta session
      description: Ordered list of programme references + gaps + loop, global gain, default mode (ananta-session), 02c §10.
      type: object
      required:
        - format
        - formatVersion
        - id
        - title
        - items
      properties:
        $schema:
          type: string
        format:
          const: ananta-session
        formatVersion:
          $ref: '#/components/schemas/formatVersion'
        id:
          $ref: '#/components/schemas/id-2'
        title:
          $ref: '#/components/schemas/i18n'
        description:
          $ref: '#/components/schemas/i18n'
        tags:
          type: array
          items:
            type: string
        license:
          type: string
          default: NativeMindNONC
        mode:
          enum:
            - digital
            - hybrid
            - combined
        gainDb:
          type: number
          maximum: 12
        loop:
          type: boolean
          default: false
        items:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/item'
        x-meta:
          type: object
      patternProperties:
        ^x-: true
      additionalProperties: false
      $defs:
        i18n:
          type: object
          minProperties: 1
          propertyNames:
            pattern: ^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$
          additionalProperties:
            type: string
        id:
          type: string
          pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
        formatVersion:
          type: string
          pattern: ^1\.\d+\.\d+$
        item:
          type: object
          required:
            - programme
          properties:
            programme:
              type: object
              description: reference by content URI or by programme id
              properties:
                uri:
                  type: string
                  minLength: 1
                id:
                  $ref: '#/components/schemas/id-2'
                sha256:
                  type: string
                  pattern: ^[0-9a-f]{64}$
              patternProperties:
                ^x-: true
              additionalProperties: false
              anyOf:
                - required:
                    - uri
                - required:
                    - id
            gapS:
              type: number
              minimum: 0
              default: 0
            repeat:
              type: integer
              minimum: 1
              default: 1
            gainDb:
              type: number
              maximum: 12
          patternProperties:
            ^x-: true
          additionalProperties: false
    presetKind:
      enum:
        - programme
        - pattern
        - session
    targets:
      type: object
      properties:
        devices:
          type: array
          items:
            $ref: '#/components/schemas/docId'
          uniqueItems: true
        zones:
          type: array
          items:
            $ref: '#/components/schemas/docId'
          uniqueItems: true
        speakers:
          type: array
          items:
            $ref: '#/components/schemas/docId'
          uniqueItems: true
      additionalProperties: false
    session:
      type: object
      required:
        - id
        - venueId
        - presetId
        - status
        - startedAt
      properties:
        id:
          $ref: '#/components/schemas/uuidv7'
        venueId:
          $ref: '#/components/schemas/docId'
        presetId:
          $ref: '#/components/schemas/docId'
        status:
          anyOf:
            - enum:
                - starting
                - running
                - paused
                - finished
                - stopped
                - failed
            - type: string
        engines:
          type: array
          items:
            type: string
        position:
          $ref: '#/components/schemas/position'
        duration:
          $ref: '#/components/schemas/seconds'
        itemIndex:
          type: integer
          minimum: 0
        targets:
          $ref: '#/components/schemas/targets'
        problem:
          $ref: '#/components/schemas/problem.schema'
        startedAt:
          $ref: '#/components/schemas/timestamp'
        endedAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    generator:
      description: Test / MULTI generator (iScillator semantics; replaces generate_test_signal).
      type: object
      required:
        - signal
      properties:
        signal:
          anyOf:
            - enum:
                - sine
                - pink
                - white
                - sweep
                - multi
            - type: string
        frequencies:
          type: array
          items:
            $ref: '#/components/schemas/hertz'
          maxItems: 64
          description: 'sine: [f]; sweep: [from, to]; multi: up to 64 partials'
        gainDb:
          type: number
          minimum: -96
          maximum: 0
        durationS:
          type: number
          exclusiveMinimum: 0
        targets:
          $ref: '#/components/schemas/targets'
      additionalProperties: false
    engineRef:
      type: object
      required:
        - status
      properties:
        status:
          anyOf:
            - enum:
                - up
                - degraded
                - down
                - incompatible
                - disabled
            - type: string
        version:
          type: string
      additionalProperties: false
    audio.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/audio.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). Audio through the media server (02e §5.5, 02c A8): venue audio state + patch, zones, devices (+ test), presets (02c ananta/1.0 programme / pattern / session, RC2), sessions, generator, source position. Root = GET /venues/{venueId}/audio.'
      title: Audio state
      type: object
      required:
        - venueId
        - mode
        - masterGainDb
        - mute
        - revision
      properties:
        venueId:
          $ref: '#/components/schemas/docId'
        mode:
          $ref: '#/components/schemas/mode'
        masterGainDb:
          $ref: '#/components/schemas/gainDb-2'
        mute:
          type: boolean
        engines:
          type: object
          properties:
            anantasound:
              $ref: '#/components/schemas/engineRef'
            anantadigital:
              $ref: '#/components/schemas/engineRef'
          additionalProperties:
            $ref: '#/components/schemas/engineRef'
        layoutRevision:
          $ref: '#/components/schemas/revision'
        revision:
          $ref: '#/components/schemas/revision'
      patternProperties:
        ^x-: true
      additionalProperties: false
      $defs:
        mode:
          description: 'A8: digital = anAntaDigital only; hybrid = anAntaSound (mechanical devices); combined = both.'
          anyOf:
            - enum:
                - digital
                - hybrid
                - combined
            - type: string
              pattern: ^[a-z][a-z0-9_-]{0,31}$
        engineRef:
          type: object
          required:
            - status
          properties:
            status:
              anyOf:
                - enum:
                    - up
                    - degraded
                    - down
                    - incompatible
                    - disabled
                - type: string
            version:
              type: string
          additionalProperties: false
        patch:
          type: object
          minProperties: 1
          properties:
            mode:
              $ref: '#/components/schemas/mode'
            masterGainDb:
              $ref: '#/components/schemas/gainDb-2'
            mute:
              type: boolean
          patternProperties:
            ^x-: true
          additionalProperties: false
        eqBand:
          type: object
          required:
            - type
            - frequencyHz
          properties:
            type:
              anyOf:
                - enum:
                    - peak
                    - lowShelf
                    - highShelf
                    - lowPass
                    - highPass
                    - notch
                    - allPass
                - type: string
            frequencyHz:
              $ref: '#/components/schemas/hertz'
            gainDb:
              type: number
              minimum: -48
              maximum: 24
            q:
              type: number
              exclusiveMinimum: 0
              maximum: 100
            enabled:
              type: boolean
              default: true
          additionalProperties: false
        zoneBody:
          type: object
          properties:
            name:
              type: string
              maxLength: 128
            speakers:
              type: array
              items:
                $ref: '#/components/schemas/docId'
              uniqueItems: true
            gainDb:
              $ref: '#/components/schemas/gainDb-2'
            delayMs:
              type: number
              minimum: 0
              maximum: 2000
            eq:
              type: array
              items:
                $ref: '#/components/schemas/eqBand'
              maxItems: 32
            mute:
              type: boolean
          patternProperties:
            ^x-: true
        zone:
          description: 'anAntaDigital zone: a subset of speakers with gain, delay, EQ.'
          type: object
          allOf:
            - $ref: '#/components/schemas/zoneBody'
          required:
            - id
            - speakers
            - gainDb
            - mute
            - revision
          properties:
            id:
              $ref: '#/components/schemas/docId'
            revision:
              $ref: '#/components/schemas/revision'
          unevaluatedProperties: false
        zoneInput:
          type: object
          allOf:
            - $ref: '#/components/schemas/zoneBody'
          required:
            - speakers
          unevaluatedProperties: false
        zonePatch:
          type: object
          allOf:
            - $ref: '#/components/schemas/zoneBody'
          minProperties: 1
          unevaluatedProperties: false
        zonePage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/zone'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        deviceKind:
          anyOf:
            - enum:
                - speaker
                - speaker_amp
                - microphone
                - qrd
                - patefon
                - karmic_cluster
                - resonator
            - type: string
              pattern: ^[a-z][a-z0-9_-]{0,31}$
        device:
          description: Audio device = .dome audio.devices[] / speakers[] entry + live state (02c §6).
          type: object
          required:
            - id
            - kind
            - state
          properties:
            id:
              $ref: '#/components/schemas/docId'
            kind:
              $ref: '#/components/schemas/deviceKind'
            name:
              type: string
            config:
              type: object
              description: the .dome audio device / speaker entry
              additionalProperties: true
            state:
              type: object
              required:
                - online
              properties:
                online:
                  type: boolean
                engine:
                  anyOf:
                    - enum:
                        - anantasound
                        - anantadigital
                    - type: string
                gainDb:
                  $ref: '#/components/schemas/gainDb-2'
                mute:
                  type: boolean
                levelDb:
                  type: number
                  maximum: 24
                  description: last meter value (dBFS)
                temperatureC:
                  type: number
                firmware:
                  type: string
                faults:
                  type: array
                  items:
                    type: string
                lastSeen:
                  $ref: '#/components/schemas/timestamp'
              additionalProperties: true
          patternProperties:
            ^x-: true
          additionalProperties: false
        devicePatch:
          type: object
          minProperties: 1
          properties:
            name:
              type: string
            gainDb:
              $ref: '#/components/schemas/gainDb-2'
            mute:
              type: boolean
            config:
              type: object
              additionalProperties: true
          additionalProperties: false
        devicePage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/device'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        deviceTest:
          type: object
          properties:
            kind:
              anyOf:
                - enum:
                    - ping
                    - tone
                    - selftest
                    - strike
                - type: string
              default: ping
            frequencyHz:
              $ref: '#/components/schemas/hertz'
            gainDb:
              type: number
              minimum: -96
              maximum: 0
            durationS:
              type: number
              exclusiveMinimum: 0
              maximum: 30
          additionalProperties: false
        deviceTestResult:
          type: object
          required:
            - deviceId
            - ok
          properties:
            deviceId:
              $ref: '#/components/schemas/docId'
            ok:
              type: boolean
            latencyMs:
              type: number
              minimum: 0
            detail:
              type: string
            problem:
              $ref: '#/components/schemas/problem.schema'
          additionalProperties: false
        presetKind:
          enum:
            - programme
            - pattern
            - session
        preset:
          description: 'Installed L6 preset: an ananta/1.0 programme, pattern or session document (02c, RC2).'
          type: object
          required:
            - id
            - kind
            - revision
            - document
          properties:
            id:
              $ref: '#/components/schemas/docId'
            kind:
              $ref: '#/components/schemas/presetKind'
            title:
              $ref: '#/components/schemas/i18n-text.schema'
            revision:
              $ref: '#/components/schemas/revision'
            document:
              type: object
            sha256:
              $ref: '#/components/schemas/sha256'
            updatedAt:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
          allOf:
            - if:
                properties:
                  kind:
                    const: programme
                required:
                  - kind
              then:
                properties:
                  document:
                    $ref: '#/components/schemas/programme.schema'
            - if:
                properties:
                  kind:
                    const: pattern
                required:
                  - kind
              then:
                properties:
                  document:
                    $ref: '#/components/schemas/pattern.schema'
            - if:
                properties:
                  kind:
                    const: session
                required:
                  - kind
              then:
                properties:
                  document:
                    $ref: '#/components/schemas/session.schema'
        presetInput:
          description: PUT /venues/{venueId}/audio/presets/{presetId} body.
          type: object
          required:
            - kind
            - document
          properties:
            kind:
              $ref: '#/components/schemas/presetKind'
            document:
              type: object
          additionalProperties: false
          allOf:
            - if:
                properties:
                  kind:
                    const: programme
                required:
                  - kind
              then:
                properties:
                  document:
                    $ref: '#/components/schemas/programme.schema'
            - if:
                properties:
                  kind:
                    const: pattern
                required:
                  - kind
              then:
                properties:
                  document:
                    $ref: '#/components/schemas/pattern.schema'
            - if:
                properties:
                  kind:
                    const: session
                required:
                  - kind
              then:
                properties:
                  document:
                    $ref: '#/components/schemas/session.schema'
        presetPage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                type: object
                required:
                  - id
                  - kind
                  - revision
                properties:
                  id:
                    $ref: '#/components/schemas/docId'
                  kind:
                    $ref: '#/components/schemas/presetKind'
                  title:
                    $ref: '#/components/schemas/i18n-text.schema'
                  revision:
                    $ref: '#/components/schemas/revision'
                additionalProperties: false
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        targets:
          type: object
          properties:
            devices:
              type: array
              items:
                $ref: '#/components/schemas/docId'
              uniqueItems: true
            zones:
              type: array
              items:
                $ref: '#/components/schemas/docId'
              uniqueItems: true
            speakers:
              type: array
              items:
                $ref: '#/components/schemas/docId'
              uniqueItems: true
          additionalProperties: false
        sessionStart:
          description: 'POST /venues/{venueId}/audio/sessions: run an installed preset (programme or session).'
          type: object
          required:
            - presetId
          properties:
            presetId:
              $ref: '#/components/schemas/docId'
            gainDb:
              type: number
              minimum: -96
              maximum: 12
            targets:
              $ref: '#/components/schemas/targets'
            at:
              $ref: '#/components/schemas/timestamp'
            loop:
              type: boolean
          additionalProperties: false
        session:
          type: object
          required:
            - id
            - venueId
            - presetId
            - status
            - startedAt
          properties:
            id:
              $ref: '#/components/schemas/uuidv7'
            venueId:
              $ref: '#/components/schemas/docId'
            presetId:
              $ref: '#/components/schemas/docId'
            status:
              anyOf:
                - enum:
                    - starting
                    - running
                    - paused
                    - finished
                    - stopped
                    - failed
                - type: string
            engines:
              type: array
              items:
                type: string
            position:
              $ref: '#/components/schemas/position'
            duration:
              $ref: '#/components/schemas/seconds'
            itemIndex:
              type: integer
              minimum: 0
            targets:
              $ref: '#/components/schemas/targets'
            problem:
              $ref: '#/components/schemas/problem.schema'
            startedAt:
              $ref: '#/components/schemas/timestamp'
            endedAt:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
        sessionPage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/session'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        generator:
          description: Test / MULTI generator (iScillator semantics; replaces generate_test_signal).
          type: object
          required:
            - signal
          properties:
            signal:
              anyOf:
                - enum:
                    - sine
                    - pink
                    - white
                    - sweep
                    - multi
                - type: string
            frequencies:
              type: array
              items:
                $ref: '#/components/schemas/hertz'
              maxItems: 64
              description: 'sine: [f]; sweep: [from, to]; multi: up to 64 partials'
            gainDb:
              type: number
              minimum: -96
              maximum: 0
            durationS:
              type: number
              exclusiveMinimum: 0
            targets:
              $ref: '#/components/schemas/targets'
          additionalProperties: false
        generatorState:
          type: object
          required:
            - running
          properties:
            running:
              type: boolean
            settings:
              $ref: '#/components/schemas/generator'
            startedAt:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
        sourcePosition:
          description: Interactive / realtime source position (spherical, listener at the dome centre).
          type: object
          required:
            - azimuth
            - elevation
          properties:
            azimuth:
              type: number
              minimum: -360
              maximum: 360
              description: degrees, 0 = front, positive = left (ambisonic convention)
            elevation:
              type: number
              minimum: -90
              maximum: 90
            distance:
              type: number
              exclusiveMinimum: 0
              description: metres
            rampMs:
              type: number
              minimum: 0
              maximum: 10000
          additionalProperties: false
    patch-8:
      type: object
      minProperties: 1
      properties:
        mode:
          $ref: '#/components/schemas/mode'
        masterGainDb:
          $ref: '#/components/schemas/gainDb-2'
        mute:
          type: boolean
      patternProperties:
        ^x-: true
      additionalProperties: false
    zonePage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/zone'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    zoneInput:
      type: object
      allOf:
        - $ref: '#/components/schemas/zoneBody'
      required:
        - speakers
      unevaluatedProperties: false
    zonePatch:
      type: object
      allOf:
        - $ref: '#/components/schemas/zoneBody'
      minProperties: 1
      unevaluatedProperties: false
    devicePage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/device'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    devicePatch:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
        gainDb:
          $ref: '#/components/schemas/gainDb-2'
        mute:
          type: boolean
        config:
          type: object
          additionalProperties: true
      additionalProperties: false
    deviceTest:
      type: object
      properties:
        kind:
          anyOf:
            - enum:
                - ping
                - tone
                - selftest
                - strike
            - type: string
          default: ping
        frequencyHz:
          $ref: '#/components/schemas/hertz'
        gainDb:
          type: number
          minimum: -96
          maximum: 0
        durationS:
          type: number
          exclusiveMinimum: 0
          maximum: 30
      additionalProperties: false
    deviceTestResult:
      type: object
      required:
        - deviceId
        - ok
      properties:
        deviceId:
          $ref: '#/components/schemas/docId'
        ok:
          type: boolean
        latencyMs:
          type: number
          minimum: 0
        detail:
          type: string
        problem:
          $ref: '#/components/schemas/problem.schema'
      additionalProperties: false
    presetPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            type: object
            required:
              - id
              - kind
              - revision
            properties:
              id:
                $ref: '#/components/schemas/docId'
              kind:
                $ref: '#/components/schemas/presetKind'
              title:
                $ref: '#/components/schemas/i18n-text.schema'
              revision:
                $ref: '#/components/schemas/revision'
            additionalProperties: false
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    preset:
      description: 'Installed L6 preset: an ananta/1.0 programme, pattern or session document (02c, RC2).'
      type: object
      required:
        - id
        - kind
        - revision
        - document
      properties:
        id:
          $ref: '#/components/schemas/docId'
        kind:
          $ref: '#/components/schemas/presetKind'
        title:
          $ref: '#/components/schemas/i18n-text.schema'
        revision:
          $ref: '#/components/schemas/revision'
        document:
          type: object
        sha256:
          $ref: '#/components/schemas/sha256'
        updatedAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
      allOf:
        - if:
            properties:
              kind:
                const: programme
            required:
              - kind
          then:
            properties:
              document:
                $ref: '#/components/schemas/programme.schema'
        - if:
            properties:
              kind:
                const: pattern
            required:
              - kind
          then:
            properties:
              document:
                $ref: '#/components/schemas/pattern.schema'
        - if:
            properties:
              kind:
                const: session
            required:
              - kind
          then:
            properties:
              document:
                $ref: '#/components/schemas/session.schema'
    presetInput:
      description: PUT /venues/{venueId}/audio/presets/{presetId} body.
      type: object
      required:
        - kind
        - document
      properties:
        kind:
          $ref: '#/components/schemas/presetKind'
        document:
          type: object
      additionalProperties: false
      allOf:
        - if:
            properties:
              kind:
                const: programme
            required:
              - kind
          then:
            properties:
              document:
                $ref: '#/components/schemas/programme.schema'
        - if:
            properties:
              kind:
                const: pattern
            required:
              - kind
          then:
            properties:
              document:
                $ref: '#/components/schemas/pattern.schema'
        - if:
            properties:
              kind:
                const: session
            required:
              - kind
          then:
            properties:
              document:
                $ref: '#/components/schemas/session.schema'
    sessionPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/session'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    sessionStart:
      description: 'POST /venues/{venueId}/audio/sessions: run an installed preset (programme or session).'
      type: object
      required:
        - presetId
      properties:
        presetId:
          $ref: '#/components/schemas/docId'
        gainDb:
          type: number
          minimum: -96
          maximum: 12
        targets:
          $ref: '#/components/schemas/targets'
        at:
          $ref: '#/components/schemas/timestamp'
        loop:
          type: boolean
      additionalProperties: false
    generatorState:
      type: object
      required:
        - running
      properties:
        running:
          type: boolean
        settings:
          $ref: '#/components/schemas/generator'
        startedAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    sourcePosition:
      description: Interactive / realtime source position (spherical, listener at the dome centre).
      type: object
      required:
        - azimuth
        - elevation
      properties:
        azimuth:
          type: number
          minimum: -360
          maximum: 360
          description: degrees, 0 = front, positive = left (ambisonic convention)
        elevation:
          type: number
          minimum: -90
          maximum: 90
        distance:
          type: number
          exclusiveMinimum: 0
          description: metres
        rampMs:
          type: number
          minimum: 0
          maximum: 10000
      additionalProperties: false
    status-4:
      description: Service status. Open list.
      anyOf:
        - enum:
            - disabled
            - starting
            - running
            - degraded
            - down
        - type: string
          pattern: ^[a-z][a-z_-]{0,31}$
    input-6:
      description: One analysed input (a vocal microphone, an instrument group, a mix).
      type: object
      required:
        - id
      properties:
        id:
          $ref: '#/components/schemas/docId'
          description: name used by scenes, e.g. vocal, drums, mix
        label:
          type: string
          maxLength: 128
        device:
          $ref: '#/components/schemas/docId'
          description: audio device id (GET …/audio/devices); default = the engine's default capture device
        channels:
          type: array
          items:
            type: integer
            minimum: 1
            maximum: 1024
          minItems: 1
          maxItems: 64
          uniqueItems: true
          description: 1-based capture channels, summed to mono; default [1]
        gainDb:
          $ref: '#/components/schemas/gainDb-2'
        gateDb:
          type: number
          minimum: -144
          maximum: 0
          description: noise gate threshold, dBFS
        envelope:
          type: object
          properties:
            attackMs:
              type: number
              minimum: 0
              maximum: 10000
              default: 8
            decayMs:
              type: number
              minimum: 0
              maximum: 10000
              default: 120
          additionalProperties: false
        onset:
          type: object
          properties:
            threshold:
              $ref: '#/components/schemas/unitInterval'
              description: 'detector sensitivity: 0 = most sensitive'
            refractoryMs:
              type: number
              minimum: 0
              maximum: 10000
              description: minimum time between two onsets
          additionalProperties: false
        centroidRange:
          description: Frequencies that map to centroid 0 and 1.
          type: object
          required:
            - minHz
            - maxHz
          properties:
            minHz:
              $ref: '#/components/schemas/hertz'
            maxHz:
              $ref: '#/components/schemas/hertz'
          additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
    analysisState:
      description: GET /venues/{venueId}/audio/analysis, event audioAnalysis.state.
      type: object
      required:
        - venueId
        - enabled
        - status
        - inputs
      properties:
        venueId:
          $ref: '#/components/schemas/venueRef'
        enabled:
          type: boolean
        status:
          $ref: '#/components/schemas/status-4'
        problem:
          $ref: '#/components/schemas/problem.schema'
        engine:
          type: string
          description: engine that runs the analysis, `<kind>@<node>`
        sampleRate:
          type: integer
          minimum: 1
        blockSize:
          type: integer
          minimum: 1
        inputs:
          type: array
          items:
            $ref: '#/components/schemas/input-6'
          maxItems: 64
      patternProperties:
        ^x-: true
      additionalProperties: false
    analysisSettings:
      description: 'PUT /venues/{venueId}/audio/analysis body: replaces the whole list of inputs.'
      type: object
      required:
        - enabled
        - inputs
      properties:
        enabled:
          type: boolean
        inputs:
          type: array
          items:
            $ref: '#/components/schemas/input-6'
          maxItems: 64
      additionalProperties: false
    bands:
      description: 'Band energies, each 0..1. Fixed ranges: subBass 30–150 Hz, mid 150–800 Hz, highMid 800–4000 Hz, treble 4000–16000 Hz.'
      type: object
      required:
        - subBass
        - mid
        - highMid
        - treble
      properties:
        subBass:
          $ref: '#/components/schemas/unitInterval'
        mid:
          $ref: '#/components/schemas/unitInterval'
        highMid:
          $ref: '#/components/schemas/unitInterval'
        treble:
          $ref: '#/components/schemas/unitInterval'
      additionalProperties: false
    metric:
      description: 'A value of the analysis frame. rms, peak: envelope; subBass, mid, highMid, treble: bands; centroid: spectral brightness; onset: attack strength. Open list.'
      anyOf:
        - enum:
            - rms
            - peak
            - subBass
            - mid
            - highMid
            - treble
            - centroid
            - onset
        - type: string
          pattern: ^[a-z][A-Za-z0-9]{0,31}$
    range:
      description: Linear map before the curve. inMax > inMin (422 otherwise); outMin > outMax inverts. Defaults 0, 1, 0, 1.
      type: object
      properties:
        inMin:
          type: number
        inMax:
          type: number
        outMin:
          type: number
        outMax:
          type: number
      additionalProperties: false
    smoothing:
      description: Asymmetric smoothing of the mapped value, applied by the media server.
      type: object
      properties:
        attackMs:
          type: number
          minimum: 0
          maximum: 10000
        decayMs:
          type: number
          minimum: 0
          maximum: 10000
      additionalProperties: false
    target:
      description: 'Where a mapping goes. kind osc: an OSC address of an outbound gateway. Open list: a server that does not know a kind answers 422.'
      type: object
      required:
        - kind
      properties:
        kind:
          anyOf:
            - enum:
                - osc
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        gateway:
          $ref: '#/components/schemas/docId'
          description: gateway id, e.g. resolume
        address:
          type: string
          pattern: ^/[^\s#*,?\[\]{}]*$
          description: concrete address, e.g. /composition/layers/1/video/opacity
        type:
          enum:
            - f
            - i
          description: OSC argument type
        value:
          type: number
          description: 'trigger mode: the argument to send; default 1'
      if:
        properties:
          kind:
            const: osc
        required:
          - kind
      then:
        required:
          - gateway
          - address
          - type
      patternProperties:
        ^x-: true
      additionalProperties: false
    textOrI18n:
      description: Plain string (treated as `en`) or a localised map. (map branch = the root schema, inlined for tools that mis-handle self references)
      anyOf:
        - type: string
        - type: object
          minProperties: 1
          propertyNames:
            pattern: ^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$
          additionalProperties:
            type: string
          examples:
            - en: Intro
              ru: Вступление
    mapping-2:
      description: 'One metric → one target. continuous: the mapped value is sent on every frame it changes. trigger: one message when the metric rises through `threshold`, then nothing for `cooldownMs`.'
      type: object
      required:
        - id
        - mode
        - source
        - target
      properties:
        id:
          $ref: '#/components/schemas/docId'
        mode:
          enum:
            - continuous
            - trigger
        source:
          type: object
          required:
            - input
            - metric
          properties:
            input:
              $ref: '#/components/schemas/docId'
              description: input id of the analysis settings
            metric:
              $ref: '#/components/schemas/metric'
          additionalProperties: false
        range:
          $ref: '#/components/schemas/range'
        curve:
          anyOf:
            - enum:
                - linear
                - exp
                - log
                - s-curve
            - type: string
              pattern: ^[a-z][a-z0-9_-]{0,31}$
          default: linear
          description: 'for t in 0..1: linear t; exp t²; log log10(1 + 9t); s-curve 3t² − 2t³'
        smoothing:
          $ref: '#/components/schemas/smoothing'
        threshold:
          $ref: '#/components/schemas/unitInterval'
        cooldownMs:
          type: number
          minimum: 0
          maximum: 60000
        target:
          $ref: '#/components/schemas/target'
      if:
        properties:
          mode:
            const: trigger
        required:
          - mode
      then:
        required:
          - threshold
      patternProperties:
        ^x-: true
      additionalProperties: false
    sceneBody:
      type: object
      properties:
        title:
          $ref: '#/components/schemas/textOrI18n'
        description:
          $ref: '#/components/schemas/textOrI18n'
        mappings:
          type: array
          items:
            $ref: '#/components/schemas/mapping-2'
          maxItems: 256
      patternProperties:
        ^x-: true
    scene:
      description: 'A scene is data: which metrics drive which targets. Mapping ids are unique within the scene.'
      type: object
      allOf:
        - $ref: '#/components/schemas/sceneBody'
      required:
        - id
        - title
        - revision
        - mappings
      properties:
        id:
          $ref: '#/components/schemas/docId'
        revision:
          $ref: '#/components/schemas/revision'
      unevaluatedProperties: false
    resolumeLink:
      type: object
      required:
        - state
      properties:
        state:
          anyOf:
            - enum:
                - disabled
                - connecting
                - up
                - down
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
          description: 'for OSC over UDP, up means sending: there is no acknowledgement'
        lastOkAt:
          $ref: '#/components/schemas/timestamp'
        sent:
          type: integer
          minimum: 0
        dropped:
          type: integer
          minimum: 0
        problem:
          $ref: '#/components/schemas/problem.schema'
      additionalProperties: false
    resolumeStatus:
      type: object
      required:
        - osc
        - rest
        - ws
      properties:
        osc:
          $ref: '#/components/schemas/resolumeLink'
        rest:
          $ref: '#/components/schemas/resolumeLink'
        ws:
          $ref: '#/components/schemas/resolumeLink'
        product:
          description: GET /api/v1/product of Resolume, when reachable.
          type: object
          properties:
            name:
              type: string
            major:
              type: integer
              minimum: 0
            minor:
              type: integer
              minimum: 0
            micro:
              type: integer
              minimum: 0
            revision:
              type: integer
              minimum: 0
          additionalProperties: false
        composition:
          description: Summary of the open composition.
          type: object
          properties:
            name:
              type: string
            layers:
              type: integer
              minimum: 0
            columns:
              type: integer
              minimum: 0
            decks:
              type: integer
              minimum: 0
          additionalProperties: false
        lastSyncAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    state-2:
      description: GET /venues/{venueId}/audio/reactive, event audioReactive.state.
      type: object
      required:
        - venueId
        - enabled
        - status
        - activeSceneId
        - intensity
      properties:
        venueId:
          $ref: '#/components/schemas/venueRef'
        enabled:
          type: boolean
        status:
          $ref: '#/components/schemas/status-4'
        problem:
          $ref: '#/components/schemas/problem.schema'
        activeSceneId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        intensity:
          $ref: '#/components/schemas/unitInterval'
          description: master amount of all continuous mappings
        stats:
          type: object
          additionalProperties:
            type: number
          description: counters, e.g. framesIn, messagesOut, framesDropped
        resolume:
          $ref: '#/components/schemas/resolumeStatus'
          description: connection status of gateway `resolume`, the same object as its gateway member, so that playback:read can see it without gateways:manage; absent when no such gateway is configured. Read-only.
      patternProperties:
        ^x-: true
      additionalProperties: false
    audio-reactive.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/audio-reactive.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). Live audio analysis (produced by anAntaDigital, L1) and AudioReactive scenes (executed by the media server, L3): sdd-360-art-center 02-specifications §0, docs/audio-reactive.md. Root = GET /venues/{venueId}/audio/reactive.'
      title: AudioReactive state
      allOf:
        - $ref: '#/components/schemas/state-2'
      $defs:
        status:
          description: Service status. Open list.
          anyOf:
            - enum:
                - disabled
                - starting
                - running
                - degraded
                - down
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        metric:
          description: 'A value of the analysis frame. rms, peak: envelope; subBass, mid, highMid, treble: bands; centroid: spectral brightness; onset: attack strength. Open list.'
          anyOf:
            - enum:
                - rms
                - peak
                - subBass
                - mid
                - highMid
                - treble
                - centroid
                - onset
            - type: string
              pattern: ^[a-z][A-Za-z0-9]{0,31}$
        bands:
          description: 'Band energies, each 0..1. Fixed ranges: subBass 30–150 Hz, mid 150–800 Hz, highMid 800–4000 Hz, treble 4000–16000 Hz.'
          type: object
          required:
            - subBass
            - mid
            - highMid
            - treble
          properties:
            subBass:
              $ref: '#/components/schemas/unitInterval'
            mid:
              $ref: '#/components/schemas/unitInterval'
            highMid:
              $ref: '#/components/schemas/unitInterval'
            treble:
              $ref: '#/components/schemas/unitInterval'
          additionalProperties: false
        frame:
          description: One analysis frame of one input (event audio.analysis). rms, peak and bands are linear amplitude relative to full scale, clamped to 0..1, after the input's envelope (a voice at -24 dBFS reads 0.06). Scene smoothing is applied after this.
          type: object
          required:
            - inputId
            - frame
            - captureUs
            - rms
            - peak
            - bands
            - centroid
            - onset
          properties:
            inputId:
              $ref: '#/components/schemas/docId'
            frame:
              type: integer
              minimum: 0
              description: frame counter of this input since the analysis started
            captureUs:
              type: integer
              minimum: 0
              description: start of the audio block on the analysis node's monotonic clock, µs; comparable only on that node
            rms:
              $ref: '#/components/schemas/unitInterval'
            peak:
              $ref: '#/components/schemas/unitInterval'
            rmsDb:
              type: number
              description: dBFS, before normalisation
            peakDb:
              type: number
              description: dBFS, before normalisation
            bands:
              $ref: '#/components/schemas/bands'
            centroid:
              $ref: '#/components/schemas/unitInterval'
              description: centroidHz on a log scale over the input's centroidRange
            centroidHz:
              $ref: '#/components/schemas/hertz'
            onset:
              $ref: '#/components/schemas/unitInterval'
              description: attack strength of this frame; 0 = no attack
            gated:
              type: boolean
              description: 'the input is below its gate: normalised values are 0'
          patternProperties:
            ^x-: true
          additionalProperties: false
        onset:
          description: One detected attack (event audio.onset).
          type: object
          required:
            - inputId
            - frame
            - captureUs
            - strength
          properties:
            inputId:
              $ref: '#/components/schemas/docId'
            frame:
              type: integer
              minimum: 0
            captureUs:
              type: integer
              minimum: 0
            strength:
              $ref: '#/components/schemas/unitInterval'
            band:
              enum:
                - subBass
                - mid
                - highMid
                - treble
              description: band with the largest energy rise, when known
          patternProperties:
            ^x-: true
          additionalProperties: false
        input:
          description: One analysed input (a vocal microphone, an instrument group, a mix).
          type: object
          required:
            - id
          properties:
            id:
              $ref: '#/components/schemas/docId'
              description: name used by scenes, e.g. vocal, drums, mix
            label:
              type: string
              maxLength: 128
            device:
              $ref: '#/components/schemas/docId'
              description: audio device id (GET …/audio/devices); default = the engine's default capture device
            channels:
              type: array
              items:
                type: integer
                minimum: 1
                maximum: 1024
              minItems: 1
              maxItems: 64
              uniqueItems: true
              description: 1-based capture channels, summed to mono; default [1]
            gainDb:
              $ref: '#/components/schemas/gainDb-2'
            gateDb:
              type: number
              minimum: -144
              maximum: 0
              description: noise gate threshold, dBFS
            envelope:
              type: object
              properties:
                attackMs:
                  type: number
                  minimum: 0
                  maximum: 10000
                  default: 8
                decayMs:
                  type: number
                  minimum: 0
                  maximum: 10000
                  default: 120
              additionalProperties: false
            onset:
              type: object
              properties:
                threshold:
                  $ref: '#/components/schemas/unitInterval'
                  description: 'detector sensitivity: 0 = most sensitive'
                refractoryMs:
                  type: number
                  minimum: 0
                  maximum: 10000
                  description: minimum time between two onsets
              additionalProperties: false
            centroidRange:
              description: Frequencies that map to centroid 0 and 1.
              type: object
              required:
                - minHz
                - maxHz
              properties:
                minHz:
                  $ref: '#/components/schemas/hertz'
                maxHz:
                  $ref: '#/components/schemas/hertz'
              additionalProperties: false
          patternProperties:
            ^x-: true
          additionalProperties: false
        analysisSettings:
          description: 'PUT /venues/{venueId}/audio/analysis body: replaces the whole list of inputs.'
          type: object
          required:
            - enabled
            - inputs
          properties:
            enabled:
              type: boolean
            inputs:
              type: array
              items:
                $ref: '#/components/schemas/input-6'
              maxItems: 64
          additionalProperties: false
        analysisState:
          description: GET /venues/{venueId}/audio/analysis, event audioAnalysis.state.
          type: object
          required:
            - venueId
            - enabled
            - status
            - inputs
          properties:
            venueId:
              $ref: '#/components/schemas/venueRef'
            enabled:
              type: boolean
            status:
              $ref: '#/components/schemas/status-4'
            problem:
              $ref: '#/components/schemas/problem.schema'
            engine:
              type: string
              description: engine that runs the analysis, `<kind>@<node>`
            sampleRate:
              type: integer
              minimum: 1
            blockSize:
              type: integer
              minimum: 1
            inputs:
              type: array
              items:
                $ref: '#/components/schemas/input-6'
              maxItems: 64
          patternProperties:
            ^x-: true
          additionalProperties: false
        smoothing:
          description: Asymmetric smoothing of the mapped value, applied by the media server.
          type: object
          properties:
            attackMs:
              type: number
              minimum: 0
              maximum: 10000
            decayMs:
              type: number
              minimum: 0
              maximum: 10000
          additionalProperties: false
        range:
          description: Linear map before the curve. inMax > inMin (422 otherwise); outMin > outMax inverts. Defaults 0, 1, 0, 1.
          type: object
          properties:
            inMin:
              type: number
            inMax:
              type: number
            outMin:
              type: number
            outMax:
              type: number
          additionalProperties: false
        target:
          description: 'Where a mapping goes. kind osc: an OSC address of an outbound gateway. Open list: a server that does not know a kind answers 422.'
          type: object
          required:
            - kind
          properties:
            kind:
              anyOf:
                - enum:
                    - osc
                - type: string
                  pattern: ^[a-z][a-z_-]{0,31}$
            gateway:
              $ref: '#/components/schemas/docId'
              description: gateway id, e.g. resolume
            address:
              type: string
              pattern: ^/[^\s#*,?\[\]{}]*$
              description: concrete address, e.g. /composition/layers/1/video/opacity
            type:
              enum:
                - f
                - i
              description: OSC argument type
            value:
              type: number
              description: 'trigger mode: the argument to send; default 1'
          if:
            properties:
              kind:
                const: osc
            required:
              - kind
          then:
            required:
              - gateway
              - address
              - type
          patternProperties:
            ^x-: true
          additionalProperties: false
        mapping:
          description: 'One metric → one target. continuous: the mapped value is sent on every frame it changes. trigger: one message when the metric rises through `threshold`, then nothing for `cooldownMs`.'
          type: object
          required:
            - id
            - mode
            - source
            - target
          properties:
            id:
              $ref: '#/components/schemas/docId'
            mode:
              enum:
                - continuous
                - trigger
            source:
              type: object
              required:
                - input
                - metric
              properties:
                input:
                  $ref: '#/components/schemas/docId'
                  description: input id of the analysis settings
                metric:
                  $ref: '#/components/schemas/metric'
              additionalProperties: false
            range:
              $ref: '#/components/schemas/range'
            curve:
              anyOf:
                - enum:
                    - linear
                    - exp
                    - log
                    - s-curve
                - type: string
                  pattern: ^[a-z][a-z0-9_-]{0,31}$
              default: linear
              description: 'for t in 0..1: linear t; exp t²; log log10(1 + 9t); s-curve 3t² − 2t³'
            smoothing:
              $ref: '#/components/schemas/smoothing'
            threshold:
              $ref: '#/components/schemas/unitInterval'
            cooldownMs:
              type: number
              minimum: 0
              maximum: 60000
            target:
              $ref: '#/components/schemas/target'
          if:
            properties:
              mode:
                const: trigger
            required:
              - mode
          then:
            required:
              - threshold
          patternProperties:
            ^x-: true
          additionalProperties: false
        sceneBody:
          type: object
          properties:
            title:
              $ref: '#/components/schemas/textOrI18n'
            description:
              $ref: '#/components/schemas/textOrI18n'
            mappings:
              type: array
              items:
                $ref: '#/components/schemas/mapping-2'
              maxItems: 256
          patternProperties:
            ^x-: true
        scene:
          description: 'A scene is data: which metrics drive which targets. Mapping ids are unique within the scene.'
          type: object
          allOf:
            - $ref: '#/components/schemas/sceneBody'
          required:
            - id
            - title
            - revision
            - mappings
          properties:
            id:
              $ref: '#/components/schemas/docId'
            revision:
              $ref: '#/components/schemas/revision'
          unevaluatedProperties: false
        sceneInput:
          description: PUT …/audio/reactive/scenes/{sceneId} body.
          type: object
          allOf:
            - $ref: '#/components/schemas/sceneBody'
          required:
            - title
            - mappings
          unevaluatedProperties: false
        scenePage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/scene'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        state:
          description: GET /venues/{venueId}/audio/reactive, event audioReactive.state.
          type: object
          required:
            - venueId
            - enabled
            - status
            - activeSceneId
            - intensity
          properties:
            venueId:
              $ref: '#/components/schemas/venueRef'
            enabled:
              type: boolean
            status:
              $ref: '#/components/schemas/status-4'
            problem:
              $ref: '#/components/schemas/problem.schema'
            activeSceneId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            intensity:
              $ref: '#/components/schemas/unitInterval'
              description: master amount of all continuous mappings
            stats:
              type: object
              additionalProperties:
                type: number
              description: counters, e.g. framesIn, messagesOut, framesDropped
            resolume:
              $ref: '#/components/schemas/resolumeStatus'
              description: connection status of gateway `resolume`, the same object as its gateway member, so that playback:read can see it without gateways:manage; absent when no such gateway is configured. Read-only.
          patternProperties:
            ^x-: true
          additionalProperties: false
        patch:
          description: PATCH /venues/{venueId}/audio/reactive. activeSceneId null = no scene. An unknown scene, or a scene whose gateway or input does not exist, is refused with 422.
          type: object
          minProperties: 1
          properties:
            enabled:
              type: boolean
            activeSceneId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            intensity:
              $ref: '#/components/schemas/unitInterval'
          additionalProperties: false
    patch-9:
      description: PATCH /venues/{venueId}/audio/reactive. activeSceneId null = no scene. An unknown scene, or a scene whose gateway or input does not exist, is refused with 422.
      type: object
      minProperties: 1
      properties:
        enabled:
          type: boolean
        activeSceneId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        intensity:
          $ref: '#/components/schemas/unitInterval'
      additionalProperties: false
    scenePage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/scene'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    sceneInput:
      description: PUT …/audio/reactive/scenes/{sceneId} body.
      type: object
      allOf:
        - $ref: '#/components/schemas/sceneBody'
      required:
        - title
        - mappings
      unevaluatedProperties: false
    textureSourcePage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/textureSource'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    engineName:
      description: 'Engine name: kind (visual | anantasound | anantadigital | …) optionally qualified by the render node, `<kind>@<node>` (multi-node installations, 02d ClusterService).'
      type: string
      pattern: ^[a-z][a-z0-9_-]{0,31}(@[A-Za-z0-9._-]{1,64})?$
      examples:
        - visual
        - visual@node-1
    engine:
      type: object
      required:
        - name
        - status
      properties:
        name:
          anyOf:
            - enum:
                - visual
                - anantasound
                - anantadigital
            - $ref: '#/components/schemas/engineName'
        status:
          anyOf:
            - enum:
                - up
                - degraded
                - down
                - incompatible
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        version:
          type: string
        protocol:
          type: string
          pattern: ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*\.v[0-9]+$
          description: gRPC package, e.g. freedome.engine.v1
        endpoint:
          type: string
          description: unix:/run/freedome/engine.sock, host:port
        nodes:
          type: array
          items:
            $ref: '#/components/schemas/docId'
        backend:
          anyOf:
            - enum:
                - vulkan
                - metal
                - d3d12
                - opengl
                - software
            - type: string
          description: 'visual: active compute / render backend'
        backends:
          type: array
          items:
            type: string
          description: 'visual: available backends'
        capabilities:
          type: object
          additionalProperties: true
          description: engine-reported capabilities (outputs, codecs, max resolution, audio channels …); keys listed in docs/capabilities.yaml
        metrics:
          type: object
          additionalProperties:
            type: number
          description: fps, droppedFrames, gpuLoad, latencyMs …
        problem:
          $ref: '#/components/schemas/problem.schema'
          description: why degraded / down / incompatible
        since:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    page-9:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/engine'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    engine.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/engine.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). gRPC engine status mirror (02e §4, §5.6): visual (freedome.engine.v1), anantasound (ananta.sound.v1), anantadigital (ananta.digital.v1). Root = GET /engines/{name}.'
      title: Engine
      allOf:
        - $ref: '#/components/schemas/engine'
      $defs:
        engine:
          type: object
          required:
            - name
            - status
          properties:
            name:
              anyOf:
                - enum:
                    - visual
                    - anantasound
                    - anantadigital
                - $ref: '#/components/schemas/engineName'
            status:
              anyOf:
                - enum:
                    - up
                    - degraded
                    - down
                    - incompatible
                - type: string
                  pattern: ^[a-z][a-z_-]{0,31}$
            version:
              type: string
            protocol:
              type: string
              pattern: ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*\.v[0-9]+$
              description: gRPC package, e.g. freedome.engine.v1
            endpoint:
              type: string
              description: unix:/run/freedome/engine.sock, host:port
            nodes:
              type: array
              items:
                $ref: '#/components/schemas/docId'
            backend:
              anyOf:
                - enum:
                    - vulkan
                    - metal
                    - d3d12
                    - opengl
                    - software
                - type: string
              description: 'visual: active compute / render backend'
            backends:
              type: array
              items:
                type: string
              description: 'visual: available backends'
            capabilities:
              type: object
              additionalProperties: true
              description: engine-reported capabilities (outputs, codecs, max resolution, audio channels …); keys listed in docs/capabilities.yaml
            metrics:
              type: object
              additionalProperties:
                type: number
              description: fps, droppedFrames, gpuLoad, latencyMs …
            problem:
              $ref: '#/components/schemas/problem.schema'
              description: why degraded / down / incompatible
            since:
              $ref: '#/components/schemas/timestamp'
          patternProperties:
            ^x-: true
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/engine'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        backendRequest:
          type: object
          required:
            - backend
          properties:
            backend:
              anyOf:
                - enum:
                    - vulkan
                    - metal
                    - d3d12
                    - opengl
                    - software
                - type: string
                  pattern: ^[a-z0-9_-]{1,32}$
          additionalProperties: false
    backendRequest:
      type: object
      required:
        - backend
      properties:
        backend:
          anyOf:
            - enum:
                - vulkan
                - metal
                - d3d12
                - opengl
                - software
            - type: string
              pattern: ^[a-z0-9_-]{1,32}$
      additionalProperties: false
    resolumeConfig:
      description: '`config` of the outbound Resolume Arena / Avenue driver (protocol resolume). Defaults are Resolume''s own ports; they equal the media server''s defaults, so on one machine move the media server (docs/audio-reactive.md).'
      type: object
      required:
        - host
      properties:
        host:
          type: string
          minLength: 1
          maxLength: 253
          description: host name or IP of the Resolume machine
        oscPort:
          type: integer
          minimum: 1
          maximum: 65535
          default: 7000
        restPort:
          type: integer
          minimum: 1
          maximum: 65535
          default: 8080
        ws:
          type: boolean
          default: true
          description: keep the WebSocket API connection (ws://host:restPort/api/v1) for state
        oscMaxRateHz:
          type: number
          exclusiveMinimum: 0
          maximum: 1000
          description: highest send rate per OSC address; newer values replace unsent ones
      patternProperties:
        ^x-: true
      additionalProperties: false
    gateway:
      type: object
      required:
        - id
        - direction
        - protocol
        - enabled
        - status
      properties:
        id:
          $ref: '#/components/schemas/docId'
        name:
          type: string
        direction:
          enum:
            - inbound
            - outbound
            - converter
        protocol:
          type: string
          description: e.g. screenberry-json, dome360-http, watchout, resolume, osc, pjlink, artnet
        vendor:
          type: string
        enabled:
          type: boolean
        status:
          anyOf:
            - enum:
                - disabled
                - starting
                - up
                - degraded
                - down
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        docsOnly:
          type: boolean
          description: implemented from public docs, no recorded fixtures from a real system yet
        listeners:
          type: array
          items:
            type: object
            required:
              - transport
              - port
            properties:
              transport:
                enum:
                  - http
                  - tcp
                  - udp
                  - osc
                  - ws
                  - socketio
                  - serial
              port:
                type: integer
                minimum: 0
                maximum: 65535
              bind:
                type: string
            additionalProperties: false
        peers:
          type: array
          items:
            type: object
            properties:
              address:
                type: string
              lastSeen:
                $ref: '#/components/schemas/timestamp'
            additionalProperties: false
        config:
          type: object
          additionalProperties: true
          description: gateway-specific; secrets redacted as "***"
        allowCidrs:
          type: array
          items:
            type: string
        problem:
          $ref: '#/components/schemas/problem.schema'
        stats:
          type: object
          additionalProperties:
            type: number
        resolume:
          $ref: '#/components/schemas/resolumeStatus'
          description: live driver status; only on protocol resolume, read-only
      if:
        properties:
          protocol:
            const: resolume
        required:
          - protocol
      then:
        properties:
          config:
            $ref: '#/components/schemas/resolumeConfig'
      patternProperties:
        ^x-: true
      additionalProperties: false
    page-10:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/gateway'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    oscArg:
      type: object
      required:
        - type
        - value
      properties:
        type:
          enum:
            - i
            - f
            - s
            - b
            - T
            - F
            - h
            - d
            - t
            - 'N'
        value:
          anyOf:
            - type: number
            - type: string
            - type: boolean
            - type: 'null'
          description: b = base64 blob; t = NTP time tag as RFC 3339
      additionalProperties: false
    gateway.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/gateway.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Compatibility gateways (inbound emulators, outbound drivers, 02d) and the OSC send integration (02e §5.6). Root = GET /gateways/{gatewayId}.
      title: Gateway
      allOf:
        - $ref: '#/components/schemas/gateway'
      $defs:
        gateway:
          type: object
          required:
            - id
            - direction
            - protocol
            - enabled
            - status
          properties:
            id:
              $ref: '#/components/schemas/docId'
            name:
              type: string
            direction:
              enum:
                - inbound
                - outbound
                - converter
            protocol:
              type: string
              description: e.g. screenberry-json, dome360-http, watchout, resolume, osc, pjlink, artnet
            vendor:
              type: string
            enabled:
              type: boolean
            status:
              anyOf:
                - enum:
                    - disabled
                    - starting
                    - up
                    - degraded
                    - down
                - type: string
                  pattern: ^[a-z][a-z_-]{0,31}$
            docsOnly:
              type: boolean
              description: implemented from public docs, no recorded fixtures from a real system yet
            listeners:
              type: array
              items:
                type: object
                required:
                  - transport
                  - port
                properties:
                  transport:
                    enum:
                      - http
                      - tcp
                      - udp
                      - osc
                      - ws
                      - socketio
                      - serial
                  port:
                    type: integer
                    minimum: 0
                    maximum: 65535
                  bind:
                    type: string
                additionalProperties: false
            peers:
              type: array
              items:
                type: object
                properties:
                  address:
                    type: string
                  lastSeen:
                    $ref: '#/components/schemas/timestamp'
                additionalProperties: false
            config:
              type: object
              additionalProperties: true
              description: gateway-specific; secrets redacted as "***"
            allowCidrs:
              type: array
              items:
                type: string
            problem:
              $ref: '#/components/schemas/problem.schema'
            stats:
              type: object
              additionalProperties:
                type: number
            resolume:
              $ref: '#/components/schemas/resolumeStatus'
              description: live driver status; only on protocol resolume, read-only
          if:
            properties:
              protocol:
                const: resolume
            required:
              - protocol
          then:
            properties:
              config:
                $ref: '#/components/schemas/resolumeConfig'
          patternProperties:
            ^x-: true
          additionalProperties: false
        resolumeConfig:
          description: '`config` of the outbound Resolume Arena / Avenue driver (protocol resolume). Defaults are Resolume''s own ports; they equal the media server''s defaults, so on one machine move the media server (docs/audio-reactive.md).'
          type: object
          required:
            - host
          properties:
            host:
              type: string
              minLength: 1
              maxLength: 253
              description: host name or IP of the Resolume machine
            oscPort:
              type: integer
              minimum: 1
              maximum: 65535
              default: 7000
            restPort:
              type: integer
              minimum: 1
              maximum: 65535
              default: 8080
            ws:
              type: boolean
              default: true
              description: keep the WebSocket API connection (ws://host:restPort/api/v1) for state
            oscMaxRateHz:
              type: number
              exclusiveMinimum: 0
              maximum: 1000
              description: highest send rate per OSC address; newer values replace unsent ones
          patternProperties:
            ^x-: true
          additionalProperties: false
        resolumeLink:
          type: object
          required:
            - state
          properties:
            state:
              anyOf:
                - enum:
                    - disabled
                    - connecting
                    - up
                    - down
                - type: string
                  pattern: ^[a-z][a-z_-]{0,31}$
              description: 'for OSC over UDP, up means sending: there is no acknowledgement'
            lastOkAt:
              $ref: '#/components/schemas/timestamp'
            sent:
              type: integer
              minimum: 0
            dropped:
              type: integer
              minimum: 0
            problem:
              $ref: '#/components/schemas/problem.schema'
          additionalProperties: false
        resolumeStatus:
          type: object
          required:
            - osc
            - rest
            - ws
          properties:
            osc:
              $ref: '#/components/schemas/resolumeLink'
            rest:
              $ref: '#/components/schemas/resolumeLink'
            ws:
              $ref: '#/components/schemas/resolumeLink'
            product:
              description: GET /api/v1/product of Resolume, when reachable.
              type: object
              properties:
                name:
                  type: string
                major:
                  type: integer
                  minimum: 0
                minor:
                  type: integer
                  minimum: 0
                micro:
                  type: integer
                  minimum: 0
                revision:
                  type: integer
                  minimum: 0
              additionalProperties: false
            composition:
              description: Summary of the open composition.
              type: object
              properties:
                name:
                  type: string
                layers:
                  type: integer
                  minimum: 0
                columns:
                  type: integer
                  minimum: 0
                decks:
                  type: integer
                  minimum: 0
              additionalProperties: false
            lastSyncAt:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
        patch:
          type: object
          minProperties: 1
          properties:
            enabled:
              type: boolean
            config:
              type: object
              additionalProperties: true
            allowCidrs:
              type: array
              items:
                type: string
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/gateway'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        testResult:
          type: object
          required:
            - gatewayId
            - ok
          properties:
            gatewayId:
              $ref: '#/components/schemas/docId'
            ok:
              type: boolean
            latencyMs:
              type: number
              minimum: 0
            remoteVersion:
              type: string
            detail:
              type: string
            problem:
              $ref: '#/components/schemas/problem.schema'
          additionalProperties: false
        oscArg:
          type: object
          required:
            - type
            - value
          properties:
            type:
              enum:
                - i
                - f
                - s
                - b
                - T
                - F
                - h
                - d
                - t
                - 'N'
            value:
              anyOf:
                - type: number
                - type: string
                - type: boolean
                - type: 'null'
              description: b = base64 blob; t = NTP time tag as RFC 3339
          additionalProperties: false
        sendOsc:
          description: 'POST /integrations/osc/send: to a configured OSC target only (never an arbitrary host).'
          type: object
          required:
            - target
            - address
          properties:
            target:
              $ref: '#/components/schemas/docId'
              description: configured target id (gateway or osc target)
            address:
              type: string
              pattern: ^/[^\s#*,?\[\]{}]*$
            args:
              type: array
              items:
                $ref: '#/components/schemas/oscArg'
              maxItems: 64
          additionalProperties: false
    patch-10:
      type: object
      minProperties: 1
      properties:
        enabled:
          type: boolean
        config:
          type: object
          additionalProperties: true
        allowCidrs:
          type: array
          items:
            type: string
      additionalProperties: false
    testResult:
      type: object
      required:
        - gatewayId
        - ok
      properties:
        gatewayId:
          $ref: '#/components/schemas/docId'
        ok:
          type: boolean
        latencyMs:
          type: number
          minimum: 0
        remoteVersion:
          type: string
        detail:
          type: string
        problem:
          $ref: '#/components/schemas/problem.schema'
      additionalProperties: false
    sendOsc:
      description: 'POST /integrations/osc/send: to a configured OSC target only (never an arbitrary host).'
      type: object
      required:
        - target
        - address
      properties:
        target:
          $ref: '#/components/schemas/docId'
          description: configured target id (gateway or osc target)
        address:
          type: string
          pattern: ^/[^\s#*,?\[\]{}]*$
        args:
          type: array
          items:
            $ref: '#/components/schemas/oscArg'
          maxItems: 64
      additionalProperties: false
    app-payload.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/app-payload.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Ecosystem-app content (02e §5.6 POST /apps/{app}/payloads) rendered by the app-content renderer (asset type realtime); replaces integration-event / integrateApp. Root = request body.
      title: App payload
      type: object
      required:
        - kind
        - data
      properties:
        kind:
          anyOf:
            - enum:
                - scene
                - overlay
                - state
            - type: string
              pattern: ^[a-z][a-z0-9_-]{0,31}$
        schema:
          type: string
          format: uri
          description: $id of the app's payload schema (app payload registry, 4.CT.4)
        data:
          description: app-defined JSON (≤ 1 MB)
        ttlSec:
          type: integer
          minimum: 1
          maximum: 86400
          default: 300
        target:
          type: object
          properties:
            venueId:
              $ref: '#/components/schemas/venueRef'
            playerId:
              $ref: '#/components/schemas/playerRef'
          additionalProperties: false
      patternProperties:
        ^x-: true
      additionalProperties: false
      $defs:
        published:
          description: Response of publishAppPayload and the `data.resource` of event app.payload.
          type: object
          required:
            - id
            - app
            - kind
            - expiresAt
          properties:
            id:
              $ref: '#/components/schemas/uuidv7'
            app:
              type: string
              pattern: ^[A-Za-z0-9._-]{1,128}$
            kind:
              type: string
            schema:
              type: string
            data: {}
            publishedBy:
              type: string
            expiresAt:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
    published:
      description: Response of publishAppPayload and the `data.resource` of event app.payload.
      type: object
      required:
        - id
        - app
        - kind
        - expiresAt
      properties:
        id:
          $ref: '#/components/schemas/uuidv7'
        app:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
        kind:
          type: string
        schema:
          type: string
        data: {}
        publishedBy:
          type: string
        expiresAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    capability:
      anyOf:
        - enum:
            - vibration
            - camera
            - biometric
            - sensors.accelerometer
            - sensors.gyroscope
            - sensors.magnetometer
            - sensors.heartRate
            - sensors.location
        - type: string
          pattern: ^[a-z][A-Za-z0-9.]{0,63}$
    topic:
      description: Concrete event topic (02e §7.2), e.g. venues/default/players/main.
      type: string
      maxLength: 512
      pattern: ^[A-Za-z0-9._@-]+(/[A-Za-z0-9._@-]+)*$
    client:
      type: object
      required:
        - id
        - app
        - deviceName
        - capabilities
        - registeredAt
      properties:
        id:
          $ref: '#/components/schemas/uuidv7'
        app:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
        appVersion:
          type: string
        deviceName:
          type: string
          maxLength: 128
        platform:
          type: string
        capabilities:
          type: array
          items:
            $ref: '#/components/schemas/capability'
          uniqueItems: true
        principal:
          type: string
        commandsTopic:
          $ref: '#/components/schemas/topic'
          description: clients/{clientId}/commands
        registeredAt:
          $ref: '#/components/schemas/timestamp'
        lastSeen:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    page-11:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/client'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    input-7:
      type: object
      required:
        - app
        - deviceName
      properties:
        app:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
        appVersion:
          type: string
          maxLength: 64
        deviceName:
          type: string
          minLength: 1
          maxLength: 128
        platform:
          type: string
          maxLength: 64
        capabilities:
          type: array
          items:
            $ref: '#/components/schemas/capability'
          uniqueItems: true
          default: []
      additionalProperties: false
    client.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/client.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). Connected client devices (02e §5.6 /clients: churyavzrosliy device fx — vibration, camera, sensors, biometric). Root = registered client.'
      title: Client
      allOf:
        - $ref: '#/components/schemas/client'
      $defs:
        capability:
          anyOf:
            - enum:
                - vibration
                - camera
                - biometric
                - sensors.accelerometer
                - sensors.gyroscope
                - sensors.magnetometer
                - sensors.heartRate
                - sensors.location
            - type: string
              pattern: ^[a-z][A-Za-z0-9.]{0,63}$
        client:
          type: object
          required:
            - id
            - app
            - deviceName
            - capabilities
            - registeredAt
          properties:
            id:
              $ref: '#/components/schemas/uuidv7'
            app:
              type: string
              pattern: ^[A-Za-z0-9._-]{1,128}$
            appVersion:
              type: string
            deviceName:
              type: string
              maxLength: 128
            platform:
              type: string
            capabilities:
              type: array
              items:
                $ref: '#/components/schemas/capability'
              uniqueItems: true
            principal:
              type: string
            commandsTopic:
              $ref: '#/components/schemas/topic'
              description: clients/{clientId}/commands
            registeredAt:
              $ref: '#/components/schemas/timestamp'
            lastSeen:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
        input:
          type: object
          required:
            - app
            - deviceName
          properties:
            app:
              type: string
              pattern: ^[A-Za-z0-9._-]{1,128}$
            appVersion:
              type: string
              maxLength: 64
            deviceName:
              type: string
              minLength: 1
              maxLength: 128
            platform:
              type: string
              maxLength: 64
            capabilities:
              type: array
              items:
                $ref: '#/components/schemas/capability'
              uniqueItems: true
              default: []
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/client'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        telemetry:
          description: 'POST /clients/{clientId}/telemetry: batched sensor samples (replaces sendSensorData).'
          type: object
          required:
            - samples
          properties:
            samples:
              type: array
              minItems: 1
              maxItems: 1000
              items:
                type: object
                required:
                  - ts
                  - sensor
                  - values
                properties:
                  ts:
                    $ref: '#/components/schemas/timestamp'
                  sensor:
                    $ref: '#/components/schemas/capability'
                  values:
                    type: array
                    items:
                      type: number
                    maxItems: 64
                  unit:
                    type: string
                additionalProperties: false
          additionalProperties: false
        command:
          description: Server → device command, delivered as event client.command on clients/{clientId}/commands.
          type: object
          required:
            - command
          properties:
            command:
              anyOf:
                - enum:
                    - vibrate
                    - capture
                    - sensors
                    - biometric
                - type: string
                  pattern: ^[a-z][A-Za-z0-9_.-]{0,63}$
            params:
              type: object
              additionalProperties: true
            expiresAt:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
    telemetry:
      description: 'POST /clients/{clientId}/telemetry: batched sensor samples (replaces sendSensorData).'
      type: object
      required:
        - samples
      properties:
        samples:
          type: array
          minItems: 1
          maxItems: 1000
          items:
            type: object
            required:
              - ts
              - sensor
              - values
            properties:
              ts:
                $ref: '#/components/schemas/timestamp'
              sensor:
                $ref: '#/components/schemas/capability'
              values:
                type: array
                items:
                  type: number
                maxItems: 64
              unit:
                type: string
            additionalProperties: false
      additionalProperties: false
    operationId:
      description: OpenAPI operationId (camelCase); also the `op` of WS / socket.io `call`.
      type: string
      pattern: ^[a-z][a-zA-Z0-9]*$
    eventData:
      type: object
      required:
        - seq
        - kind
      properties:
        seq:
          type: integer
          minimum: 0
          description: Per-server monotonically increasing sequence (resume with sinceSeq).
        kind:
          type: string
          pattern: ^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$
          description: Event kind from events/topics.yaml, e.g. player.state.
        resource:
          description: Full new representation.
        patch:
          type: object
          description: JSON merge patch (high-rate kinds).
        revision:
          $ref: '#/components/schemas/revision'
      not:
        required:
          - resource
          - patch
      additionalProperties: true
    callData:
      type: object
      required:
        - op
      properties:
        op:
          $ref: '#/components/schemas/operationId'
        params:
          type: object
        body: {}
        ifMatch:
          type: string
      additionalProperties: true
    envelope.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/common/1.0/envelope.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Message envelope for plain WS (sub-protocol freedome.v1), socket.io `event` payload, GraphQL Event and OSC JSON events, 02e §7.1 / §8.1. `type`, `id`, `ts`, `data` on every message; `topic` on events; `replyTo` on result / error replies. Message types are open (readers ignore unknown ones).
      title: Message envelope
      type: object
      required:
        - type
        - id
        - ts
        - data
      properties:
        type:
          description: 'Known: hello, welcome, subscribe, unsubscribe, call, result, event, error, resync, ping, pong, bye.'
          type: string
          pattern: ^[a-z][a-zA-Z0-9]*$
        id:
          $ref: '#/components/schemas/uuidv7'
          description: Unique per message; the idempotency key of `call`.
        ts:
          $ref: '#/components/schemas/timestamp'
        topic:
          $ref: '#/components/schemas/topic'
        replyTo:
          $ref: '#/components/schemas/uuidv7'
        data: {}
      allOf:
        - if:
            properties:
              type:
                const: event
            required:
              - type
          then:
            required:
              - topic
            properties:
              data:
                $ref: '#/components/schemas/eventData'
        - if:
            properties:
              type:
                const: result
            required:
              - type
          then:
            required:
              - replyTo
        - if:
            properties:
              type:
                const: error
            required:
              - type
          then:
            properties:
              data:
                $ref: '#/components/schemas/problem.schema'
        - if:
            properties:
              type:
                const: call
            required:
              - type
          then:
            properties:
              data:
                $ref: '#/components/schemas/callData'
      additionalProperties: true
      examples:
        - type: event
          id: 0192f3a4-5b6c-7d8e-9f01-23456789abcd
          ts: '2026-10-19T12:00:00.123Z'
          topic: venues/default/players/main
          data:
            seq: 48213
            kind: player.state
            resource:
              status: playing
              position: 12.5
      $defs:
        knownType:
          enum:
            - hello
            - welcome
            - subscribe
            - unsubscribe
            - call
            - result
            - event
            - error
            - resync
            - ping
            - pong
            - bye
        eventData:
          type: object
          required:
            - seq
            - kind
          properties:
            seq:
              type: integer
              minimum: 0
              description: Per-server monotonically increasing sequence (resume with sinceSeq).
            kind:
              type: string
              pattern: ^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$
              description: Event kind from events/topics.yaml, e.g. player.state.
            resource:
              description: Full new representation.
            patch:
              type: object
              description: JSON merge patch (high-rate kinds).
            revision:
              $ref: '#/components/schemas/revision'
          not:
            required:
              - resource
              - patch
          additionalProperties: true
        callData:
          type: object
          required:
            - op
          properties:
            op:
              $ref: '#/components/schemas/operationId'
            params:
              type: object
            body: {}
            ifMatch:
              type: string
          additionalProperties: true
    engineStatus:
      type: object
      required:
        - name
        - status
      properties:
        name:
          anyOf:
            - enum:
                - visual
                - anantasound
                - anantadigital
            - type: string
              pattern: ^[a-z][a-z0-9_-]{0,31}(@[A-Za-z0-9._-]{1,64})?$
        status:
          enum:
            - up
            - degraded
            - down
            - incompatible
        version:
          type: string
        protocol:
          type: string
          examples:
            - freedome.engine.v1
        capabilities:
          type: array
          items:
            type: string
        message:
          type: string
      additionalProperties: false
    engineMetrics:
      description: ≤ 1 Hz; any subset (patch) of the metrics.
      type: object
      properties:
        fps:
          type: number
          minimum: 0
        frameTimeMs:
          type: number
          minimum: 0
        droppedFrames:
          type: integer
          minimum: 0
        gpuLoad:
          type: number
          minimum: 0
          maximum: 1
        cpuLoad:
          type: number
          minimum: 0
          maximum: 1
        temperatureC:
          type: number
      patternProperties:
        ^x-: true
      additionalProperties: false
    deleted:
      type: object
      required:
        - id
      properties:
        id:
          $ref: '#/components/schemas/docId'
      additionalProperties: false
    activeShow-2:
      type: object
      required:
        - activeShowId
      properties:
        activeShowId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
      additionalProperties: false
    progressPatch:
      type: object
      minProperties: 1
      properties:
        progress:
          type: number
          minimum: 0
          maximum: 1
        currentStep:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
        message:
          type: string
      additionalProperties: false
    awaiting-2:
      description: Same object as calibration-job `awaiting` (null when nothing is awaited).
      anyOf:
        - type: 'null'
        - $ref: '#/components/schemas/awaiting'
    anyPatch:
      description: Merge patch of the resource (high-rate kinds).
      type: object
      minProperties: 1
    positionPatch:
      description: player.position (default 4 Hz, ≤ 30 Hz per subscription option positionHz).
      type: object
      required:
        - position
      properties:
        position:
          $ref: '#/components/schemas/position'
        duration:
          anyOf:
            - $ref: '#/components/schemas/seconds'
            - type: 'null'
        rate:
          type: number
        timecode:
          type: string
          pattern: ^\d{2}:\d{2}:\d{2}[:;]\d{2}$
      additionalProperties: false
    levels:
      description: 'audio.levels meters (≤ 10 Hz): id (speaker / zone / device / master) → dBFS.'
      type: object
      required:
        - levels
      properties:
        levels:
          type: object
          additionalProperties:
            type: number
            maximum: 24
        peak:
          type: object
          additionalProperties:
            type: number
            maximum: 24
      additionalProperties: false
    showItem:
      description: Fine-grained show edit (sub-kind of show.updated).
      type: object
      required:
        - showId
        - itemId
        - revision
      properties:
        showId:
          $ref: '#/components/schemas/docId'
        itemId:
          $ref: '#/components/schemas/docId'
        revision:
          $ref: '#/components/schemas/revision'
        item:
          type: object
          description: the stored .dome entry (absent on delete)
      additionalProperties: false
    uploadProgress:
      type: object
      required:
        - offset
      properties:
        offset:
          type: integer
          minimum: 0
        length:
          type: integer
          minimum: 0
        status:
          type: string
      additionalProperties: false
    gatewayMessage:
      description: Debug trace of a gateway message (gateways:manage only).
      type: object
      required:
        - direction
        - summary
      properties:
        direction:
          enum:
            - in
            - out
        peer:
          type: string
        summary:
          type: string
        mapsTo:
          $ref: '#/components/schemas/operationId'
        raw:
          type: string
          maxLength: 4096
      additionalProperties: false
    appCleared:
      type: object
      required:
        - app
      properties:
        app:
          type: string
          pattern: ^[A-Za-z0-9._-]{1,128}$
      additionalProperties: false
    command:
      description: Server → device command, delivered as event client.command on clients/{clientId}/commands.
      type: object
      required:
        - command
      properties:
        command:
          anyOf:
            - enum:
                - vibrate
                - capture
                - sensors
                - biometric
            - type: string
              pattern: ^[a-z][A-Za-z0-9_.-]{0,63}$
        params:
          type: object
          additionalProperties: true
        expiresAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    sessionRevoked:
      type: object
      required:
        - reason
      properties:
        sessionId:
          type: string
        reason:
          anyOf:
            - enum:
                - signed-out
                - refresh-reuse
                - password-changed
                - user-deleted
                - key-revoked
                - admin
            - type: string
      additionalProperties: false
    status-5:
      anyOf:
        - enum:
            - starting
            - running
            - paused
            - finished
            - stopped
            - failed
        - type: string
          pattern: ^[a-z][a-z_-]{0,31}$
    session-2:
      type: object
      required:
        - id
        - venueId
        - scenarioId
        - status
        - params
        - startedAt
      properties:
        id:
          $ref: '#/components/schemas/uuidv7'
        venueId:
          $ref: '#/components/schemas/docId'
        scenarioId:
          type: string
          pattern: ^[a-z][a-z0-9_]{0,63}$
        scenarioVersion:
          $ref: '#/components/schemas/semver'
        status:
          $ref: '#/components/schemas/status-5'
        params:
          type: object
        owner:
          type: string
          description: principal that started it (the session runs with its role)
        progress:
          $ref: '#/components/schemas/unitInterval'
        phase:
          type: string
        message:
          type: string
        problem:
          $ref: '#/components/schemas/problem.schema'
        startedAt:
          $ref: '#/components/schemas/timestamp'
        endedAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
      allOf:
        - if:
            properties:
              status:
                const: failed
            required:
              - status
          then:
            required:
              - problem
    logEntry-2:
      type: object
      required:
        - seq
        - ts
        - level
        - message
      properties:
        seq:
          type: integer
          minimum: 0
        ts:
          $ref: '#/components/schemas/timestamp'
        level:
          enum:
            - debug
            - info
            - warn
            - error
        message:
          type: string
        fields:
          type: object
          additionalProperties: true
      additionalProperties: false
    scenario-session.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/scenario-session.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Scenario session (02d §3 ScenarioSession, UC-SC2–SC4; topic venues/{venueId}/scenarios/{sessionId}, RC5). Root = GET /venues/{venueId}/scenario-sessions/{sessionId}.
      title: Scenario session
      allOf:
        - $ref: '#/components/schemas/session-2'
      $defs:
        status:
          anyOf:
            - enum:
                - starting
                - running
                - paused
                - finished
                - stopped
                - failed
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        session:
          type: object
          required:
            - id
            - venueId
            - scenarioId
            - status
            - params
            - startedAt
          properties:
            id:
              $ref: '#/components/schemas/uuidv7'
            venueId:
              $ref: '#/components/schemas/docId'
            scenarioId:
              type: string
              pattern: ^[a-z][a-z0-9_]{0,63}$
            scenarioVersion:
              $ref: '#/components/schemas/semver'
            status:
              $ref: '#/components/schemas/status-5'
            params:
              type: object
            owner:
              type: string
              description: principal that started it (the session runs with its role)
            progress:
              $ref: '#/components/schemas/unitInterval'
            phase:
              type: string
            message:
              type: string
            problem:
              $ref: '#/components/schemas/problem.schema'
            startedAt:
              $ref: '#/components/schemas/timestamp'
            endedAt:
              $ref: '#/components/schemas/timestamp'
          patternProperties:
            ^x-: true
          additionalProperties: false
          allOf:
            - if:
                properties:
                  status:
                    const: failed
                required:
                  - status
              then:
                required:
                  - problem
        start:
          description: POST /venues/{venueId}/scenario-sessions (UC-SC2); params are validated against the scenario's paramsSchema (422).
          type: object
          required:
            - scenarioId
          properties:
            scenarioId:
              type: string
              pattern: ^[a-z][a-z0-9_]{0,63}$
            params:
              type: object
              default: {}
            at:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/session-2'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        logEntry:
          type: object
          required:
            - seq
            - ts
            - level
            - message
          properties:
            seq:
              type: integer
              minimum: 0
            ts:
              $ref: '#/components/schemas/timestamp'
            level:
              enum:
                - debug
                - info
                - warn
                - error
            message:
              type: string
            fields:
              type: object
              additionalProperties: true
          additionalProperties: false
        logPage:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/logEntry-2'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
    device-2:
      type: object
      required:
        - id
        - variant
        - address
        - paired
        - state
      properties:
        id:
          $ref: '#/components/schemas/docId'
        serial:
          type: string
        product:
          type: string
        variant:
          type: string
          description: fd-vp-f-1, fd-vp-p-s, …
        hwRevision:
          type: string
        agentVersion:
          type: string
        protocolVersion:
          type: string
        capabilities:
          type: array
          items:
            type: string
          uniqueItems: true
        address:
          type: string
          description: host:port of the agent
        paired:
          type: boolean
        displayId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
          description: venue display this device drives
        state:
          type: object
          required:
            - online
          properties:
            online:
              type: boolean
            power:
              anyOf:
                - enum:
                    - 'on'
                    - 'off'
                    - standby
                    - warming
                    - cooling
                    - unknown
                - type: string
            temperatureC:
              type: number
            fanRpm:
              type: number
              minimum: 0
            lampHours:
              type: number
              minimum: 0
            battery:
              $ref: '#/components/schemas/unitInterval'
            faults:
              type: array
              items:
                type: string
            lastSeen:
              $ref: '#/components/schemas/timestamp'
          additionalProperties: true
      patternProperties:
        ^x-: true
      additionalProperties: false
    device.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/device.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). Visual device agents speaking freedome.device.v1 (projector variants FD-VP-*, master PR3, MS 1.MS.18): discovery, pairing, binding to a display, telemetry. Root = GET /devices/{deviceId}.'
      title: Visual device
      allOf:
        - $ref: '#/components/schemas/device-2'
      $defs:
        device:
          type: object
          required:
            - id
            - variant
            - address
            - paired
            - state
          properties:
            id:
              $ref: '#/components/schemas/docId'
            serial:
              type: string
            product:
              type: string
            variant:
              type: string
              description: fd-vp-f-1, fd-vp-p-s, …
            hwRevision:
              type: string
            agentVersion:
              type: string
            protocolVersion:
              type: string
            capabilities:
              type: array
              items:
                type: string
              uniqueItems: true
            address:
              type: string
              description: host:port of the agent
            paired:
              type: boolean
            displayId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
              description: venue display this device drives
            state:
              type: object
              required:
                - online
              properties:
                online:
                  type: boolean
                power:
                  anyOf:
                    - enum:
                        - 'on'
                        - 'off'
                        - standby
                        - warming
                        - cooling
                        - unknown
                    - type: string
                temperatureC:
                  type: number
                fanRpm:
                  type: number
                  minimum: 0
                lampHours:
                  type: number
                  minimum: 0
                battery:
                  $ref: '#/components/schemas/unitInterval'
                faults:
                  type: array
                  items:
                    type: string
                lastSeen:
                  $ref: '#/components/schemas/timestamp'
              additionalProperties: true
          patternProperties:
            ^x-: true
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/device-2'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
        pairingStarted:
          description: 'POST /devices/{deviceId}/pairing: the device shows a code (display / LED); the operator confirms it.'
          type: object
          required:
            - deviceId
            - expiresAt
          properties:
            deviceId:
              $ref: '#/components/schemas/docId'
            expiresAt:
              $ref: '#/components/schemas/timestamp'
            codeLength:
              type: integer
              minimum: 4
              maximum: 8
          additionalProperties: false
        pairingConfirm:
          type: object
          required:
            - code
          properties:
            code:
              type: string
              pattern: ^[0-9A-Z]{4,8}$
          additionalProperties: false
        bind:
          type: object
          required:
            - displayId
          properties:
            displayId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
              description: null unbinds
          additionalProperties: false
    frame:
      description: One analysis frame of one input (event audio.analysis). rms, peak and bands are linear amplitude relative to full scale, clamped to 0..1, after the input's envelope (a voice at -24 dBFS reads 0.06). Scene smoothing is applied after this.
      type: object
      required:
        - inputId
        - frame
        - captureUs
        - rms
        - peak
        - bands
        - centroid
        - onset
      properties:
        inputId:
          $ref: '#/components/schemas/docId'
        frame:
          type: integer
          minimum: 0
          description: frame counter of this input since the analysis started
        captureUs:
          type: integer
          minimum: 0
          description: start of the audio block on the analysis node's monotonic clock, µs; comparable only on that node
        rms:
          $ref: '#/components/schemas/unitInterval'
        peak:
          $ref: '#/components/schemas/unitInterval'
        rmsDb:
          type: number
          description: dBFS, before normalisation
        peakDb:
          type: number
          description: dBFS, before normalisation
        bands:
          $ref: '#/components/schemas/bands'
        centroid:
          $ref: '#/components/schemas/unitInterval'
          description: centroidHz on a log scale over the input's centroidRange
        centroidHz:
          $ref: '#/components/schemas/hertz'
        onset:
          $ref: '#/components/schemas/unitInterval'
          description: attack strength of this frame; 0 = no attack
        gated:
          type: boolean
          description: 'the input is below its gate: normalised values are 0'
      patternProperties:
        ^x-: true
      additionalProperties: false
    onset:
      description: One detected attack (event audio.onset).
      type: object
      required:
        - inputId
        - frame
        - captureUs
        - strength
      properties:
        inputId:
          $ref: '#/components/schemas/docId'
        frame:
          type: integer
          minimum: 0
        captureUs:
          type: integer
          minimum: 0
        strength:
          $ref: '#/components/schemas/unitInterval'
        band:
          enum:
            - subBass
            - mid
            - highMid
            - treble
          description: band with the largest energy rise, when known
      patternProperties:
        ^x-: true
      additionalProperties: false
    event-kinds.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/event-kinds.schema.json
      $comment: 'NativeMindNONC — freedome_contract (see LICENSE). `data` of an event message (02e §7.1) with the payload bound to its `kind` (events/topics.yaml). v1: every kind of the topic tree (02e §7.2 + scenarios, parameters, nodes, devices). Unknown kinds stay valid (open catalogue, 02e §13).'
      title: Event data by kind
      type: object
      allOf:
        - $ref: '#/components/schemas/eventData'
        - if:
            properties:
              kind:
                const: system.info
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/info.schema'
        - if:
            properties:
              kind:
                const: system.health
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/health'
        - if:
            properties:
              kind:
                const: engine.status
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/engineStatus'
        - if:
            properties:
              kind:
                const: engine.metrics
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/engineMetrics'
              patch:
                $ref: '#/components/schemas/engineMetrics'
        - if:
            properties:
              kind:
                const: engine.error
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/problem.schema'
        - if:
            properties:
              kind:
                const: venue.updated
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/venue.schema'
              patch:
                $ref: '#/components/schemas/patch'
        - if:
            properties:
              kind:
                const: venue.deleted
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/deleted'
        - if:
            properties:
              kind:
                const: venue.activeShow
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/activeShow-2'
        - if:
            properties:
              kind:
                const: output.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/output.schema'
              patch:
                $ref: '#/components/schemas/patch-3'
        - if:
            properties:
              kind:
                const: display.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/state'
              patch:
                type: object
        - if:
            properties:
              kind:
                const: display.pattern
            required:
              - kind
          then:
            properties:
              resource:
                anyOf:
                  - $ref: '#/components/schemas/testPattern'
                  - type: 'null'
        - if:
            properties:
              kind:
                const: display.error
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/problem.schema'
        - if:
            properties:
              kind:
                const: calibration.updated
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/calibration'
        - if:
            properties:
              kind:
                const: calibrationJob.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/calibration-job.schema'
        - if:
            properties:
              kind:
                const: calibrationJob.progress
            required:
              - kind
          then:
            properties:
              patch:
                $ref: '#/components/schemas/progressPatch'
        - if:
            properties:
              kind:
                const: calibrationJob.awaiting
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/awaiting-2'
        - if:
            properties:
              kind:
                const: job.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/job.schema'
        - if:
            properties:
              kind:
                const: job.progress
            required:
              - kind
          then:
            properties:
              patch:
                $ref: '#/components/schemas/progressPatch'
        - if:
            properties:
              kind:
                const: log.entry
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/logEntry'
        - if:
            properties:
              kind:
                const: node.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/node'
              patch:
                $ref: '#/components/schemas/anyPatch'
        - if:
            properties:
              kind:
                const: player.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/player.schema'
              patch:
                $ref: '#/components/schemas/anyPatch'
        - if:
            properties:
              kind:
                const: player.position
            required:
              - kind
          then:
            properties:
              patch:
                $ref: '#/components/schemas/positionPatch'
        - if:
            properties:
              kind:
                const: player.ended
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/player.schema'
        - if:
            properties:
              kind:
                const: player.error
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/problem.schema'
        - if:
            properties:
              kind:
                const: cue.fired
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/cueFired'
        - if:
            properties:
              kind:
                const: cue.failed
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/cueFired'
        - if:
            properties:
              kind:
                const: parameter.changed
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/parameter'
        - if:
            properties:
              kind:
                const: audio.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/audio.schema'
              patch:
                $ref: '#/components/schemas/patch-8'
        - if:
            properties:
              kind:
                const: audio.layout
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/audio'
        - if:
            properties:
              kind:
                const: audio.levels
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/levels'
        - if:
            properties:
              kind:
                const: zone.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/zone'
              patch:
                $ref: '#/components/schemas/anyPatch'
        - if:
            properties:
              kind:
                const: zone.deleted
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/deleted'
        - if:
            properties:
              kind:
                const: audioDevice.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/device'
              patch:
                $ref: '#/components/schemas/anyPatch'
        - if:
            properties:
              kind:
                const: audioSession.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/session'
              patch:
                $ref: '#/components/schemas/anyPatch'
        - if:
            properties:
              kind:
                const: generator.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/generatorState'
        - if:
            properties:
              kind:
                const: show.updated
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/show.schema'
              patch:
                $ref: '#/components/schemas/patch-5'
        - if:
            properties:
              kind:
                const: show.deleted
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/deleted'
        - if:
            properties:
              kind:
                const: playlist.updated
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/showItem'
        - if:
            properties:
              kind:
                const: playlist.deleted
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/showItem'
        - if:
            properties:
              kind:
                const: timeline.updated
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/showItem'
        - if:
            properties:
              kind:
                const: timeline.deleted
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/showItem'
        - if:
            properties:
              kind:
                const: cue.updated
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/showItem'
        - if:
            properties:
              kind:
                const: cue.deleted
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/showItem'
        - if:
            properties:
              kind:
                const: trigger.updated
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/showItem'
        - if:
            properties:
              kind:
                const: trigger.deleted
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/showItem'
        - if:
            properties:
              kind:
                const: matrix.updated
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/showItem'
        - if:
            properties:
              kind:
                const: asset.created
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/asset.schema'
        - if:
            properties:
              kind:
                const: asset.progress
            required:
              - kind
          then:
            properties:
              patch:
                $ref: '#/components/schemas/progressPatch'
        - if:
            properties:
              kind:
                const: asset.ready
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/asset.schema'
        - if:
            properties:
              kind:
                const: asset.failed
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/asset.schema'
        - if:
            properties:
              kind:
                const: asset.deleted
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/deleted'
        - if:
            properties:
              kind:
                const: upload.progress
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/upload.schema'
              patch:
                $ref: '#/components/schemas/uploadProgress'
        - if:
            properties:
              kind:
                const: gateway.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/gateway.schema'
              patch:
                $ref: '#/components/schemas/anyPatch'
        - if:
            properties:
              kind:
                const: gateway.message
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/gatewayMessage'
        - if:
            properties:
              kind:
                const: app.payload
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/published'
        - if:
            properties:
              kind:
                const: app.cleared
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/appCleared'
        - if:
            properties:
              kind:
                const: client.command
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/command'
        - if:
            properties:
              kind:
                const: session.revoked
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/sessionRevoked'
        - if:
            properties:
              kind:
                const: scenarioSession.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/scenario-session.schema'
              patch:
                $ref: '#/components/schemas/anyPatch'
        - if:
            properties:
              kind:
                const: scenarioSession.log
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/logEntry-2'
        - if:
            properties:
              kind:
                const: device.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/device.schema'
              patch:
                $ref: '#/components/schemas/anyPatch'
        - if:
            properties:
              kind:
                const: audioAnalysis.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/analysisState'
        - if:
            properties:
              kind:
                const: audio.analysis
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/frame'
        - if:
            properties:
              kind:
                const: audio.onset
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/onset'
        - if:
            properties:
              kind:
                const: audioReactive.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/state-2'
        - if:
            properties:
              kind:
                const: audioReactiveScene.updated
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/scene'
        - if:
            properties:
              kind:
                const: audioReactiveScene.deleted
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/deleted'
        - if:
            properties:
              kind:
                const: textureSource.state
            required:
              - kind
          then:
            properties:
              resource:
                $ref: '#/components/schemas/textureSource'
      $defs:
        knownKind:
          description: Kinds of the v1 topic tree (events/topics.yaml); open catalogue — clients ignore unknown kinds.
          enum:
            - system.info
            - system.health
            - engine.status
            - engine.metrics
            - engine.error
            - venue.updated
            - venue.deleted
            - venue.activeShow
            - output.state
            - display.state
            - display.pattern
            - display.error
            - calibration.updated
            - calibrationJob.state
            - calibrationJob.progress
            - calibrationJob.awaiting
            - job.state
            - job.progress
            - log.entry
            - node.state
            - player.state
            - player.position
            - player.ended
            - player.error
            - cue.fired
            - cue.failed
            - parameter.changed
            - audio.state
            - audio.layout
            - audio.levels
            - zone.state
            - zone.deleted
            - audioDevice.state
            - audioSession.state
            - generator.state
            - show.updated
            - show.deleted
            - playlist.updated
            - playlist.deleted
            - timeline.updated
            - timeline.deleted
            - cue.updated
            - cue.deleted
            - trigger.updated
            - trigger.deleted
            - matrix.updated
            - asset.created
            - asset.progress
            - asset.ready
            - asset.failed
            - asset.deleted
            - upload.progress
            - gateway.state
            - gateway.message
            - app.payload
            - app.cleared
            - client.command
            - session.revoked
            - scenarioSession.state
            - scenarioSession.log
            - device.state
            - audioAnalysis.state
            - audio.analysis
            - audio.onset
            - audioReactive.state
            - audioReactiveScene.updated
            - audioReactiveScene.deleted
            - textureSource.state
        engineStatus:
          type: object
          required:
            - name
            - status
          properties:
            name:
              anyOf:
                - enum:
                    - visual
                    - anantasound
                    - anantadigital
                - type: string
                  pattern: ^[a-z][a-z0-9_-]{0,31}(@[A-Za-z0-9._-]{1,64})?$
            status:
              enum:
                - up
                - degraded
                - down
                - incompatible
            version:
              type: string
            protocol:
              type: string
              examples:
                - freedome.engine.v1
            capabilities:
              type: array
              items:
                type: string
            message:
              type: string
          additionalProperties: false
        engineMetrics:
          description: ≤ 1 Hz; any subset (patch) of the metrics.
          type: object
          properties:
            fps:
              type: number
              minimum: 0
            frameTimeMs:
              type: number
              minimum: 0
            droppedFrames:
              type: integer
              minimum: 0
            gpuLoad:
              type: number
              minimum: 0
              maximum: 1
            cpuLoad:
              type: number
              minimum: 0
              maximum: 1
            temperatureC:
              type: number
          patternProperties:
            ^x-: true
          additionalProperties: false
        deleted:
          type: object
          required:
            - id
          properties:
            id:
              $ref: '#/components/schemas/docId'
          additionalProperties: false
        activeShow:
          type: object
          required:
            - activeShowId
          properties:
            activeShowId:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
          additionalProperties: false
        progressPatch:
          type: object
          minProperties: 1
          properties:
            progress:
              type: number
              minimum: 0
              maximum: 1
            currentStep:
              anyOf:
                - $ref: '#/components/schemas/docId'
                - type: 'null'
            message:
              type: string
          additionalProperties: false
        awaiting:
          description: Same object as calibration-job `awaiting` (null when nothing is awaited).
          anyOf:
            - type: 'null'
            - $ref: '#/components/schemas/awaiting'
        anyPatch:
          description: Merge patch of the resource (high-rate kinds).
          type: object
          minProperties: 1
        positionPatch:
          description: player.position (default 4 Hz, ≤ 30 Hz per subscription option positionHz).
          type: object
          required:
            - position
          properties:
            position:
              $ref: '#/components/schemas/position'
            duration:
              anyOf:
                - $ref: '#/components/schemas/seconds'
                - type: 'null'
            rate:
              type: number
            timecode:
              type: string
              pattern: ^\d{2}:\d{2}:\d{2}[:;]\d{2}$
          additionalProperties: false
        levels:
          description: 'audio.levels meters (≤ 10 Hz): id (speaker / zone / device / master) → dBFS.'
          type: object
          required:
            - levels
          properties:
            levels:
              type: object
              additionalProperties:
                type: number
                maximum: 24
            peak:
              type: object
              additionalProperties:
                type: number
                maximum: 24
          additionalProperties: false
        showItem:
          description: Fine-grained show edit (sub-kind of show.updated).
          type: object
          required:
            - showId
            - itemId
            - revision
          properties:
            showId:
              $ref: '#/components/schemas/docId'
            itemId:
              $ref: '#/components/schemas/docId'
            revision:
              $ref: '#/components/schemas/revision'
            item:
              type: object
              description: the stored .dome entry (absent on delete)
          additionalProperties: false
        uploadProgress:
          type: object
          required:
            - offset
          properties:
            offset:
              type: integer
              minimum: 0
            length:
              type: integer
              minimum: 0
            status:
              type: string
          additionalProperties: false
        gatewayMessage:
          description: Debug trace of a gateway message (gateways:manage only).
          type: object
          required:
            - direction
            - summary
          properties:
            direction:
              enum:
                - in
                - out
            peer:
              type: string
            summary:
              type: string
            mapsTo:
              $ref: '#/components/schemas/operationId'
            raw:
              type: string
              maxLength: 4096
          additionalProperties: false
        appCleared:
          type: object
          required:
            - app
          properties:
            app:
              type: string
              pattern: ^[A-Za-z0-9._-]{1,128}$
          additionalProperties: false
        sessionRevoked:
          type: object
          required:
            - reason
          properties:
            sessionId:
              type: string
            reason:
              anyOf:
                - enum:
                    - signed-out
                    - refresh-reuse
                    - password-changed
                    - user-deleted
                    - key-revoked
                    - admin
                - type: string
          additionalProperties: false
    event:
      description: S→C event (02e §7.1).
      type: object
      allOf:
        - $ref: '#/components/schemas/envelope.schema'
      required:
        - type
        - id
        - ts
        - data
        - topic
      properties:
        type:
          const: event
        data:
          $ref: '#/components/schemas/event-kinds.schema'
    eventPage:
      description: 'GET /events (history ring buffer, resume after reconnect): events with seq > sinceSeq on the requested topics. `resync: true` = the gap is older than the buffer; re-read state.'
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/event'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
        lastSeq:
          type: integer
          minimum: 0
        resync:
          type: boolean
      additionalProperties: false
    capability-2:
      anyOf:
        - enum:
            - playback
            - parameters
            - audioDigital
            - audioHybrid
            - cues
            - timers
            - events
            - storage
            - http
        - type: string
          pattern: ^[a-z][A-Za-z0-9]{0,31}$
    scenario:
      type: object
      required:
        - id
        - version
        - api
        - title
        - enabled
        - paramsSchema
        - capabilities
      properties:
        id:
          type: string
          pattern: ^[a-z][a-z0-9_]{0,63}$
        version:
          $ref: '#/components/schemas/semver'
        api:
          type: string
          pattern: ^[0-9]+\.[0-9]+$
          description: freedome-scenario-api major.minor the plugin is built against
        title:
          $ref: '#/components/schemas/i18n-text.schema'
        description:
          $ref: '#/components/schemas/i18n-text.schema'
        enabled:
          type: boolean
        paramsSchema:
          type: object
          description: JSON Schema of the start parameters (Manager renders the form)
        capabilities:
          type: array
          items:
            $ref: '#/components/schemas/capability-2'
          uniqueItems: true
        minRole:
          $ref: '#/components/schemas/role'
        sessions:
          type: integer
          minimum: 0
          description: running sessions
      patternProperties:
        ^x-: true
      additionalProperties: false
    page-12:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/scenario'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    scenario.schema:
      $schema: https://json-schema.org/draft/2020-12/schema
      $id: https://freedome.nativemind.us/schemas/api/1.0/scenario.schema.json
      $comment: NativeMindNONC — freedome_contract (see LICENSE). Installed L6 scenario plugin (02d §4a ScenarioManifest, UC-SC1 / SC5; RC5). Root = GET /scenarios/{scenarioId}.
      title: Scenario
      allOf:
        - $ref: '#/components/schemas/scenario'
      $defs:
        capability:
          anyOf:
            - enum:
                - playback
                - parameters
                - audioDigital
                - audioHybrid
                - cues
                - timers
                - events
                - storage
                - http
            - type: string
              pattern: ^[a-z][A-Za-z0-9]{0,31}$
        scenario:
          type: object
          required:
            - id
            - version
            - api
            - title
            - enabled
            - paramsSchema
            - capabilities
          properties:
            id:
              type: string
              pattern: ^[a-z][a-z0-9_]{0,63}$
            version:
              $ref: '#/components/schemas/semver'
            api:
              type: string
              pattern: ^[0-9]+\.[0-9]+$
              description: freedome-scenario-api major.minor the plugin is built against
            title:
              $ref: '#/components/schemas/i18n-text.schema'
            description:
              $ref: '#/components/schemas/i18n-text.schema'
            enabled:
              type: boolean
            paramsSchema:
              type: object
              description: JSON Schema of the start parameters (Manager renders the form)
            capabilities:
              type: array
              items:
                $ref: '#/components/schemas/capability-2'
              uniqueItems: true
            minRole:
              $ref: '#/components/schemas/role'
            sessions:
              type: integer
              minimum: 0
              description: running sessions
          patternProperties:
            ^x-: true
          additionalProperties: false
        patch:
          description: PATCH /scenarios/{scenarioId} (admin, UC-SC5).
          type: object
          required:
            - enabled
          properties:
            enabled:
              type: boolean
          additionalProperties: false
        page:
          type: object
          required:
            - items
            - nextCursor
          properties:
            items:
              type: array
              items:
                $ref: '#/components/schemas/scenario'
            nextCursor:
              anyOf:
                - type: string
                - type: 'null'
          additionalProperties: false
    patch-11:
      description: PATCH /scenarios/{scenarioId} (admin, UC-SC5).
      type: object
      required:
        - enabled
      properties:
        enabled:
          type: boolean
      additionalProperties: false
    page-13:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/session-2'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    start-2:
      description: POST /venues/{venueId}/scenario-sessions (UC-SC2); params are validated against the scenario's paramsSchema (422).
      type: object
      required:
        - scenarioId
      properties:
        scenarioId:
          type: string
          pattern: ^[a-z][a-z0-9_]{0,63}$
        params:
          type: object
          default: {}
        at:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    logPage-2:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/logEntry-2'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    page-14:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/device-2'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    pairingStarted:
      description: 'POST /devices/{deviceId}/pairing: the device shows a code (display / LED); the operator confirms it.'
      type: object
      required:
        - deviceId
        - expiresAt
      properties:
        deviceId:
          $ref: '#/components/schemas/docId'
        expiresAt:
          $ref: '#/components/schemas/timestamp'
        codeLength:
          type: integer
          minimum: 4
          maximum: 8
      additionalProperties: false
    pairingConfirm:
      type: object
      required:
        - code
      properties:
        code:
          type: string
          pattern: ^[0-9A-Z]{4,8}$
      additionalProperties: false
    bind:
      type: object
      required:
        - displayId
      properties:
        displayId:
          anyOf:
            - $ref: '#/components/schemas/docId'
            - type: 'null'
          description: null unbinds
      additionalProperties: false
    page-15:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/job'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
  responses:
    Problem:
      description: Error (RFC 9457 problem details, 02e §3.3).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/problem.schema'
          example:
            type: https://freedome.nativemind.us/problems/internal
            title: Internal error
            status: 500
            code: internal
            traceId: 4bf92f3577b34da6a3ce929d0e0e4736
    NotModified:
      description: Representation unchanged (If-None-Match).
      headers:
        ETag:
          $ref: '#/components/headers/ETag'
    TooManyRequests:
      description: Rate limited.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        RateLimit:
          $ref: '#/components/headers/RateLimit'
        RateLimit-Policy:
          $ref: '#/components/headers/RateLimitPolicy'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/problem.schema'
          example:
            type: https://freedome.nativemind.us/problems/rate-limited
            title: Too many requests
            status: 429
            code: rate-limited
    NoContent:
      description: Done, no body.
    AcceptedJob:
      description: Long action accepted; `Location` → /api/v1/jobs/{jobId}; body = the job (02e §3.1).
      headers:
        Location:
          $ref: '#/components/headers/Location'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/job.schema'
          example:
            id: 0192f3a4-5b6c-7d8e-9f01-23456789abce
            kind: diagnostics
            status: queued
            createdAt: '2026-10-19T12:00:00.000Z'
            links:
              self: /api/v1/jobs/0192f3a4-5b6c-7d8e-9f01-23456789abce
              cancel: /api/v1/jobs/0192f3a4-5b6c-7d8e-9f01-23456789abce/cancel
  headers:
    ETag:
      description: Strong ETag derived from the resource `revision` (sha256 for downloads).
      schema:
        type: string
    RetryAfter:
      description: Seconds to wait (429, 503).
      schema:
        type: integer
        minimum: 0
    RateLimit:
      description: IETF RateLimit header (draft-ietf-httpapi-ratelimit-headers).
      schema:
        type: string
    RateLimitPolicy:
      description: IETF RateLimit-Policy header.
      schema:
        type: string
    Link:
      description: RFC 8288 links, e.g. `<…>; rel="next"`, `rel="deprecation"`.
      schema:
        type: string
    Location:
      description: URL of the created resource or of the job of a 202 response.
      schema:
        type: string
        format: uri-reference
    TusResumable:
      description: tus protocol version of the response (always `1.0.0`).
      schema:
        type: string
        enum:
          - 1.0.0
    UploadOffset:
      description: Bytes received and persisted so far.
      schema:
        type: integer
        minimum: 0
    UploadExpires:
      description: RFC 9110 HTTP-date after which an unfinished upload is deleted.
      schema:
        type: string
    TusVersion:
      description: Supported tus versions (OPTIONS).
      schema:
        type: string
        example: 1.0.0
    TusExtension:
      description: Supported tus extensions (OPTIONS).
      schema:
        type: string
        example: creation,creation-with-upload,termination,checksum,expiration
    TusMaxSize:
      description: Maximum upload size in bytes (configured quota), OPTIONS.
      schema:
        type: integer
        minimum: 0
    TusChecksumAlgorithm:
      description: Supported checksum algorithms (OPTIONS).
      schema:
        type: string
        enum:
          - sha256
    UploadLength:
      description: Total size of the upload in bytes.
      schema:
        type: integer
        minimum: 0
    UploadMetadata:
      description: 'tus Upload-Metadata: `key base64value` pairs, comma-separated (keys: filename, contentType, sha256, purpose, assetId).'
      schema:
        type: string
    CacheControl:
      description: '`no-store` on tus HEAD responses.'
      schema:
        type: string
    AcceptRanges:
      description: '`bytes` on downloads.'
      schema:
        type: string
        enum:
          - bytes
    ContentRange:
      description: Range of a 206 response.
      schema:
        type: string
