# GENERATED by 'make openapi-bundle' from openapi/politburo/. Do not edit.
openapi: 3.0.3
info:
  title: Politburo API
  version: 0.1.0
  description: |
    Authoritative JSON contract for Politburo (Infinite Experiment backend).

    **Authentication**
    - `X-API-Key` — UUID API key for `/api/v1/*` machine clients (Discord bot, integrations).
    - `X-Discord-User-Id` and `X-Discord-Server-Id` — Discord context on bot-originated requests.
    - `session_id` cookie — browser session after signed-link login (`/api/v1/game/*`, `/api/v1/operator/*`, `/api/v1/user/me`, `/api/v1/oauth/infinite-flight`, `/api/v1/live/organizations`, `/oauth/callback`, `/api/v1/va`, `/api/v1/va/config`, `/api/v1/va/webhook/trigger`, and related routes).

    **Documentation** — https://sanketpandia.github.io/comrade-bot/api/reference/
servers:
  - url: https://comradebot.cc
    description: Production
  - url: http://localhost:8082
    description: Local dev (Politburo rewrite)
  - url: http://localhost:8080
    description: Local rewrite default
tags:
  - name: Health
    description: Process and dependency readiness.
  - name: Game
    description: Cached Infinite Flight live game data.
  - name: Auth
    description: Browser session and signed links.
  - name: Users
    description: Discord user registration and account flows.
  - name: Admin
    description: Platform operator (god mode) actions.
  - name: VirtualAirline
    description: VA administrator settings and metadata.
  - name: Live
    description: Infinite Flight Live API proxies for dashboard Live Mode.
paths:
  /health/status:
    get:
      operationId: getHealthStatus
      summary: Report process uptime and dependency readiness
      tags:
        - Health
      responses:
        '200':
          description: The process is up and required dependencies are reachable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
        '503':
          description: The process is up but a required dependency is unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
  /api/v1/game/sessions/active:
    get:
      operationId: getActiveSessions
      summary: Read the latest active Infinite Flight sessions from cache
      tags:
        - Game
      responses:
        '200':
          description: The latest cached active sessions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActiveSessionsResponse'
        '500':
          description: The cache entry could not be read
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: No active-session snapshot is currently cached
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/game/flights/active/{normalizedServerName}:
    get:
      operationId: getActiveFlights
      summary: Read cached active flight motion tracks for a server
      tags:
        - Game
      parameters:
        - name: normalizedServerName
          in: path
          required: true
          description: Normalized Infinite Flight session name (for example casual).
          schema:
            type: string
        - name: pageNumber
          in: query
          description: 1-based page of flight tracks to return.
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: pageLength
          in: query
          description: Number of flights per page (max 5000).
          schema:
            type: integer
            minimum: 1
            maximum: 5000
            default: 50
        - name: pilotState
          in: query
          style: form
          explode: true
          description: Restrict results to one or more pilot states.
          schema:
            type: array
            items:
              $ref: '#/components/schemas/PilotStateName'
        - name: userName
          in: query
          description: Restrict results to flights whose username contains this value.
          schema:
            type: string
        - name: callSign
          in: query
          description: Restrict results to flights whose callsign contains this value.
          schema:
            type: string
        - name: vaCode
          in: query
          description: |
            Restrict results to flights matched to this active virtual airline code (case-insensitive).
            Resolved from the cached VA code index and per-VA active-flight index written by the
            `infinite-flight-va-flight-index` job; the database is never queried.
          schema:
            type: string
      responses:
        '200':
          description: The latest cached motion tracks for active flights on the server
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActiveFlightsResponse'
        '400':
          description: An invalid query value was supplied, or `vaCode` is not an active virtual airline
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: The cache entry could not be read
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: No active-flight snapshot is currently cached for the server, or the VA flight index is not cached yet when `vaCode` is set
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/game/flight/active/{flightId}:
    get:
      operationId: getActiveFlight
      summary: Read cached enriched record for one active flight
      tags:
        - Game
      parameters:
        - name: flightId
          in: path
          required: true
          description: Infinite Flight flight identifier (UUID).
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: The latest cached enriched flight record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActiveFlightRecordResponse'
        '400':
          description: An invalid flight identifier was supplied
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No cached record exists for the flight
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: The cache entry could not be read
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/signed-link:
    post:
      operationId: generateSignedLink
      summary: Create a one-time signed URL that mints a browser session
      tags:
        - Auth
      description: |
        Discord bots with a valid API key and X-Discord-User-Id receive a
        10-minute URL. Visiting it creates a session cookie and redirects
        to the path for the requested type (DASHBOARD, LIVE_MAP, or CUSTOM
        with redirectTo). X-Discord-Server-Id is stored on the session when
        present; membership is enforced when the guild is a configured VA.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: X-Discord-User-Id
          in: header
          required: false
          description: Discord user id of the member to sign in.
          schema:
            type: string
        - name: X-Discord-Server-Id
          in: header
          required: false
          description: Optional Discord guild id stored on the session.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateSignedLinkRequest'
      responses:
        '200':
          description: Signed link generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenerateSignedLinkResponse'
        '400':
          description: Invalid redirect path or request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or inactive API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Missing X-Discord-User-Id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No user exists for the Discord user id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Ticket generation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/live/organizations:
    get:
      operationId: listLiveOrganizations
      summary: List Infinite Flight Live organizations for the current user
      description: |
        Proxies Infinite Flight Public API v3 `GET /live/organizations` using the caller's
        stored Infinite Flight OAuth access token (from Admin Live Mode connect). Returns
        organizations where the caller is Admin or Owner only.

        Requires VA administrator role (`administrator`). Does not persist organization data.
      tags:
        - Live
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      responses:
        '200':
          description: Organizations the caller can administer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LiveOrganizationsResponse'
        '401':
          description: Missing session or API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Caller is not a VA administrator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Infinite Flight OAuth is required or the access token expired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: Infinite Flight Live API request failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/oauth/infinite-flight:
    get:
      operationId: getInfiniteFlightOAuthAuthorize
      summary: Start Infinite Flight OAuth for the current user
      description: |
        Returns the Infinite Flight authorization URL for the current user. The Politburo
        user id is resolved the same way as `GET /api/v1/user/me` (session cookie, or API
        key plus `X-Discord-User-Id`). Politburo stores short-lived PKCE state in Redis;
        the user completes consent at Infinite Flight and returns via `GET /oauth/callback`.

        Requires Infinite Flight OAuth client credentials on the server.

        Use query `scope_set=admin_scopes` for the VA administrator Live Mode grant (requires virtual airline context on the caller).
      tags:
        - Auth
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      parameters:
        - name: scope_set
          in: query
          required: false
          description: |
            OAuth scope bundle. `admin_scopes` requests the VA staff grant including refresh and schedule write.
            Omitted uses read-only user scopes.
          schema:
            type: string
            enum:
              - admin_scopes
      responses:
        '200':
          description: Authorize URL for the browser
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InfiniteFlightOAuthAuthorizeResponse'
        '400':
          description: Invalid scope set or missing VA context for admin scopes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing session or Discord user id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: User record not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Could not start OAuth
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Infinite Flight OAuth is not configured on this deployment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /oauth/callback:
    get:
      operationId: completeInfiniteFlightOAuthCallback
      summary: Infinite Flight OAuth redirect callback
      description: |
        Browser return target registered with the Infinite Flight OAuth client
        (`{UI_BASE_URL}/oauth/callback`). Exchanges the authorization code for tokens,
        stores them in Redis for the Politburo user id stored in pending OAuth state, and redirects
        to the dashboard. A `session_id` cookie is optional; when present it must match the pending user.

        This path is not under `/api/v1` so the redirect URI matches Infinite Flight app settings.
      tags:
        - Auth
      security: []
      parameters:
        - name: code
          in: query
          required: true
          description: Authorization code from Infinite Flight.
          schema:
            type: string
        - name: state
          in: query
          required: true
          description: Opaque value from the authorize request; must match pending Redis state.
          schema:
            type: string
      responses:
        '303':
          description: Redirect to the dashboard (`oauth=connected` or `oauth=error` query flag).
          headers:
            Location:
              schema:
                type: string
                example: /dashboard?oauth=connected
  /api/v1/user/me:
    get:
      operationId: getCurrentUser
      summary: Read the authenticated user's profile and VA role
      description: |
        Returns Politburo user fields for the caller. The `role` field reflects VA membership in the
        current context (browser session snapshot or live resolution for bot requests with Discord headers).
        `role` is null when the user has no VA role in that context.
      tags:
        - Users
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      responses:
        '200':
          description: Current user profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CurrentUserResponse'
        '401':
          description: Missing or inactive API key or session cookie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: User record not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Failed to load user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/users:
    post:
      operationId: createUser
      summary: Register a Discord user with IFC identity and logbook proof
      description: |
        Creates a global Politburo user for the Discord member in X-Discord-User-Id.
        Requires X-Discord-Server-Id (bot context). Verifies the IFC username resolves
        upstream and that logbookEntry matches the latest complete online logbook flight.

        Error responses use `error.code` from the CreateUserErrorCode enum (see response examples).
      tags:
        - Users
      security:
        - ApiKeyAuth: []
      parameters:
        - name: X-Discord-User-Id
          in: header
          required: true
          description: Discord user id of the member registering.
          schema:
            type: string
        - name: X-Discord-Server-Id
          in: header
          required: true
          description: Discord guild id for bot context and VA registration hint.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '200':
          description: User registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUserResponse'
        '400':
          description: Invalid request body or flight proof failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUserErrorResponse'
              examples:
                invalidRequest:
                  summary: Invalid request body
                  value:
                    error:
                      code: INVALID_REQUEST
                      message: discourseNames must contain exactly one IFC username
                flightProofFailed:
                  summary: Logbook proof mismatch
                  value:
                    error:
                      code: FLIGHT_PROOF_FAILED
                      message: Flight validation failed
                registrationFailed:
                  summary: Unclassified registration failure
                  value:
                    error:
                      code: REGISTRATION_FAILED
                      message: registration failed
        '401':
          description: Missing or inactive API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUserErrorResponse'
              examples:
                unauthorized:
                  value:
                    error:
                      code: UNAUTHORIZED
                      message: Unauthorized
        '403':
          description: Banned Discord account or missing Discord context headers
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUserErrorResponse'
              examples:
                banned:
                  summary: Discord account is banned
                  value:
                    error:
                      code: BANNED
                      message: This Discord account cannot register
                missingDiscordContext:
                  summary: Bot omitted required Discord headers
                  value:
                    error:
                      code: MISSING_DISCORD_CONTEXT
                      message: 'Missing required Discord context headers: X-Discord-User-Id and X-Discord-Server-Id'
        '404':
          description: IFC user not found upstream
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUserErrorResponse'
              examples:
                ifUserNotFound:
                  value:
                    error:
                      code: IF_USER_NOT_FOUND
                      message: IFC user not found
        '409':
          description: Discord user or IFC username already registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUserErrorResponse'
              examples:
                userAlreadyRegistered:
                  value:
                    error:
                      code: USER_ALREADY_REGISTERED
                      message: User already registered
                ifcAlreadyLinked:
                  value:
                    error:
                      code: IFC_ALREADY_LINKED
                      message: IFC ID is already registered to another Discord account
        '500':
          description: Authentication lookup or internal registration failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUserErrorResponse'
              examples:
                authLookupFailed:
                  value:
                    error:
                      code: AUTH_LOOKUP_FAILED
                      message: authentication lookup failed
        '503':
          description: Infinite Flight API unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateUserErrorResponse'
              examples:
                ifUnavailable:
                  value:
                    error:
                      code: IF_UNAVAILABLE
                      message: Infinite Flight API is not configured
  /api/v1/servers:
    post:
      operationId: createServer
      summary: Bootstrap a Discord guild as a virtual airline
      description: |
        Binds the Discord guild in X-Discord-Server-Id to a new VA with the given code.
        The caller (X-Discord-User-Id) must already be a registered Politburo user and
        becomes VA administrator.

        Error responses use `error.code` from the CreateServerErrorCode enum.
      tags:
        - Users
      security:
        - ApiKeyAuth: []
      parameters:
        - name: X-Discord-User-Id
          in: header
          required: true
          description: Discord user id of the member initializing the server.
          schema:
            type: string
        - name: X-Discord-Server-Id
          in: header
          required: true
          description: Discord guild id to bind as a VA.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateServerRequest'
      responses:
        '200':
          description: Virtual airline created and guild bound
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateServerResponse'
        '400':
          description: Invalid request, VA code, or user not registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateServerErrorResponse'
              examples:
                invalidRequest:
                  value:
                    error:
                      code: INVALID_REQUEST
                      message: Invalid request body
                invalidVaCode:
                  value:
                    error:
                      code: INVALID_VA_CODE
                      message: Invalid VA code
                userNotFound:
                  value:
                    error:
                      code: USER_NOT_FOUND
                      message: You must register as a user before initializing a server
        '401':
          description: Missing or inactive API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateServerErrorResponse'
              examples:
                unauthorized:
                  value:
                    error:
                      code: UNAUTHORIZED
                      message: Unauthorized
        '403':
          description: Missing Discord context headers
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateServerErrorResponse'
              examples:
                missingDiscordContext:
                  value:
                    error:
                      code: MISSING_DISCORD_CONTEXT
                      message: 'Missing required Discord context headers: X-Discord-User-Id and X-Discord-Server-Id'
        '409':
          description: Guild or VA code already taken
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateServerErrorResponse'
              examples:
                serverAlreadyVa:
                  value:
                    error:
                      code: SERVER_ALREADY_VA
                      message: This Discord server is already registered as a VA
                vaCodeTaken:
                  value:
                    error:
                      code: VA_CODE_TAKEN
                      message: This VA code is already in use
        '500':
          description: Internal initialization failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateServerErrorResponse'
              examples:
                initFailed:
                  value:
                    error:
                      code: INIT_FAILED
                      message: initialization failed
  /api/v1/memberships/link:
    post:
      operationId: linkMembership
      summary: Link the caller to the current guild virtual airline
      description: |
        Creates a VA membership for the Discord member in X-Discord-User-Id on the virtual airline
        bound to X-Discord-Server-Id. The caller must already be a registered Politburo user.
        Assigns role proletariat with no callsign in the request body.

        Error responses use `error.code` from the LinkMembershipErrorCode enum.
      tags:
        - Users
      security:
        - ApiKeyAuth: []
      parameters:
        - name: X-Discord-User-Id
          in: header
          required: true
          description: Discord user id of the member linking to the VA.
          schema:
            type: string
        - name: X-Discord-Server-Id
          in: header
          required: true
          description: Discord guild id that must be registered as a VA.
          schema:
            type: string
      responses:
        '200':
          description: Linked to virtual airline
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkMembershipResponse'
        '400':
          description: Link failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkMembershipErrorResponse'
              examples:
                linkFailed:
                  value:
                    error:
                      code: LINK_FAILED
                      message: link failed
        '401':
          description: Missing or inactive API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkMembershipErrorResponse'
              examples:
                unauthorized:
                  value:
                    error:
                      code: UNAUTHORIZED
                      message: Unauthorized
        '403':
          description: Missing Discord context headers
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkMembershipErrorResponse'
              examples:
                missingDiscordContext:
                  value:
                    error:
                      code: MISSING_DISCORD_CONTEXT
                      message: 'Missing required Discord context headers: X-Discord-User-Id and X-Discord-Server-Id'
        '404':
          description: User or VA not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkMembershipErrorResponse'
              examples:
                userNotFound:
                  value:
                    error:
                      code: USER_NOT_FOUND
                      message: User not found. Please register first
                vaNotFound:
                  value:
                    error:
                      code: VA_NOT_FOUND
                      message: Virtual airline not found for this server
        '409':
          description: Already a member of this VA
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkMembershipErrorResponse'
              examples:
                alreadyMember:
                  value:
                    error:
                      code: ALREADY_MEMBER
                      message: You are already a member of this VA
        '500':
          description: Internal link failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkMembershipErrorResponse'
  /api/v1/admin/verify-god:
    get:
      operationId: verifyGodMode
      summary: Confirm the caller has platform god-mode access
      description: |
        Requires a valid API key and X-Discord-User-Id for a configured platform operator.
        X-Discord-Server-Id is required for bot context.
      tags:
        - Admin
      security:
        - ApiKeyAuth: []
      parameters:
        - name: X-Discord-User-Id
          in: header
          required: true
          schema:
            type: string
        - name: X-Discord-Server-Id
          in: header
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Caller is a platform operator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifyGodResponse'
        '401':
          description: Missing or inactive API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Missing Discord context or not a platform operator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/admin/users/ban:
    post:
      operationId: banUser
      summary: Ban a Discord user from Politburo
      description: |
        Platform operator (god mode) only. Adds the Discord id to banned_discord_ids and,
        when a Politburo user exists, archives and deletes their account and memberships.
      tags:
        - Admin
      security:
        - ApiKeyAuth: []
      parameters:
        - name: X-Discord-User-Id
          in: header
          required: true
          description: Discord user id of the platform operator performing the ban.
          schema:
            type: string
        - name: X-Discord-Server-Id
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BanUserRequest'
      responses:
        '200':
          description: User banned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BanUserResponse'
        '400':
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or inactive API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Missing Discord context or not a platform operator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Ban failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/va:
    get:
      operationId: getVA
      summary: Read virtual airline details for the authenticated VA administrator
      description: |
        Returns metadata for the virtual airline tied to the caller's session or Discord membership context.
        Requires VA administrator role (`administrator`).
      tags:
        - VirtualAirline
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      responses:
        '200':
          description: Virtual airline details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VADetailsResponse'
        '401':
          description: Missing or inactive API key or session cookie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Caller is not a VA administrator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No virtual airline context or row not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Failed to load virtual airline
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/va/config:
    get:
      operationId: getVAConfig
      summary: Read a typed VA configuration block
      description: |
        Returns the JSON config block for the virtual airline tied to the caller's session or Discord membership context.
        Requires VA administrator role (`administrator`).
      tags:
        - VirtualAirline
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      parameters:
        - name: configType
          in: query
          required: true
          description: Typed config block to read (for example `BASIC` or `WEBHOOKS`).
          schema:
            $ref: '#/components/schemas/VAConfigType'
      responses:
        '200':
          description: VA configuration block
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VAConfigResponse'
        '400':
          description: Unknown or invalid config type query parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or inactive API key or session cookie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Caller is not a VA administrator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No virtual airline context, VA not found, or no config for this type
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Failed to load configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      operationId: upsertVAConfig
      summary: Create or replace a typed VA configuration block
      description: |
        Validates and stores the JSON config for the requested type. `updatedAt` and `updatedBy` are set by the server
        from the authenticated Politburo user. Requires VA administrator role (`administrator`).
      tags:
        - VirtualAirline
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      parameters:
        - name: configType
          in: query
          required: true
          description: Typed config block to write (for example `BASIC` or `WEBHOOKS`).
          schema:
            $ref: '#/components/schemas/VAConfigType'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VAConfigPayload'
      responses:
        '200':
          description: Stored configuration block
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VAConfigResponse'
        '400':
          description: Invalid request body or config validation failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing authentication or Politburo user for audit
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Caller is not a VA administrator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No virtual airline context
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Failed to store configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/va/webhook/trigger:
    post:
      operationId: triggerVAWebhook
      summary: Manually trigger a VA Discord webhook feature
      description: |
        Loads the requested WEBHOOKS feature for the caller's virtual airline and dispatches it immediately,
        using the same handler, metrics, and run log persistence as the scheduled webhook job.
        Requires VA administrator role (`administrator`).
      tags:
        - VirtualAirline
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VAWebhookTriggerRequest'
      responses:
        '200':
          description: Webhook dispatch finished (including skipped or error outcomes)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VAWebhookTriggerResponse'
        '400':
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or inactive API key or session cookie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Caller is not a VA administrator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No virtual airline context, WEBHOOKS config missing, or unknown webhook type
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Failed to load config or persist dispatch
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/va/live/connections:
    get:
      operationId: listVALiveConnections
      summary: List persisted Live organisation connections for this VA
      description: |
        Returns Infinite Flight Live organisations linked to the caller's virtual airline.
        Requires VA staff role (`bourgeoisie` or `administrator`).
      tags:
        - VirtualAirline
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      responses:
        '200':
          description: Linked organisations
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VALiveConnectionsResponse'
        '401':
          description: Missing or inactive API key or session cookie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Caller is not VA staff (bourgeoisie or administrator)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: No virtual airline context
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Failed to load connections
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/va/live/connections/{organizationId}:
    delete:
      operationId: deleteVALiveConnection
      summary: Remove a Live organisation connection from this VA
      description: |
        Unlinks an Infinite Flight Live organisation from the caller's virtual airline.
        Requires VA administrator role (`administrator`).
      tags:
        - VirtualAirline
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      parameters:
        - name: organizationId
          in: path
          required: true
          description: Infinite Flight Live organisation id
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Connection removed
        '401':
          description: Missing or inactive API key or session cookie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Caller is not a VA administrator
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Connection or VA context not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Failed to remove connection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/va/live/connections/{organizationId}/ping:
    get:
      operationId: pingVALiveConnection
      summary: Verify Live API access for a linked organisation
      description: |
        Calls Infinite Flight Live API for the linked organisation using this VA's OAuth grant.
        Requires VA staff role (`bourgeoisie` or `administrator`).

        Staff access may be tightened once persistence supports per-org real-time access checks.
      tags:
        - VirtualAirline
      security:
        - ApiKeyAuth: []
        - CookieAuth: []
      parameters:
        - name: organizationId
          in: path
          required: true
          description: Infinite Flight Live organisation id
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Organisation reachable with the VA grant
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VALiveConnectionPingResponse'
        '401':
          description: Missing or inactive API key or session cookie
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Caller is not VA staff (bourgeoisie or administrator)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Connection or VA context not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: VA Live OAuth grant missing or needs reconnect
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Failed to ping connection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: Infinite Flight Live API request failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: UUID of an active row in the api_keys table.
    CookieAuth:
      type: apiKey
      in: cookie
      name: session_id
      description: Browser session cookie minted by GET /auth/login.
  schemas:
    HealthResponse:
      type: object
      required:
        - status
      properties:
        status:
          type: string
        started_at:
          type: string
          format: date-time
        uptime:
          type: string
        services:
          type: object
          additionalProperties:
            type: string
    ActiveSessionsResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/ActiveSessionsData'
    ActiveSessionsData:
      type: object
      required:
        - result
        - meta
      properties:
        result:
          type: array
          items:
            $ref: '#/components/schemas/ActiveSession'
        meta:
          $ref: '#/components/schemas/CacheMetadata'
    ActiveSession:
      type: object
      required:
        - normalizedName
        - userCount
        - type
      properties:
        normalizedName:
          type: string
          description: Lowercase underscore form of the session name for filters and serverId.
        userCount:
          type: integer
        type:
          type: integer
    AvailableFilter:
      type: object
      required:
        - name
        - type
        - desc
        - current
        - default
      properties:
        name:
          type: string
        type:
          type: string
        desc:
          type: string
        current: {}
        default: {}
        options:
          type: array
          items:
            type: string
    CacheMetadata:
      type: object
      required:
        - lastCached
        - refreshIntervalMins
      properties:
        lastCached:
          type: string
          format: date-time
        refreshIntervalMins:
          type: integer
          minimum: 1
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
            message:
              type: string
    GenerateSignedLinkRequest:
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - DASHBOARD
            - LIVE_MAP
            - CUSTOM
          description: Where to send the browser after login.
          example: DASHBOARD
        redirectTo:
          type: string
          description: Relative path after login. Required when type is CUSTOM; ignored for DASHBOARD and LIVE_MAP.
          example: /dashboard/logbook?user_id=abc
    GenerateSignedLinkData:
      type: object
      required:
        - url
        - expiresIn
        - redirectTo
      properties:
        url:
          type: string
          description: Full one-time URL the member should open.
        expiresIn:
          type: integer
          description: Seconds until the link expires.
          example: 600
        redirectTo:
          type: string
          example: /dashboard
    GenerateSignedLinkResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/GenerateSignedLinkData'
    Session:
      type: object
      required:
        - id
        - name
        - worldType
        - type
        - minimumGradeLevel
        - userCount
        - minimumAppVersion
        - maxUsers
        - normalizedName
        - timestamp
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        worldType:
          type: integer
        type:
          type: integer
        minimumGradeLevel:
          type: integer
        userCount:
          type: integer
        minimumAppVersion:
          type: string
        maximumAppVersion:
          type: string
          nullable: true
        maxUsers:
          type: integer
        normalizedName:
          type: string
          description: Lowercase underscore form of the session name for filters.
        timestamp:
          type: string
          format: date-time
          description: UTC time when this session row was last refreshed into cache.
    PilotStateName:
      type: string
      enum:
        - active
        - away_in_flight
        - away_parked
        - in_background
    ActiveFlightsResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/ActiveFlightsData'
    ActiveFlightsData:
      type: object
      required:
        - availableFilters
        - result
        - meta
        - pagination
      properties:
        availableFilters:
          type: array
          items:
            $ref: '#/components/schemas/AvailableFilter'
        result:
          type: array
          items:
            $ref: '#/components/schemas/ActiveFlightTrack'
        meta:
          $ref: '#/components/schemas/CacheMetadata'
        pagination:
          $ref: '#/components/schemas/Pagination'
    ActiveFlightRecord:
      type: object
      required:
        - flightId
        - callsign
        - latitude
        - longitude
        - altitude
        - speed
        - verticalSpeed
        - track
        - lastReport
        - pilotState
        - isConnected
        - sessionId
        - normalizedName
        - normalized
      properties:
        flightId:
          type: string
          format: uuid
          description: Infinite Flight flight identifier.
        userId:
          type: string
          description: Infinite Flight user identifier.
        aircraftId:
          type: string
        liveryId:
          type: string
        username:
          type: string
          nullable: true
        virtualOrganization:
          type: string
          nullable: true
        callsign:
          type: string
        normalizedCallsign:
          type: string
          description: Callsign with Infinite Flight weight/formation trailers removed for VA affix matching.
        latitude:
          type: number
          format: double
        longitude:
          type: number
          format: double
        altitude:
          type: integer
          description: Altitude in feet.
        speed:
          type: integer
          description: Ground speed in knots (rounded).
        verticalSpeed:
          type: number
          format: double
          description: Vertical speed in feet per minute.
        track:
          type: number
          format: double
          description: Ground track in degrees clockwise from north.
        lastReport:
          type: string
          format: date-time
          description: Timestamp of the latest upstream position report.
        pilotState:
          type: integer
          description: Raw Infinite Flight pilot state code.
        isConnected:
          type: boolean
        aircraftName:
          type: string
        liveryName:
          type: string
        sessionId:
          type: string
        normalizedName:
          type: string
          description: Normalized session name (for example casual, expert, training).
        normalized:
          $ref: '#/components/schemas/ActiveFlightNormalized'
        pathSync:
          $ref: '#/components/schemas/ActiveFlightPathSync'
        matchedVirtualAirlines:
          type: array
          description: Virtual airlines whose BASIC callsign affix rules matched this flight on the current session.
          items:
            $ref: '#/components/schemas/ActiveFlightVirtualAirlineMatch'
        flightState:
          $ref: '#/components/schemas/ActiveFlightState'
          description: Temporal flight-phase estimate when available.
        users:
          type: array
          description: Distinct Infinite Flight user identifiers who have operated this flight during the current cache lifetime.
          items:
            type: string
    ActiveFlightRecordResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/ActiveFlightRecord'
    ActiveFlightNormalized:
      type: object
      required:
        - speed
        - verticalSpeed
        - pilotState
        - isConnected
      properties:
        speed:
          type: string
          description: Display-formatted ground speed (for example "186 kts").
        verticalSpeed:
          type: string
          description: Display-formatted vertical speed (for example "-434.3 ft/min").
        pilotState:
          $ref: '#/components/schemas/PilotStateName'
        isConnected:
          type: string
          enum:
            - connected
            - disconnected
          description: Human-readable connection status.
    ActiveFlightPathSync:
      type: object
      required:
        - fplSyncRequired
      properties:
        fplSyncRequired:
          type: boolean
          description: Whether a flight-plan sync is pending for this flight.
        lastFPLSyncAt:
          type: string
          format: date-time
          description: When the last flight-plan sync completed, if any.
    ActiveFlightVirtualAirlineMatch:
      type: object
      required:
        - vaId
        - vaCode
      properties:
        vaId:
          type: string
          format: uuid
          description: Virtual airline identifier.
        vaCode:
          type: string
          description: Virtual airline code.
    Pagination:
      type: object
      required:
        - totalLength
        - pageLength
        - pageNumber
      properties:
        totalLength:
          type: integer
          minimum: 0
          description: Number of flights matching the current filters before paging.
        pageLength:
          type: integer
          minimum: 1
          description: Requested page size.
        pageNumber:
          type: integer
          minimum: 1
          description: Requested 1-based page number.
    CreateUserErrorCode:
      type: string
      description: Machine-readable error code for POST /api/v1/users failures.
      enum:
        - INVALID_REQUEST
        - FLIGHT_PROOF_FAILED
        - REGISTRATION_FAILED
        - UNAUTHORIZED
        - BANNED
        - MISSING_DISCORD_CONTEXT
        - IF_USER_NOT_FOUND
        - USER_ALREADY_REGISTERED
        - IFC_ALREADY_LINKED
        - AUTH_LOOKUP_FAILED
        - IF_UNAVAILABLE
    CreateUserErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              $ref: '#/components/schemas/CreateUserErrorCode'
            message:
              type: string
    CreateUserRequest:
      type: object
      required:
        - discourseNames
        - logbookEntry
      properties:
        discourseNames:
          type: array
          minItems: 1
          maxItems: 1
          items:
            type: string
            minLength: 1
          description: IFC Community username (same as Infinite Flight discourseNames lookup).
        logbookEntry:
          type: string
          pattern: ^[A-Z]{4}-[A-Z]{4}$
          description: Origin-destination proof for the pilot's latest complete online logbook flight (ICAO ORIG-DEST).
    CreateUserData:
      type: object
      required:
        - success
        - message
        - is_va_registered
        - if_community_id
      properties:
        success:
          type: boolean
        message:
          type: string
        is_va_registered:
          type: boolean
          description: True when the Discord server in X-Discord-Server-Id is configured as a VA.
        if_community_id:
          type: string
          description: Linked IFC Community username stored for the new user.
    CreateUserResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/CreateUserData'
    CreateServerErrorCode:
      type: string
      description: Machine-readable error code for POST /api/v1/servers failures.
      enum:
        - INVALID_REQUEST
        - INVALID_VA_CODE
        - USER_NOT_FOUND
        - UNAUTHORIZED
        - MISSING_DISCORD_CONTEXT
        - SERVER_ALREADY_VA
        - VA_CODE_TAKEN
        - INIT_FAILED
    CreateServerErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              $ref: '#/components/schemas/CreateServerErrorCode'
            message:
              type: string
    CreateServerRequest:
      type: object
      required:
        - va_code
      properties:
        va_code:
          type: string
          pattern: ^[A-Z0-9]{3,5}$
          description: Short unique identifier for the virtual airline (e.g. IFE, DAL).
        display_name:
          type: string
          maxLength: 100
          description: Optional display name; defaults to va_code when omitted.
    CreateServerData:
      type: object
      required:
        - success
        - message
        - va_code
        - setup_required
        - va_id
      properties:
        success:
          type: boolean
        message:
          type: string
        va_code:
          type: string
        setup_required:
          type: boolean
          description: True when Vizburo basic setup is still required.
        va_id:
          type: string
          format: uuid
          description: Internal virtual airline id.
    CreateServerResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/CreateServerData'
    LinkMembershipErrorCode:
      type: string
      description: Machine-readable error code for POST /api/v1/memberships/link failures.
      enum:
        - UNAUTHORIZED
        - MISSING_DISCORD_CONTEXT
        - USER_NOT_FOUND
        - VA_NOT_FOUND
        - ALREADY_MEMBER
        - LINK_FAILED
    LinkMembershipErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              $ref: '#/components/schemas/LinkMembershipErrorCode'
            message:
              type: string
    LinkMembershipData:
      type: object
      required:
        - success
        - role
        - va_id
      properties:
        success:
          type: boolean
          example: true
        role:
          type: string
          description: VA membership role assigned on link.
          example: proletariat
        va_id:
          type: string
          format: uuid
          description: Virtual airline id for the current Discord guild.
    LinkMembershipResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/LinkMembershipData'
    VerifyGodData:
      type: object
      required:
        - is_god
      properties:
        is_god:
          type: boolean
    VerifyGodResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/VerifyGodData'
    BanUserRequest:
      type: object
      required:
        - discord_user_id
      properties:
        discord_user_id:
          type: string
          minLength: 1
          description: Discord user id to ban from registration and remove from Politburo if registered.
        reason:
          type: string
          description: Optional audit reason stored on the ban record.
    BanUserData:
      type: object
      required:
        - success
        - discord_user_id
        - user_deleted
      properties:
        success:
          type: boolean
        discord_user_id:
          type: string
        user_deleted:
          type: boolean
          description: True when a Politburo user row existed and was archived and removed.
    BanUserResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/BanUserData'
    VADetails:
      type: object
      required:
        - id
        - name
        - code
        - isActive
      properties:
        id:
          type: string
          format: uuid
          description: Internal virtual airline id.
        name:
          type: string
        code:
          type: string
          description: Short VA identifier (callsign prefix).
        isActive:
          type: boolean
    VADetailsResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/VADetails'
    VAConfigType:
      type: string
      enum:
        - BASIC
        - WEBHOOKS
      description: Typed VA configuration block identifier.
    VAConfigPayload:
      type: object
      additionalProperties: true
      description: Opaque JSON config body for the requested type (validated server-side per type).
    VAConfigBasic:
      type: object
      description: |
        Documented shape for BASIC va config (stored as opaque payload; validated server-side).
        `game_callsign.prefix`/`suffix` are trimmed and matched case-sensitively against live callsigns
        (prefix against the raw callsign; suffix against the raw or normalized callsign).
        `preferred_servers` are lowercased, trimmed, de-duplicated, and sorted; when empty the VA matches
        flights on every server.
      additionalProperties: true
      properties:
        game_callsign:
          type: object
          additionalProperties: true
          properties:
            prefix:
              type: string
            suffix:
              type: string
        preferred_servers:
          type: array
          description: Normalized Infinite Flight session names (e.g. casual, training, expert).
          items:
            type: string
            minLength: 1
    VAConfigWebhooks:
      type: object
      description: |
        Feature map for Discord webhook dispatches. Keys are feature ids (`active_flights` is the first).
        Each entry has an HTTPS Discord webhook `url`, a `frequency` of `30m`, `60m`, or `120m`, and a `filters` object.
        Unknown feature keys are stored without feature-specific validation.
        `active_flights` filters accept optional `pilotState` (pilot state names) and `vaCode`.
        Messages include callsign, pilot, aircraft, altitude, and speed. Route is shown as `Route: —`
        until the flight cache carries route data.
      additionalProperties:
        type: object
        required:
          - url
          - frequency
          - filters
        properties:
          url:
            type: string
            format: uri
            description: HTTPS Discord webhook URL on discord.com or discordapp.com (`/api/webhooks/{id}/{token}`).
            example: https://discord.com/api/webhooks/123456789012345678/token
          frequency:
            type: string
            enum:
              - 30m
              - 60m
              - 120m
          filters:
            type: object
            description: Feature-specific filters. For `active_flights`, optional `pilotState` and `vaCode`.
            additionalProperties: true
            properties:
              pilotState:
                type: array
                items:
                  type: string
                  enum:
                    - active
                    - away_in_flight
                    - away_parked
                    - in_background
              vaCode:
                type: string
                minLength: 1
      example:
        active_flights:
          url: https://discord.com/api/webhooks/123456789012345678/token
          frequency: 30m
          filters:
            pilotState:
              - active
            vaCode: TEST
    VAConfigData:
      type: object
      required:
        - type
        - config
        - updatedAt
        - updatedBy
      properties:
        type:
          $ref: '#/components/schemas/VAConfigType'
        config:
          $ref: '#/components/schemas/VAConfigPayload'
        updatedAt:
          type: string
          format: date-time
        updatedBy:
          type: string
          format: uuid
    VAConfigResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/VAConfigData'
    VAWebhookTriggerRequest:
      type: object
      required:
        - webhookType
      properties:
        webhookType:
          type: string
          minLength: 1
          description: WEBHOOKS feature key (for example `active_flights`).
          example: active_flights
    VAWebhookTriggerData:
      type: object
      required:
        - webhookType
        - outcome
      properties:
        webhookType:
          type: string
          description: Feature key that was dispatched.
        outcome:
          type: string
          enum:
            - success
            - skipped
            - error
          description: Dispatch outcome recorded in the webhook run log.
        skipReason:
          type: string
          description: Present when outcome is `skipped`.
        discordMessages:
          type: integer
          minimum: 0
          description: Number of Discord messages posted for this run.
        errorMessage:
          type: string
          description: Present when outcome is `error`.
    VAWebhookTriggerResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/VAWebhookTriggerData'
    CurrentUser:
      type: object
      required:
        - id
        - discordId
        - isActive
        - role
      properties:
        id:
          type: string
          format: uuid
          description: Internal Politburo user id.
        discordId:
          type: string
          description: Discord snowflake for the user.
        isActive:
          type: boolean
        username:
          type: string
          description: Display username when stored.
        ifCommunityId:
          type: string
          description: Linked Infinite Flight Community username.
        role:
          type: string
          nullable: true
          description: VA membership role in the current context; null when the caller has no role.
          enum:
            - proletariat
            - bourgeoisie
            - administrator
        vaId:
          type: string
          format: uuid
          description: Virtual airline id for the current Discord server context, when known.
        discordServerId:
          type: string
          description: Discord guild id bound to the session or request context.
    CurrentUserResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/CurrentUser'
    InfiniteFlightOAuthAuthorizeData:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          format: uri
          description: Infinite Flight authorization URL (`/connect/authorize`) to open in the browser.
    InfiniteFlightOAuthAuthorizeResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: '#/components/schemas/InfiniteFlightOAuthAuthorizeData'
    LiveOrganizationListItem:
      type: object
      required:
        - id
        - name
        - role
      properties:
        id:
          type: string
          format: uuid
          description: Infinite Flight Live organization ID.
        name:
          type: string
          description: Organization display name.
        role:
          type: integer
          description: Caller role in the organization (`PersistentOrganizationMemberRole`). Only Admin (1) and Owner (2) are returned.
          enum:
            - 1
            - 2
    LiveOrganizationsResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/LiveOrganizationListItem'
    VALiveConnection:
      type: object
      required:
        - id
        - name
        - role
        - linkedAt
      properties:
        id:
          type: string
          format: uuid
          description: Infinite Flight Live organisation id
        name:
          type: string
        role:
          type: integer
          description: Caller's in-game role in the organisation (Admin or Owner when linked)
        linkedAt:
          type: string
          format: date-time
    VALiveConnectionsResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/VALiveConnection'
    VALiveConnectionPingResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - id
            - name
            - role
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
            role:
              type: integer
    ActiveFlightTrack:
      type: object
      required:
        - flightId
        - callsign
        - latitude
        - longitude
        - speed
      properties:
        flightId:
          type: string
          format: uuid
          description: Infinite Flight flight identifier.
        callsign:
          type: string
          description: Aircraft callsign from the latest upstream poll.
        latitude:
          type: number
          format: double
        longitude:
          type: number
          format: double
        speed:
          type: integer
          description: Ground speed in knots (rounded).
        track:
          type: number
          format: double
          description: Ground track in degrees clockwise from north (0–360), for map icon rotation.
    ActiveFlightState:
      type: object
      required:
        - phase
        - activity
        - dataQuality
        - confidence
        - phaseSince
        - apPlus
      properties:
        phase:
          type: string
          enum:
            - spawned_in
            - taxiing
            - takeoff
            - climbing
            - cruise
            - descending
            - landing
            - parked
            - awaiting_ap_plus_resumption
            - unknown
        activity:
          type: string
          enum:
            - moving
            - stationary
            - unknown
        dataQuality:
          type: string
          enum:
            - fresh
            - long_gap
            - stale
            - uncertain
            - invalid
        confidence:
          type: number
          format: double
          description: Evidence strength (0-1), not a calibrated probability.
        previousPhase:
          type: string
          nullable: true
        phaseSince:
          type: string
          format: date-time
        reasons:
          type: array
          items:
            type: string
        apPlus:
          type: object
          required:
            - candidate
            - score
            - since
          properties:
            candidate:
              type: boolean
            score:
              type: number
              format: double
            since:
              type: string
              format: date-time
            reasons:
              type: array
              items:
                type: string
        features:
          type: object
          properties:
            elapsedSec:
              type: number
              format: double
            distanceNm:
              type: number
              format: double
            altDeltaFt:
              type: number
              format: double
            avgVerticalFpm:
              type: number
              format: double
            speedDeltaKt:
              type: number
              format: double
            trackDeltaDeg:
              type: number
              format: double
            moved:
              type: boolean
            altStable:
              type: boolean
            progressRatio:
              type: number
              format: double
            gapClass:
              type: string
              enum:
                - normal
                - long
                - too_large
            fresh:
              type: boolean
            stationarySamples:
              type: integer
            stationaryDurationSec:
              type: number
              format: double
            sustainedClimbCount:
              type: integer
            sustainedDescentCount:
              type: integer
            lastReportAdvancing:
              type: boolean
