openapi: 3.1.0
info:
  title: Freedome cloud API (freedome_server)
  version: 1.0.0
  summary: 'L7 cloud server: accounts, organisations, registry, enrollment (outline; catalogue / sync / licences in P6).'
  description: |-
    OUTLINE (03e P2; detailed by 6.CT.1 with 02h). Base `https://freedome.nativemind.us/api/v1`; `/health`, `/ready`, `/metrics` unversioned.
    Conventions as the media server (docs/conventions.md): RFC 9457 `application/problem+json` errors (lowercase-kebab `code`), cursor pages (`limit` ≤ 100), `Idempotency-Key` on creating POSTs, ES256 access JWTs (15 min) + rotating refresh tokens, API keys `fdk_` (user / org), `fdi_` (installation), `fds_` (service).
    Operations with `x-status: planned` are not implemented yet (P6) and are excluded from conformance.
    Support: `support@freedome.nativemind.net`. Commercial use → https://freedome.nativemind.us/commercial.
  license:
    name: NativeMindNONC
    url: https://freedome.nativemind.us/license
servers:
  - url: https://freedome.nativemind.us/api/v1
    description: Cloud (public host, S1 paths)
tags:
  - name: discovery
    description: Self-description, versions, JWKS.
  - name: auth
    description: 'Accounts: sign-up / sign-in, refresh, e-mail, password, magic link, OAuth (Apple, Google).'
  - name: account
    description: The signed-in account, devices, deletion.
  - name: api-keys
    description: User and organisation API keys.
  - name: orgs
    description: Organisations, members, invitations.
  - name: registry
    description: Venues, domes, installations, enrollment tokens.
  - name: enrollment
    description: Media-server enrollment (RFC 8628 device code, enrollment tokens).
  - name: catalogue
    description: Content catalogue (planned).
  - name: apps
    description: Ecosystem app registry (planned).
paths:
  /info:
    get:
      operationId: getInfo
      tags:
        - discovery
      summary: Server self-description (product freedome-cloud)
      description: Server self-description (product freedome-cloud)
      security:
        - {}
      x-scope: anon
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/info.schema'
              example:
                product: freedome-cloud
                serverVersion: 0.1.0
                instanceId: 0192f3a4-5b6c-7d8e-9f01-23456789c0ff
                apiVersions:
                  - v1
        default:
          $ref: '#/components/responses/Problem'
  /meta:
    get:
      operationId: getMeta
      tags:
        - discovery
      summary: Server / contract / schema versions, channel states
      description: Server / contract / schema versions, channel states.
      security:
        - {}
      x-scope: anon
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/meta'
              example:
                serverVersion: 0.1.0
                contractVersion: 1.0.0-rc.1
                channels:
                  stable: 1.0.0
        default:
          $ref: '#/components/responses/Problem'
  /.well-known/jwks.json:
    get:
      operationId: getJwks
      tags:
        - discovery
      summary: JWKS for access and licence tokens (media servers verify offline)
      description: JWKS for access and licence tokens (media servers verify offline)
      security:
        - {}
      x-scope: anon
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/jwks'
              example:
                keys:
                  - kty: EC
                    crv: P-256
                    kid: k1
                    x: f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU
                    'y': x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0
                    alg: ES256
                    use: sig
        default:
          $ref: '#/components/responses/Problem'
  /auth/sign-up:
    post:
      operationId: signUp
      tags:
        - auth
      summary: Create an account (e-mail + password)
      description: Create an account (e-mail + password)
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/signUp'
            example:
              email: anna@example.org
              password: correct-horse-battery
              client: native
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/session'
              example:
                accessToken: example.access-token.not-a-secret
                tokenType: Bearer
                expiresIn: 900
                refreshToken: example-refresh-token-not-a-secret
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/sign-in:
    post:
      operationId: signIn
      tags:
        - auth
      summary: Sign in (e-mail + password)
      description: Sign in (e-mail + password)
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/signIn'
            example:
              email: anna@example.org
              password: correct-horse-battery
              client: native
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/session'
              example:
                accessToken: example.access-token.not-a-secret
                tokenType: Bearer
                expiresIn: 900
                refreshToken: example-refresh-token-not-a-secret
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/session:
    post:
      operationId: createSession
      tags:
        - auth
      summary: Sign in (02e §5.7 name; alias of signIn)
      description: Sign in (02e §5.7 name; alias of signIn)
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/signIn'
            example:
              email: anna@example.org
              password: correct-horse-battery
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/session'
              example:
                accessToken: example.access-token.not-a-secret
                tokenType: Bearer
                expiresIn: 900
                refreshToken: example-refresh-token-not-a-secret
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteSession
      tags:
        - auth
      summary: Sign out the current session (alias of signOut)
      description: Sign out the current session (alias of signOut)
      security:
        - bearer: []
        - apiKey: []
      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 (reuse revokes the family)
      description: Rotate the refresh token (reuse revokes the family)
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/refresh'
            example:
              refreshToken: example-refresh-token-not-a-secret
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/session'
              example:
                accessToken: example.access-token.not-a-secret
                tokenType: Bearer
                expiresIn: 900
                refreshToken: example-refresh-token-not-a-secret
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/sign-out:
    post:
      operationId: signOut
      tags:
        - auth
      summary: Sign out the current session
      description: Sign out the current session.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-idempotency: none
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/sign-out-all:
    post:
      operationId: signOutAll
      tags:
        - auth
      summary: Revoke every session of the account
      description: Revoke every session of the account.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-idempotency: none
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/email/verify:
    post:
      operationId: verifyEmail
      tags:
        - auth
      summary: Confirm the e-mail address with the mailed token
      description: Confirm the e-mail address with the mailed token.
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/token'
            example:
              token: example-mail-token-not-a-secret
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/email/verify/resend:
    post:
      operationId: resendEmailVerification
      tags:
        - auth
      summary: Send the verification e-mail again
      description: Send the verification e-mail again.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-idempotency: none
      responses:
        '202':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/accepted'
              example:
                status: queued
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/password/forgot:
    post:
      operationId: forgotPassword
      tags:
        - auth
      summary: Mail a reset link (no account enumeration)
      description: Mail a reset link (no account enumeration)
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/email'
            example:
              email: anna@example.org
      responses:
        '202':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/accepted'
              example:
                status: queued
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/password/reset:
    post:
      operationId: resetPassword
      tags:
        - auth
      summary: Set a new password with the mailed token (revokes sessions)
      description: Set a new password with the mailed token (revokes sessions)
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/passwordReset'
            example:
              token: example-mail-token-not-a-secret
              password: new-correct-horse
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/password/change:
    post:
      operationId: changePassword
      tags:
        - auth
      summary: Change the password of the signed-in account
      description: Change the password of the signed-in account.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/passwordChange'
            example:
              currentPassword: correct-horse-battery
              password: new-correct-horse
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/magic-link:
    post:
      operationId: requestMagicLink
      tags:
        - auth
      summary: Mail a one-time sign-in link
      description: Mail a one-time sign-in link.
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/email'
            example:
              email: anna@example.org
      responses:
        '202':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/accepted'
              example:
                status: queued
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/magic-link/verify:
    post:
      operationId: verifyMagicLink
      tags:
        - auth
      summary: Sign in with the magic-link token
      description: Sign in with the magic-link token.
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/token'
            example:
              token: example-mail-token-not-a-secret
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/session'
              example:
                accessToken: example.access-token.not-a-secret
                tokenType: Bearer
                expiresIn: 900
                refreshToken: example-refresh-token-not-a-secret
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /auth/oauth/{provider}:
    post:
      operationId: signInWithProvider
      tags:
        - auth
      summary: Sign in with Apple / Google (provider id token); 503 NOT_CONFIGURED without client ids
      description: Sign in with Apple / Google (provider id token); 503 NOT_CONFIGURED without client ids.
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      parameters:
        - $ref: '#/components/parameters/Provider'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/oauth'
            example:
              idToken: example.id-token.not-a-secret
              nonce: n-0S6_WzA2Mj
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/session'
              example:
                accessToken: example.access-token.not-a-secret
                tokenType: Bearer
                expiresIn: 900
                refreshToken: example-refresh-token-not-a-secret
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /me:
    get:
      operationId: getAccount
      tags:
        - account
      summary: The signed-in account with memberships
      description: The signed-in account with memberships.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/account'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c001
                email: anna@example.org
                emailVerified: true
                displayName: Anna
                platformRole: none
                status: active
                memberships:
                  - orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                    orgName: Dome Studio
                    role: owner
                createdAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchAccount
      tags:
        - account
      summary: Merge-patch profile (display name, locale)
      description: Merge-patch profile (display name, locale)
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-concurrency: none
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/accountPatch'
            example:
              displayName: Anna K.
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/account'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c001
                email: anna@example.org
                emailVerified: true
                displayName: Anna
                platformRole: none
                status: active
                memberships:
                  - orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                    orgName: Dome Studio
                    role: owner
                createdAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteAccount
      tags:
        - account
      summary: Delete the account (7-day grace; 409 while owning an org)
      description: Delete the account (7-day grace; 409 while owning an org)
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-concurrency: none
      responses:
        '202':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/account'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c001
                email: anna@example.org
                emailVerified: true
                displayName: Anna
                platformRole: none
                status: deleting
                memberships:
                  - orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                    orgName: Dome Studio
                    role: owner
                createdAt: '2026-10-19T12:00:00.000Z'
                hardDeleteAfter: '2026-10-26T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /me/deletion/cancel:
    post:
      operationId: cancelAccountDeletion
      tags:
        - account
      summary: Cancel a pending deletion
      description: Cancel a pending deletion.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-idempotency: none
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/account'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c001
                email: anna@example.org
                emailVerified: true
                displayName: Anna
                platformRole: none
                status: active
                memberships:
                  - orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                    orgName: Dome Studio
                    role: owner
                createdAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /me/devices:
    get:
      operationId: listAccountDevices
      tags:
        - account
      summary: Active logins (refresh-token families)
      description: Active logins (refresh-token families)
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/loginDevicePage'
              example:
                items:
                  - sessionId: 0192f3a4-5b6c-7d8e-9f01-23456789c009
                    client: native
                    current: true
                    createdAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /me/devices/{sessionId}:
    delete:
      operationId: deleteAccountDevice
      tags:
        - account
      summary: Revoke one login
      description: Revoke one login.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/SessionUuid'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /me/api-keys:
    get:
      operationId: listAccountApiKeys
      tags:
        - api-keys
      summary: List personal API keys
      description: List personal API keys.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/cloudKeyPage'
              example:
                items:
                  - id: 4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e
                    kind: user
                    name: CI
                    scopes:
                      - catalogue:read
                    prefix: fdk_4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e
                    createdAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createAccountApiKey
      tags:
        - api-keys
      summary: Create a personal API key (token shown once)
      description: Create a personal API key (token shown once)
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cloudKeyInput'
            example:
              name: CI
              scopes:
                - catalogue:read
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/cloudKeyCreated'
              example:
                key:
                  id: 4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e
                  kind: user
                  name: CI
                  scopes:
                    - catalogue:read
                  prefix: fdk_4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e
                  createdAt: '2026-10-19T12:00:00.000Z'
                token: fdk_4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE0
        '401':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /me/api-keys/{keyId}:
    delete:
      operationId: deleteAccountApiKey
      tags:
        - api-keys
      summary: Revoke a personal API key
      description: Revoke a personal API key.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/CloudKeyId'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs:
    get:
      operationId: listOrgs
      tags:
        - orgs
      summary: Organisations of the caller
      description: Organisations of the caller.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/orgPage'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                    name: Dome Studio
                    kind: studio
                    country: RU
                    myRole: owner
                    createdAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createOrg
      tags:
        - orgs
      summary: Create an organisation (caller = owner)
      description: Create an organisation (caller = owner)
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/orgInput'
            example:
              name: Dome Studio
              kind: studio
              country: RU
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/org'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                name: Dome Studio
                kind: studio
                country: RU
                myRole: owner
                createdAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}:
    get:
      operationId: getOrg
      tags:
        - orgs
      summary: Get an organisation (non-members → 404)
      description: Get an organisation (non-members → 404)
      security:
        - bearer:
            - org:read
        - apiKey:
            - org:read
      x-scope: org:read
      parameters:
        - $ref: '#/components/parameters/OrgId'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/org'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                name: Dome Studio
                kind: studio
                country: RU
                myRole: owner
                createdAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchOrg
      tags:
        - orgs
      summary: Merge-patch an organisation
      description: Merge-patch an organisation.
      security:
        - bearer:
            - org:write
        - apiKey:
            - org:write
      x-scope: org:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/orgPatch'
            example:
              name: Dome Studio LLC
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/org'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                name: Dome Studio
                kind: studio
                country: RU
                myRole: owner
                createdAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteOrg
      tags:
        - orgs
      summary: Delete an organisation (owner)
      description: Delete an organisation (owner)
      security:
        - bearer:
            - org:owner
        - apiKey:
            - org:owner
      x-scope: org:owner
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/members:
    get:
      operationId: listOrgMembers
      tags:
        - orgs
      summary: Members
      description: Members.
      security:
        - bearer:
            - org:read
        - apiKey:
            - org:read
      x-scope: org:read
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/memberPage'
              example:
                items:
                  - userId: 0192f3a4-5b6c-7d8e-9f01-23456789c001
                    email: anna@example.org
                    role: owner
                    joinedAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/members/{userId}:
    patch:
      operationId: patchOrgMember
      tags:
        - orgs
      summary: Change a member role (only owners grant owner)
      description: Change a member role (only owners grant owner)
      security:
        - bearer:
            - members:write
        - apiKey:
            - members:write
      x-scope: members:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/MemberId'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/memberPatch'
            example:
              role: admin
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/member'
              example:
                userId: 0192f3a4-5b6c-7d8e-9f01-23456789c001
                email: anna@example.org
                role: admin
                joinedAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: removeOrgMember
      tags:
        - orgs
      summary: Remove a member / leave (at least one owner stays)
      description: Remove a member / leave (at least one owner stays)
      security:
        - bearer:
            - members:write
        - apiKey:
            - members:write
      x-scope: members:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/MemberId'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/invitations:
    get:
      operationId: listOrgInvitations
      tags:
        - orgs
      summary: Pending invitations
      description: Pending invitations.
      security:
        - bearer:
            - members:write
        - apiKey:
            - members:write
      x-scope: members:write
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/invitationPage'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789c006
                    email: ivan@example.org
                    role: operator
                    expiresAt: '2026-10-26T12:00:00.000Z'
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createOrgInvitation
      tags:
        - orgs
      summary: Invite by e-mail (token shown once, 7 days)
      description: Invite by e-mail (token shown once, 7 days)
      security:
        - bearer:
            - members:write
        - apiKey:
            - members:write
      x-scope: members:write
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/invitationInput'
            example:
              email: ivan@example.org
              role: operator
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/invitation'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c006
                email: ivan@example.org
                role: operator
                expiresAt: '2026-10-26T12:00:00.000Z'
                token: example-invitation-token-not-a-secret
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/invitations/{invitationId}:
    delete:
      operationId: revokeOrgInvitation
      tags:
        - orgs
      summary: Revoke an invitation
      description: Revoke an invitation.
      security:
        - bearer:
            - members:write
        - apiKey:
            - members:write
      x-scope: members:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/InvitationId'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /invitations/accept:
    post:
      operationId: acceptInvitation
      tags:
        - orgs
      summary: Accept an invitation (only by the invited address)
      description: Accept an invitation (only by the invited address)
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/token'
            example:
              token: example-invitation-token-not-a-secret
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/org'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                name: Dome Studio
                kind: studio
                country: RU
                myRole: owner
                createdAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/api-keys:
    get:
      operationId: listOrgApiKeys
      tags:
        - api-keys
      summary: Organisation API keys
      description: Organisation API keys.
      security:
        - bearer:
            - keys:write
        - apiKey:
            - keys:write
      x-scope: keys:write
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/cloudKeyPage'
              example:
                items:
                  - id: 4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e
                    kind: org
                    name: CI
                    scopes:
                      - catalogue:read
                    prefix: fdk_4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e
                    createdAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createOrgApiKey
      tags:
        - api-keys
      summary: Create an organisation API key (token shown once)
      description: Create an organisation API key (token shown once)
      security:
        - bearer:
            - keys:write
        - apiKey:
            - keys:write
      x-scope: keys:write
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cloudKeyInput'
            example:
              name: CI
              scopes:
                - catalogue:read
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/cloudKeyCreated'
              example:
                key:
                  id: 4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e
                  kind: org
                  name: CI
                  scopes:
                    - catalogue:read
                  prefix: fdk_4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e
                  createdAt: '2026-10-19T12:00:00.000Z'
                token: fdk_4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE0
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/api-keys/{keyId}:
    delete:
      operationId: deleteOrgApiKey
      tags:
        - api-keys
      summary: Revoke an organisation API key
      description: Revoke an organisation API key.
      security:
        - bearer:
            - keys:write
        - apiKey:
            - keys:write
      x-scope: keys:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudKeyId'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/venues:
    get:
      operationId: listOrgVenues
      tags:
        - registry
      summary: Venues of the organisation
      description: Venues of the organisation.
      security:
        - bearer:
            - registry:read
        - apiKey:
            - registry:read
      x-scope: registry:read
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venuePage'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789c003
                    orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                    name: Planetarium
                    timezone: Europe/Moscow
                    public: true
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createOrgVenue
      tags:
        - registry
      summary: Register a venue
      description: Register a venue.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/venueInput'
            example:
              name: Planetarium
              timezone: Europe/Moscow
              public: true
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venue'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c003
                orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                name: Planetarium
                timezone: Europe/Moscow
                public: true
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/venues/{venueId}:
    get:
      operationId: getOrgVenue
      tags:
        - registry
      summary: Get a venue
      description: Get a venue.
      security:
        - bearer:
            - registry:read
        - apiKey:
            - registry:read
      x-scope: registry:read
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudVenueId'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venue'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c003
                orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                name: Planetarium
                timezone: Europe/Moscow
                public: true
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchOrgVenue
      tags:
        - registry
      summary: Merge-patch a venue
      description: Merge-patch a venue.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudVenueId'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/venueInput'
            example:
              name: Planetarium
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/venue'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c003
                orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                name: Planetarium
                timezone: Europe/Moscow
                public: true
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteOrgVenue
      tags:
        - registry
      summary: Delete a venue
      description: Delete a venue.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudVenueId'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/venues/{venueId}/domes:
    post:
      operationId: createOrgDome
      tags:
        - registry
      summary: Register a dome in a venue
      description: Register a dome in a venue.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudVenueId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/domeInput'
            example:
              name: Main dome
              kind: classical
              diameterM: 8
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/dome'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c004
                venueId: 0192f3a4-5b6c-7d8e-9f01-23456789c003
                name: Main dome
                kind: classical
                diameterM: 8
                projectorCount: 2
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/domes/{domeId}:
    get:
      operationId: getOrgDome
      tags:
        - registry
      summary: Get a dome
      description: Get a dome.
      security:
        - bearer:
            - registry:read
        - apiKey:
            - registry:read
      x-scope: registry:read
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/DomeId'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/dome'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c004
                venueId: 0192f3a4-5b6c-7d8e-9f01-23456789c003
                name: Main dome
                kind: classical
                diameterM: 8
                projectorCount: 2
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    patch:
      operationId: patchOrgDome
      tags:
        - registry
      summary: Merge-patch a dome
      description: Merge-patch a dome.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/DomeId'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/domeInput'
            example:
              name: Main dome
              kind: classical
              tiltDeg: 15
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/dome'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c004
                venueId: 0192f3a4-5b6c-7d8e-9f01-23456789c003
                name: Main dome
                kind: classical
                diameterM: 8
                projectorCount: 2
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: deleteOrgDome
      tags:
        - registry
      summary: Delete a dome
      description: Delete a dome.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/DomeId'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/installations:
    get:
      operationId: listOrgInstallations
      tags:
        - registry
      summary: Installations (media servers) of the organisation
      description: Installations (media servers) of the organisation.
      security:
        - bearer:
            - registry:read
        - apiKey:
            - registry:read
      x-scope: registry:read
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/installationPage'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789c005
                    orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                    name: Dome 8 m
                    platform: linux
                    arch: x86_64
                    mediaserverVersion: 1.0.0
                    status: active
                    telemetryLevel: basic
                    enrolledAt: '2026-10-19T12:00:00.000Z'
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/installations/{installationId}:
    patch:
      operationId: patchOrgInstallation
      tags:
        - registry
      summary: Rename / bind to a dome / telemetry level
      description: Rename / bind to a dome / telemetry level.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/InstallationId'
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: '#/components/schemas/installationPatch'
            example:
              telemetryLevel: full
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/installation'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c005
                orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                name: Dome 8 m
                platform: linux
                arch: x86_64
                mediaserverVersion: 1.0.0
                status: active
                telemetryLevel: basic
                enrolledAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    delete:
      operationId: revokeOrgInstallation
      tags:
        - registry
      summary: Revoke an installation (its fdi_ key stops working)
      description: Revoke an installation (its fdi_ key stops working)
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/InstallationId'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/enrollment-tokens:
    get:
      operationId: listEnrollmentTokens
      tags:
        - registry
      summary: Enrollment tokens (headless enrollment)
      description: Enrollment tokens (headless enrollment)
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/enrollmentTokenPage'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789c007
                    name: Bench
                    expiresAt: '2026-10-20T12:00:00.000Z'
                    usedAt: null
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
    post:
      operationId: createEnrollmentToken
      tags:
        - registry
      summary: Create an enrollment token (shown once)
      description: Create an enrollment token (shown once)
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/enrollmentTokenInput'
            example:
              name: Bench
              ttlHours: 24
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/enrollmentToken'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c007
                name: Bench
                expiresAt: '2026-10-20T12:00:00.000Z'
                usedAt: null
                token: example-enrollment-token-not-a-secret
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /orgs/{orgId}/enrollment-tokens/{tokenId}:
    delete:
      operationId: revokeEnrollmentToken
      tags:
        - registry
      summary: Revoke an enrollment token
      description: Revoke an enrollment token.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      x-concurrency: none
      parameters:
        - $ref: '#/components/parameters/OrgId'
        - $ref: '#/components/parameters/EnrollmentTokenId'
      responses:
        '204':
          $ref: '#/components/responses/NoContent'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /installations/enroll-requests:
    post:
      operationId: createEnrollmentRequest
      tags:
        - enrollment
      summary: Media server asks to enroll (RFC 8628 device code)
      description: Media server asks to enroll (RFC 8628 device code)
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/enrollRequest'
            example:
              fingerprintHash: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
              publicKey: 11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo
              name: Dome 8 m
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/deviceCode'
              example:
                deviceCode: example-device-code-not-a-secret
                userCode: WDJB-MJHT
                verificationUri: https://freedome.nativemind.us/app/enroll
                expiresIn: 900
                interval: 5
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /installations/enroll-requests/token:
    post:
      operationId: pollEnrollment
      tags:
        - enrollment
      summary: Media server polls with its device code (authorization_pending until approved)
      description: Media server polls with its device code (authorization_pending until approved)
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/deviceTokenRequest'
            example:
              deviceCode: example-device-code-not-a-secret
              grantType: urn:ietf:params:oauth:grant-type:device_code
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/installationCredentials'
              example:
                installationId: 0192f3a4-5b6c-7d8e-9f01-23456789c005
                apiKey: fdi_4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE0
                jwksUri: https://freedome.nativemind.us/api/v1/.well-known/jwks.json
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /installations/enroll-requests/{userCode}:
    get:
      operationId: getEnrollmentRequest
      tags:
        - enrollment
      summary: Show a pending request to the approving user
      description: Show a pending request to the approving user.
      security:
        - bearer: []
        - apiKey: []
      x-scope: any
      parameters:
        - $ref: '#/components/parameters/UserCode'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/enrollmentState'
              example:
                userCode: WDJB-MJHT
                status: pending
                name: Dome 8 m
                expiresAt: '2026-10-19T12:15:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /installations/enroll-requests/{userCode}/approve:
    post:
      operationId: approveEnrollmentRequest
      tags:
        - enrollment
      summary: Approve into an organisation
      description: Approve into an organisation.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      parameters:
        - $ref: '#/components/parameters/UserCode'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/enrollmentApproval'
            example:
              orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
              name: Dome 8 m
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/enrollmentState'
              example:
                userCode: WDJB-MJHT
                status: approved
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /installations/enroll-requests/{userCode}/deny:
    post:
      operationId: denyEnrollmentRequest
      tags:
        - enrollment
      summary: Deny a request
      description: Deny a request.
      security:
        - bearer:
            - registry:write
        - apiKey:
            - registry:write
      x-scope: registry:write
      parameters:
        - $ref: '#/components/parameters/UserCode'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/enrollmentDenial'
            example:
              orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
              reason: unknown device
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/enrollmentState'
              example:
                userCode: WDJB-MJHT
                status: denied
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /installations/enroll:
    post:
      operationId: enrollWithToken
      tags:
        - enrollment
      summary: Headless enrollment with an enrollment token
      description: Headless enrollment with an enrollment token.
      security:
        - {}
      x-scope: anon
      x-idempotency: none
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/enrollWithToken'
            example:
              token: example-enrollment-token-not-a-secret
              fingerprintHash: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
              publicKey: 11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo
      responses:
        '201':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/installationCredentials'
              example:
                installationId: 0192f3a4-5b6c-7d8e-9f01-23456789c005
                apiKey: fdi_4f1c2a9e0b7d4c3a8e6f5d2c1b0a9f8e_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE0
        '422':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /installations/self:
    get:
      operationId: getOwnInstallation
      tags:
        - enrollment
      summary: The calling installation (fdi_ key)
      description: The calling installation (fdi_ key)
      security:
        - bearer:
            - registry:self
        - apiKey:
            - registry:self
      x-scope: registry:self
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/installation'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c005
                orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                name: Dome 8 m
                platform: linux
                arch: x86_64
                mediaserverVersion: 1.0.0
                status: active
                telemetryLevel: basic
                enrolledAt: '2026-10-19T12:00:00.000Z'
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /catalogue/items:
    get:
      operationId: listCatalogueItems
      tags:
        - catalogue
      summary: Content catalogue (planned, P6)
      description: Content catalogue (planned, P6)
      security:
        - bearer:
            - catalogue:read
        - apiKey:
            - catalogue:read
      x-scope: catalogue:read
      x-status: planned
      parameters:
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/catalogueItemPage'
              example:
                items:
                  - id: 0192f3a4-5b6c-7d8e-9f01-23456789c008
                    orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                    slug: intro-show
                    kind: show
                    title:
                      en: Intro show
                    visibility: public
                    status: published
                    priceKind: free
                    latestVersion: 1.0.0
                nextCursor: null
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /catalogue/items/{itemId}:
    get:
      operationId: getCatalogueItem
      tags:
        - catalogue
      summary: Catalogue item (planned, P6)
      description: Catalogue item (planned, P6)
      security:
        - bearer:
            - catalogue:read
        - apiKey:
            - catalogue:read
      x-scope: catalogue:read
      x-status: planned
      parameters:
        - $ref: '#/components/parameters/ItemId'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/catalogueItem'
              example:
                id: 0192f3a4-5b6c-7d8e-9f01-23456789c008
                orgId: 0192f3a4-5b6c-7d8e-9f01-23456789c002
                slug: intro-show
                kind: show
                title:
                  en: Intro show
                visibility: public
                status: published
                priceKind: free
                latestVersion: 1.0.0
        '401':
          $ref: '#/components/responses/Problem'
        '403':
          $ref: '#/components/responses/Problem'
        '404':
          $ref: '#/components/responses/Problem'
        default:
          $ref: '#/components/responses/Problem'
  /apps:
    get:
      operationId: listApps
      tags:
        - apps
      summary: Ecosystem app registry (planned, P6)
      description: Ecosystem app registry (planned, P6)
      security:
        - {}
      x-scope: anon
      x-status: planned
      parameters:
        - $ref: '#/components/parameters/CloudLimit'
        - $ref: '#/components/parameters/Cursor'
      responses:
        '200':
          description: OK.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/appPage'
              example:
                items:
                  - id: net.nativemind.starveda
                    slug: starveda
                    name: Starveda
                    platforms:
                      - android
                      - ios
                nextCursor: null
        default:
          $ref: '#/components/responses/Problem'
components:
  parameters:
    Provider:
      name: provider
      in: path
      required: true
      description: Identity provider.
      schema:
        enum:
          - apple
          - google
    SessionUuid:
      name: sessionId
      in: path
      required: true
      description: Login (refresh family) id.
      schema:
        type: string
        format: uuid
    CloudKeyId:
      name: keyId
      in: path
      required: true
      description: API key id (32 hex).
      schema:
        type: string
        pattern: ^[0-9a-f]{32}$
    OrgId:
      name: orgId
      in: path
      required: true
      description: Organisation id.
      schema:
        type: string
        format: uuid
    MemberId:
      name: userId
      in: path
      required: true
      description: Member user id.
      schema:
        type: string
        format: uuid
    InvitationId:
      name: invitationId
      in: path
      required: true
      description: Invitation id.
      schema:
        type: string
        format: uuid
    CloudVenueId:
      name: venueId
      in: path
      required: true
      description: Registry venue id.
      schema:
        type: string
        format: uuid
    DomeId:
      name: domeId
      in: path
      required: true
      description: Dome id.
      schema:
        type: string
        format: uuid
    InstallationId:
      name: installationId
      in: path
      required: true
      description: Installation id.
      schema:
        type: string
        format: uuid
    EnrollmentTokenId:
      name: tokenId
      in: path
      required: true
      description: Enrollment token id.
      schema:
        type: string
        format: uuid
    UserCode:
      name: userCode
      in: path
      required: true
      description: RFC 8628 user code shown on the media server.
      schema:
        type: string
        pattern: ^[A-Z0-9]{4}-?[A-Z0-9]{4}$
    ItemId:
      name: itemId
      in: path
      required: true
      description: Catalogue item id.
      schema:
        type: string
        format: uuid
    CloudLimit:
      name: limit
      in: query
      description: Page size (default 20, max 100 on the cloud server, 02h §3.4).
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    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
  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).'
  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
    meta:
      type: object
      required:
        - serverVersion
      properties:
        serverVersion:
          type: string
        contractVersion:
          type: string
        schemaVersions:
          type: object
        channels:
          type: object
      patternProperties:
        ^x-: true
      additionalProperties: false
    jwks:
      type: object
      required:
        - keys
      properties:
        keys:
          type: array
          items:
            type: object
            required:
              - kty
              - kid
            properties:
              kty:
                type: string
              kid:
                type: string
              crv:
                type: string
              x:
                type: string
              'y':
                type: string
              alg:
                type: string
              use:
                type: string
            additionalProperties: true
      additionalProperties: false
    signUp:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 10
          maxLength: 1024
          writeOnly: true
        displayName:
          type: string
          maxLength: 128
        locale:
          type: string
        client:
          enum:
            - web
            - native
      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}$
    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$
    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
    session:
      description: Session tokens (same shape as the media server, api/1.0/auth tokens) + created / linked for OAuth.
      type: object
      allOf:
        - $ref: '#/components/schemas/tokens'
    signIn:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 1
          maxLength: 1024
          writeOnly: true
        client:
          enum:
            - web
            - native
      additionalProperties: false
    refresh:
      type: object
      required: []
      properties:
        refreshToken:
          type: string
          minLength: 16
          description: native clients; web clients send the fd_rt cookie
      additionalProperties: false
    token:
      type: object
      required:
        - token
      properties:
        token:
          type: string
          minLength: 16
      additionalProperties: false
    accepted:
      type: object
      required:
        - status
      properties:
        status:
          type: string
      additionalProperties: false
    email:
      type: object
      required:
        - email
      properties:
        email:
          type: string
          format: email
      additionalProperties: false
    passwordReset:
      type: object
      required:
        - token
        - password
      properties:
        token:
          type: string
          minLength: 16
        password:
          type: string
          minLength: 10
          writeOnly: true
      additionalProperties: false
    passwordChange:
      type: object
      required:
        - currentPassword
        - password
      properties:
        currentPassword:
          type: string
          writeOnly: true
        password:
          type: string
          minLength: 10
          writeOnly: true
      additionalProperties: false
    oauth:
      type: object
      required:
        - idToken
      properties:
        idToken:
          type: string
          minLength: 16
        nonce:
          type: string
        displayName:
          type: string
        client:
          enum:
            - web
            - native
      additionalProperties: false
    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}$
    membership:
      type: object
      required:
        - orgId
        - role
      properties:
        orgId:
          $ref: '#/components/schemas/uuid'
        orgName:
          type: string
        role:
          anyOf:
            - enum:
                - owner
                - admin
                - operator
                - author
                - viewer
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
      additionalProperties: false
    account:
      type: object
      required:
        - id
        - email
        - emailVerified
        - platformRole
        - status
        - createdAt
      properties:
        id:
          $ref: '#/components/schemas/uuid'
        email:
          type: string
          format: email
        emailVerified:
          type: boolean
        displayName:
          type: string
          maxLength: 128
        locale:
          type: string
          maxLength: 35
        platformRole:
          anyOf:
            - enum:
                - none
                - support
                - admin
                - owner
            - type: string
        status:
          anyOf:
            - enum:
                - active
                - locked
                - deleting
            - type: string
        hardDeleteAfter:
          $ref: '#/components/schemas/timestamp'
        memberships:
          type: array
          items:
            $ref: '#/components/schemas/membership'
        createdAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    accountPatch:
      type: object
      minProperties: 1
      properties:
        displayName:
          type: string
          maxLength: 128
        locale:
          type: string
          maxLength: 35
      additionalProperties: false
    cursor:
      description: Opaque continuation token.
      type: string
      minLength: 1
      maxLength: 1024
    loginDevice:
      type: object
      required:
        - sessionId
        - client
        - createdAt
      properties:
        sessionId:
          $ref: '#/components/schemas/uuid'
        client:
          type: string
        userAgent:
          type: string
        current:
          type: boolean
        createdAt:
          $ref: '#/components/schemas/timestamp'
        lastUsedAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    loginDevicePage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/loginDevice'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    cloudKey:
      type: object
      required:
        - id
        - kind
        - name
        - scopes
        - prefix
        - createdAt
      properties:
        id:
          type: string
          pattern: ^[0-9a-f]{32}$
        kind:
          enum:
            - user
            - org
            - installation
            - service
        name:
          type: string
        scopes:
          type: array
          items:
            $ref: '#/components/schemas/scope'
        prefix:
          type: string
          pattern: ^fd[kis]_[0-9a-f]{32}$
        expiresAt:
          anyOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
        lastUsedAt:
          anyOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
        createdAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    cloudKeyPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/cloudKey'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    cloudKeyInput:
      type: object
      required:
        - name
        - scopes
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 128
        scopes:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/scope'
        expiresAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    cloudKeyCreated:
      type: object
      required:
        - key
        - token
      properties:
        key:
          $ref: '#/components/schemas/cloudKey'
        token:
          type: string
          pattern: ^fd[kis]_[0-9a-f]{32}_[A-Za-z0-9_-]{43}$
      additionalProperties: false
    org:
      type: object
      required:
        - id
        - name
        - kind
        - createdAt
      properties:
        id:
          $ref: '#/components/schemas/uuid'
        name:
          type: string
          minLength: 1
          maxLength: 128
        kind:
          anyOf:
            - enum:
                - venue_operator
                - studio
                - school
                - partner
                - vendor
            - type: string
        country:
          type: string
          pattern: ^[A-Z]{2}$
        contactEmail:
          type: string
          format: email
        myRole:
          anyOf:
            - enum:
                - owner
                - admin
                - operator
                - author
                - viewer
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        createdAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    orgPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/org'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    orgInput:
      type: object
      required:
        - name
        - kind
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 128
        kind:
          type: string
        country:
          type: string
          pattern: ^[A-Z]{2}$
        contactEmail:
          type: string
          format: email
      additionalProperties: false
    orgPatch:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 128
        country:
          type: string
          pattern: ^[A-Z]{2}$
        contactEmail:
          type: string
          format: email
      additionalProperties: false
    member:
      type: object
      required:
        - userId
        - role
        - joinedAt
      properties:
        userId:
          $ref: '#/components/schemas/uuid'
        email:
          type: string
          format: email
        displayName:
          type: string
        role:
          anyOf:
            - enum:
                - owner
                - admin
                - operator
                - author
                - viewer
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        joinedAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    memberPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/member'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    memberPatch:
      type: object
      required:
        - role
      properties:
        role:
          anyOf:
            - enum:
                - owner
                - admin
                - operator
                - author
                - viewer
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
      additionalProperties: false
    invitation:
      type: object
      required:
        - id
        - email
        - role
        - expiresAt
      properties:
        id:
          $ref: '#/components/schemas/uuid'
        email:
          type: string
          format: email
        role:
          anyOf:
            - enum:
                - owner
                - admin
                - operator
                - author
                - viewer
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
        expiresAt:
          $ref: '#/components/schemas/timestamp'
        token:
          type: string
          description: returned once on creation
      additionalProperties: false
    invitationPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/invitation'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    invitationInput:
      type: object
      required:
        - email
        - role
      properties:
        email:
          type: string
          format: email
        role:
          anyOf:
            - enum:
                - owner
                - admin
                - operator
                - author
                - viewer
            - type: string
              pattern: ^[a-z][a-z_-]{0,31}$
      additionalProperties: false
    venue:
      type: object
      required:
        - id
        - orgId
        - name
      properties:
        id:
          $ref: '#/components/schemas/uuid'
        orgId:
          $ref: '#/components/schemas/uuid'
        name:
          type: string
        address:
          type: string
        lat:
          type: number
          minimum: -90
          maximum: 90
        lon:
          type: number
          minimum: -180
          maximum: 180
        timezone:
          type: string
        public:
          type: boolean
      patternProperties:
        ^x-: true
      additionalProperties: false
    venuePage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/venue'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    venueInput:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        address:
          type: string
        lat:
          type: number
          minimum: -90
          maximum: 90
        lon:
          type: number
          minimum: -180
          maximum: 180
        timezone:
          type: string
        public:
          type: boolean
      additionalProperties: false
    domeInput:
      type: object
      required:
        - name
        - kind
      properties:
        name:
          type: string
        kind:
          anyOf:
            - enum:
                - classical
                - quantum
                - smartphone
            - type: string
        diameterM:
          type: number
          exclusiveMinimum: 0
        tiltDeg:
          type: number
        projectorCount:
          type: integer
          minimum: 0
        audioLayout:
          type: string
        visualLineVariant:
          type: string
        summary:
          type: object
          description: summary of the .dome venue manifest
      additionalProperties: false
    dome:
      type: object
      required:
        - id
        - venueId
        - name
        - kind
      properties:
        id:
          $ref: '#/components/schemas/uuid'
        venueId:
          $ref: '#/components/schemas/uuid'
        name:
          type: string
        kind:
          anyOf:
            - enum:
                - classical
                - quantum
                - smartphone
            - type: string
        diameterM:
          type: number
          exclusiveMinimum: 0
        tiltDeg:
          type: number
        projectorCount:
          type: integer
          minimum: 0
        audioLayout:
          type: string
        visualLineVariant:
          type: string
        summary:
          type: object
          description: summary of the .dome venue manifest
      patternProperties:
        ^x-: true
      additionalProperties: false
    installation:
      type: object
      required:
        - id
        - orgId
        - name
        - status
      properties:
        id:
          $ref: '#/components/schemas/uuid'
        orgId:
          $ref: '#/components/schemas/uuid'
        domeId:
          anyOf:
            - $ref: '#/components/schemas/uuid'
            - type: 'null'
        name:
          type: string
        platform:
          type: string
        os:
          type: string
        arch:
          type: string
        mediaserverVersion:
          type: string
        engineVersions:
          type: object
        contractVersions:
          type: object
        status:
          anyOf:
            - enum:
                - active
                - revoked
                - offline
            - type: string
        telemetryLevel:
          enum:
            - 'off'
            - basic
            - full
        enrolledAt:
          $ref: '#/components/schemas/timestamp'
        lastSeenAt:
          $ref: '#/components/schemas/timestamp'
      patternProperties:
        ^x-: true
      additionalProperties: false
    installationPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/installation'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    installationPatch:
      type: object
      minProperties: 1
      properties:
        name:
          type: string
        domeId:
          anyOf:
            - $ref: '#/components/schemas/uuid'
            - type: 'null'
        telemetryLevel:
          enum:
            - 'off'
            - basic
            - full
      additionalProperties: false
    enrollmentToken:
      type: object
      required:
        - id
        - expiresAt
      properties:
        id:
          $ref: '#/components/schemas/uuid'
        name:
          type: string
        token:
          type: string
          description: returned once on creation
        expiresAt:
          $ref: '#/components/schemas/timestamp'
        usedAt:
          anyOf:
            - $ref: '#/components/schemas/timestamp'
            - type: 'null'
      additionalProperties: false
    enrollmentTokenPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/enrollmentToken'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    enrollmentTokenInput:
      type: object
      required: []
      properties:
        name:
          type: string
        ttlHours:
          type: integer
          minimum: 1
          maximum: 720
      additionalProperties: false
    sha256:
      description: Lowercase hex SHA-256 digest.
      type: string
      pattern: ^[0-9a-f]{64}$
    enrollRequest:
      type: object
      required:
        - fingerprintHash
        - publicKey
      properties:
        fingerprintHash:
          $ref: '#/components/schemas/sha256'
        publicKey:
          type: string
          description: ed25519 public key, base64url (32 bytes)
        name:
          type: string
        versions:
          type: object
        platform:
          type: string
          maxLength: 64
        os:
          type: string
          maxLength: 64
        arch:
          type: string
          maxLength: 64
      additionalProperties: false
    deviceCode:
      description: RFC 8628 device authorization response.
      type: object
      required:
        - deviceCode
        - userCode
        - verificationUri
        - expiresIn
        - interval
      properties:
        deviceCode:
          type: string
        userCode:
          type: string
          pattern: ^[A-Z0-9]{4}-?[A-Z0-9]{4}$
        verificationUri:
          type: string
          format: uri
        verificationUriComplete:
          type: string
          format: uri
        expiresIn:
          type: integer
          minimum: 1
        interval:
          type: integer
          minimum: 1
      additionalProperties: false
    deviceTokenRequest:
      type: object
      required:
        - deviceCode
      properties:
        deviceCode:
          type: string
        grantType:
          const: urn:ietf:params:oauth:grant-type:device_code
      additionalProperties: false
    installationCredentials:
      type: object
      required:
        - installationId
        - apiKey
      properties:
        installationId:
          $ref: '#/components/schemas/uuid'
        apiKey:
          type: string
          pattern: ^fdi_[0-9a-f]{32}_[A-Za-z0-9_-]{43}$
        jwksUri:
          type: string
          format: uri
      additionalProperties: false
    enrollmentState:
      type: object
      required:
        - userCode
        - status
      properties:
        userCode:
          type: string
        status:
          anyOf:
            - enum:
                - pending
                - approved
                - denied
                - expired
            - type: string
        name:
          type: string
        versions:
          type: object
        expiresAt:
          $ref: '#/components/schemas/timestamp'
      additionalProperties: false
    enrollmentApproval:
      type: object
      required:
        - orgId
      properties:
        orgId:
          $ref: '#/components/schemas/uuid'
        domeId:
          $ref: '#/components/schemas/uuid'
        name:
          type: string
      additionalProperties: false
    enrollmentDenial:
      type: object
      properties:
        orgId:
          $ref: '#/components/schemas/uuid'
        reason:
          type: string
          maxLength: 256
      additionalProperties: false
    enrollWithToken:
      type: object
      required:
        - token
        - fingerprintHash
        - publicKey
      properties:
        token:
          type: string
        fingerprintHash:
          $ref: '#/components/schemas/sha256'
        publicKey:
          type: string
        name:
          type: string
        versions:
          type: object
        platform:
          type: string
          maxLength: 64
        os:
          type: string
          maxLength: 64
        arch:
          type: string
          maxLength: 64
      additionalProperties: false
    catalogueItem:
      type: object
      required:
        - id
        - orgId
        - slug
        - kind
        - title
        - visibility
        - status
      properties:
        id:
          $ref: '#/components/schemas/uuid'
        orgId:
          $ref: '#/components/schemas/uuid'
        slug:
          type: string
          pattern: ^[a-z0-9][a-z0-9-]{0,63}$
        kind:
          anyOf:
            - enum:
                - show
                - venue_preset
                - asset_pack
                - app_content
            - type: string
        title:
          $ref: '#/components/schemas/i18n-text.schema'
        description:
          $ref: '#/components/schemas/i18n-text.schema'
        tags:
          type: array
          items:
            type: string
        visibility:
          enum:
            - private
            - org
            - unlisted
            - public
        status:
          enum:
            - draft
            - review
            - published
            - withdrawn
        priceKind:
          enum:
            - free
            - entitled
        latestVersion:
          type: string
      patternProperties:
        ^x-: true
      additionalProperties: false
    catalogueItemPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/catalogueItem'
        nextCursor:
          anyOf:
            - type: string
            - type: 'null'
      additionalProperties: false
    app:
      type: object
      required:
        - id
        - slug
        - name
      properties:
        id:
          type: string
        slug:
          type: string
        name:
          type: string
        platforms:
          type: array
          items:
            type: string
        storeUrls:
          type: object
        privacyUrl:
          type: string
          format: uri
        termsUrl:
          type: string
          format: uri
        supportUrl:
          type: string
        deletionUrl:
          type: string
          format: uri
      patternProperties:
        ^x-: true
      additionalProperties: false
    appPage:
      type: object
      required:
        - items
        - nextCursor
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/app'
        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
    NoContent:
      description: Done, no body.
