asyncapi: 2.6.0
id: urn:vimh:event-transport-api
info:
  title: VIMH Event Transport API
  version: 1.6.0
  contact:
    name: VIMH API maintainers
  description: |
    AsyncAPI specification for the authenticated VIMH WebSocket and WebTransport endpoints.

    Connection:
    - WebSocket endpoint: `GET /ws/global/register`
    - WebTransport endpoint: `CONNECT /webtransport/global/register/{token}` on `api.diogen.space:7092`.
      It runs over HTTP/3/QUIC and uses the same message contracts as WebSocket.
    - WebSocket authentication is performed by the same HTTP-only `access_token` cookie used by the REST API.
    - WebTransport authentication uses a one-time REST-issued registration token from
      `POST /api/auth/webtransport-token`. The token is bound to the authenticated user,
      expires after 30 seconds and is deleted when consumed.
    - Client commands are JSON text frames in the envelope `{ "type": "...", "nonce": "...", "data": { ... } }`.
      `nonce` is optional and generated by the client; direct server replies echo the same `nonce`.
    - WebTransport framing uses exactly one long-lived bidirectional stream per session.
      Each application message is sent as binary length-prefixed framing:
      `[4 bytes big-endian payload length][UTF-8 JSON payload]`.
      The JSON payload itself uses the same envelope as WebSocket.
    - Server events use the same `{ "type": "...", "data": { ... } }` envelope. Broadcast events do not include
      a request `nonce`.
    - Heartbeat timeout is 25 seconds. WebSocket uses protocol ping control frames every 20 seconds;
      clients must answer with WebSocket pong control frames.
    - WebTransport heartbeat uses QUIC datagrams, not the length-prefixed JSON stream.
      The server sends `{"type":"ping","ts":<unix_ms>}` datagrams every 20 seconds and closes
      the session if no `pong` datagram is received within 25 seconds. Clients must answer each
      ping datagram with `{"type":"pong","ts":<same_ts>}`. These heartbeat datagrams are transport
      control messages and are not delivered through the application event envelope.
    - Incoming client messages are rate limited to 60 messages per minute per connection.
    - WebRTC SFU uses Unified Plan SDP. Clients should implement the polite side of perfect negotiation:
      rollback a local offer if a server `sfu_offer` arrives while the client is making an offer or is not stable.
    - SFU ICE uses Trickle ICE. End-of-candidates is represented by `data: null` in `sfu_ice_candidate`.
      ICE restart is requested with `sfu_ice_restart`; the restart itself is delivered as a normal server `sfu_offer`.
    - Direct-call SFU flow is room-scoped by direct-room UUID:
      `POST /api/direct/rooms/{room_id}/calls` creates the call session and broadcasts `direct_call_started`,
      then both peers send `sfu_join` with the same direct-room UUID. The `sfu_joined` ack returns
      `scope: direct` and intentionally does not include `session_id`. After `sfu_joined`, the client sends
      the initial `sfu_offer`, completes trickle ICE, and treats `direct_call_ended` as the canonical
      call-termination event for that direct room.
defaultContentType: application/json
tags:
  - name: connection
    description: WebSocket/WebTransport connection, authentication and lifecycle.
  - name: servers
    description: Server subscriptions and room membership commands.
  - name: rooms
    description: Room chat and message lifecycle events.
  - name: direct
    description: Direct messages and direct calls.
  - name: sfu
    description: WebRTC SFU signaling.
  - name: presence
    description: User presence notifications.
servers:
  production:
    url: api.diogen.space
    protocol: wss
    description: Production WebSocket endpoint.
  productionWebTransport:
    url: api.diogen.space:7092
    protocol: https
    protocolVersion: HTTP/3
    description: Production WebTransport endpoint over HTTP/3/QUIC using one REST-issued registration token and one bidirectional stream with 4-byte big-endian length-prefixed JSON messages.
#  local:
#    url: 127.0.0.1:{port}
#    protocol: ws
#    description: Local development endpoint.
#    variables:
#      port:
#        default: "8080"
#        description: Value of `APP_PORT`.
#    security:
#      - accessTokenCookie: []
channels:
  /ws/global/register:
    description: |
      Main authenticated WebSocket channel for subscriptions, room chat, direct messages,
      presence updates and SFU signaling.
    servers:
      - production
    bindings:
      ws:
        method: GET
    subscribe: &receiveServerEventOperation
      operationId: receiveServerEvent
      summary: Messages pushed from the server to the client
      description: |
        Events delivered to the connected user. Some events are sent only after the user joins a room,
        subscribes to a server, belongs to a direct room, or is a friend of the user whose presence changed.
      tags:
        - name: connection
        - name: servers
        - name: rooms
        - name: direct
        - name: sfu
        - name: presence
      message:
        oneOf:
          - $ref: '#/components/messages/SubscribedServerAck'
          - $ref: '#/components/messages/UnsubscribedServerAck'
          - $ref: '#/components/messages/ServerParticipants'
          - $ref: '#/components/messages/ServerRoomsSnapshot'
          - $ref: '#/components/messages/ServerUpdated'
          - $ref: '#/components/messages/ServerMemberRemoved'
          - $ref: '#/components/messages/ServerRoleUpdated'
          - $ref: '#/components/messages/JoinedRoomAck'
          - $ref: '#/components/messages/LeftRoomAck'
          - $ref: '#/components/messages/RTCRoomParticipants'
          - $ref: '#/components/messages/RoomCreated'
          - $ref: '#/components/messages/RoomUpdated'
          - $ref: '#/components/messages/RoomDeleted'
          - $ref: '#/components/messages/RoomChatMessage'
          - $ref: '#/components/messages/TypingIndicator'
          - $ref: '#/components/messages/MessageUpdated'
          - $ref: '#/components/messages/MessageDeleted'
          - $ref: '#/components/messages/MessageStatusUpdated'
          - $ref: '#/components/messages/MessagePinUpdated'
          - $ref: '#/components/messages/MessageReactionUpdated'
          - $ref: '#/components/messages/RoomMessagesSnapshot'
          - $ref: '#/components/messages/DirectRoomCreated'
          - $ref: '#/components/messages/DirectRoomUpdated'
          - $ref: '#/components/messages/DirectRoomLeft'
          - $ref: '#/components/messages/DirectMessagesSnapshot'
          - $ref: '#/components/messages/DirectMessageCreated'
          - $ref: '#/components/messages/DirectMessageUpdated'
          - $ref: '#/components/messages/DirectMessageDeleted'
          - $ref: '#/components/messages/DirectMessageStatusUpdated'
          - $ref: '#/components/messages/DirectCallStarted'
          - $ref: '#/components/messages/DirectCallEnded'
          - $ref: '#/components/messages/DirectCallDeclined'
          - $ref: '#/components/messages/FriendRequestsSnapshot'
          - $ref: '#/components/messages/FriendRequestReceived'
          - $ref: '#/components/messages/FriendRequestAccepted'
          - $ref: '#/components/messages/FriendRequestRejected'
          - $ref: '#/components/messages/FriendRemoved'
          - $ref: '#/components/messages/UserPresenceUpdated'
          - $ref: '#/components/messages/SFUJoinedAck'
          - $ref: '#/components/messages/SFULeftAck'
          - $ref: '#/components/messages/SFUOfferFromServer'
          - $ref: '#/components/messages/SFUAnswerFromServer'
          - $ref: '#/components/messages/SFUIceCandidateFromServer'
          - $ref: '#/components/messages/SFUParticipantMuted'
          - $ref: '#/components/messages/SFUParticipantUnmuted'
          - $ref: '#/components/messages/SFUActiveSpeakers'
          - $ref: '#/components/messages/SFUMediaState'
          - $ref: '#/components/messages/Error'
    publish: &sendClientCommandOperation
      operationId: sendClientCommand
      summary: Messages accepted by the server from the client
      description: |
        Commands sent by the client. The WebSocket route must be opened with a valid `access_token`
        cookie before any command is accepted.
      tags:
        - name: servers
        - name: rooms
        - name: direct
        - name: sfu
      message:
        oneOf:
          - $ref: '#/components/messages/SubscribeServer'
          - $ref: '#/components/messages/UnsubscribeServer'
          - $ref: '#/components/messages/GetServerParticipants'
          - $ref: '#/components/messages/GetServerRooms'
          - $ref: '#/components/messages/GetRoomMessages'
          - $ref: '#/components/messages/JoinRoom'
          - $ref: '#/components/messages/LeaveRoom'
          - $ref: '#/components/messages/GetRTCRoomParticipants'
          - $ref: '#/components/messages/Chat'
          - $ref: '#/components/messages/EditMessage'
          - $ref: '#/components/messages/DeleteMessage'
          - $ref: '#/components/messages/MarkMessageRead'
          - $ref: '#/components/messages/PinMessage'
          - $ref: '#/components/messages/UnpinMessage'
          - $ref: '#/components/messages/TypingIndicatorCommand'
          - $ref: '#/components/messages/GetDirectMessages'
          - $ref: '#/components/messages/GetFriendRequests'
          - $ref: '#/components/messages/DirectRoomCreate'
          - $ref: '#/components/messages/DirectMessage'
          - $ref: '#/components/messages/DirectMessageEdit'
          - $ref: '#/components/messages/DirectMessageDelete'
          - $ref: '#/components/messages/DirectMessageRead'
          - $ref: '#/components/messages/DirectCallStart'
          - $ref: '#/components/messages/DirectCallEnd'
          - $ref: '#/components/messages/DirectCallDecline'
          - $ref: '#/components/messages/SFUJoin'
          - $ref: '#/components/messages/SFULeave'
          - $ref: '#/components/messages/SFUOfferToServer'
          - $ref: '#/components/messages/SFUAnswerToServer'
          - $ref: '#/components/messages/SFUIceCandidateToServer'
          - $ref: '#/components/messages/SFUIceRestartToServer'
          - $ref: '#/components/messages/SFUParticipantMutedToServer'
          - $ref: '#/components/messages/SFUParticipantUnmutedToServer'
          - $ref: '#/components/messages/SFUActiveSpeakersToServer'
          - $ref: '#/components/messages/SFUMediaStateToServer'
  /webtransport/global/register/{token}:
    description: |
      Main authenticated WebTransport session for subscriptions, room chat, direct messages,
      presence updates and SFU signaling.

      Before connecting, the client must call `POST /api/auth/webtransport-token` with
      the normal REST `access_token` cookie. Use the returned one-time `token` in this
      path. The token expires after 30 seconds and is removed after a successful
      WebTransport registration.

      After the HTTP/3 CONNECT upgrade succeeds, the client must open exactly one bidirectional
      WebTransport stream and keep it open for the whole session.

      Framing on that stream is binary and length-prefixed:
      - `4 bytes` unsigned big-endian payload length
      - `N bytes` UTF-8 JSON payload

      The payload JSON is the same envelope documented for the WebSocket channel.

      WebTransport heartbeat is carried out-of-band over QUIC datagrams. The server sends
      `HeartbeatPingDatagram`; clients must answer with `HeartbeatPongDatagram` using the same
      `ts` value. The heartbeat timeout is 25 seconds.
    servers:
      - productionWebTransport
    publish:
      operationId: sendClientCommandOverWebTransport
      summary: Messages accepted by the server from the client
      description: |
        Commands sent by the client over the authenticated WebTransport session.
        The JSON envelope and command set are identical to the WebSocket transport.
      tags:
        - name: servers
        - name: rooms
        - name: direct
        - name: sfu
      message:
        oneOf:
          - $ref: '#/components/messages/SubscribeServer'
          - $ref: '#/components/messages/UnsubscribeServer'
          - $ref: '#/components/messages/GetServerParticipants'
          - $ref: '#/components/messages/GetServerRooms'
          - $ref: '#/components/messages/GetRoomMessages'
          - $ref: '#/components/messages/JoinRoom'
          - $ref: '#/components/messages/LeaveRoom'
          - $ref: '#/components/messages/GetRTCRoomParticipants'
          - $ref: '#/components/messages/Chat'
          - $ref: '#/components/messages/EditMessage'
          - $ref: '#/components/messages/DeleteMessage'
          - $ref: '#/components/messages/MarkMessageRead'
          - $ref: '#/components/messages/PinMessage'
          - $ref: '#/components/messages/UnpinMessage'
          - $ref: '#/components/messages/TypingIndicatorCommand'
          - $ref: '#/components/messages/GetDirectMessages'
          - $ref: '#/components/messages/GetFriendRequests'
          - $ref: '#/components/messages/DirectRoomCreate'
          - $ref: '#/components/messages/DirectMessage'
          - $ref: '#/components/messages/DirectMessageEdit'
          - $ref: '#/components/messages/DirectMessageDelete'
          - $ref: '#/components/messages/DirectMessageRead'
          - $ref: '#/components/messages/DirectCallStart'
          - $ref: '#/components/messages/DirectCallEnd'
          - $ref: '#/components/messages/DirectCallDecline'
          - $ref: '#/components/messages/SFUJoin'
          - $ref: '#/components/messages/SFULeave'
          - $ref: '#/components/messages/SFUOfferToServer'
          - $ref: '#/components/messages/SFUAnswerToServer'
          - $ref: '#/components/messages/SFUIceCandidateToServer'
          - $ref: '#/components/messages/SFUIceRestartToServer'
          - $ref: '#/components/messages/SFUParticipantMutedToServer'
          - $ref: '#/components/messages/SFUParticipantUnmutedToServer'
          - $ref: '#/components/messages/SFUActiveSpeakersToServer'
          - $ref: '#/components/messages/SFUMediaStateToServer'
    subscribe:
      operationId: receiveServerEventOverWebTransport
      summary: Messages pushed from the server to the client
      description: |
        Events delivered over the authenticated WebTransport session.
        The event envelope and payloads are identical to the WebSocket transport.
      tags:
        - name: connection
        - name: servers
        - name: rooms
        - name: direct
        - name: sfu
        - name: presence
      message:
        oneOf:
          - $ref: '#/components/messages/SubscribedServerAck'
          - $ref: '#/components/messages/UnsubscribedServerAck'
          - $ref: '#/components/messages/ServerParticipants'
          - $ref: '#/components/messages/ServerRoomsSnapshot'
          - $ref: '#/components/messages/ServerUpdated'
          - $ref: '#/components/messages/ServerMemberRemoved'
          - $ref: '#/components/messages/ServerRoleUpdated'
          - $ref: '#/components/messages/JoinedRoomAck'
          - $ref: '#/components/messages/LeftRoomAck'
          - $ref: '#/components/messages/RTCRoomParticipants'
          - $ref: '#/components/messages/RoomCreated'
          - $ref: '#/components/messages/RoomUpdated'
          - $ref: '#/components/messages/RoomDeleted'
          - $ref: '#/components/messages/RoomChatMessage'
          - $ref: '#/components/messages/TypingIndicator'
          - $ref: '#/components/messages/MessageUpdated'
          - $ref: '#/components/messages/MessageDeleted'
          - $ref: '#/components/messages/MessageStatusUpdated'
          - $ref: '#/components/messages/MessagePinUpdated'
          - $ref: '#/components/messages/MessageReactionUpdated'
          - $ref: '#/components/messages/RoomMessagesSnapshot'
          - $ref: '#/components/messages/DirectRoomCreated'
          - $ref: '#/components/messages/DirectRoomUpdated'
          - $ref: '#/components/messages/DirectRoomLeft'
          - $ref: '#/components/messages/DirectMessagesSnapshot'
          - $ref: '#/components/messages/DirectMessageCreated'
          - $ref: '#/components/messages/DirectMessageUpdated'
          - $ref: '#/components/messages/DirectMessageDeleted'
          - $ref: '#/components/messages/DirectMessageStatusUpdated'
          - $ref: '#/components/messages/DirectCallStarted'
          - $ref: '#/components/messages/DirectCallEnded'
          - $ref: '#/components/messages/DirectCallDeclined'
          - $ref: '#/components/messages/FriendRequestsSnapshot'
          - $ref: '#/components/messages/FriendRequestReceived'
          - $ref: '#/components/messages/FriendRequestAccepted'
          - $ref: '#/components/messages/FriendRequestRejected'
          - $ref: '#/components/messages/FriendRemoved'
          - $ref: '#/components/messages/UserPresenceUpdated'
          - $ref: '#/components/messages/SFUJoinedAck'
          - $ref: '#/components/messages/SFULeftAck'
          - $ref: '#/components/messages/SFUOfferFromServer'
          - $ref: '#/components/messages/SFUAnswerFromServer'
          - $ref: '#/components/messages/SFUIceCandidateFromServer'
          - $ref: '#/components/messages/SFUParticipantMuted'
          - $ref: '#/components/messages/SFUParticipantUnmuted'
          - $ref: '#/components/messages/SFUActiveSpeakers'
          - $ref: '#/components/messages/SFUMediaState'
          - $ref: '#/components/messages/Error'
components:
  schemas:
    UUID:
      type: string
      format: uuid
      example: 8c2fd426-b799-4d4d-976d-18a35bf1b57f
    DateTime:
      type: string
      format: date-time
      example: "2026-05-16T12:00:00Z"
    ServerSubscriptionData:
      type: object
      required:
        - server_id
      properties:
        server_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    PublicUser:
      type: object
      required:
        - id
        - nickname
        - status
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        nickname:
          type: string
          example: grimcoder
        avatar_url:
          type: string
          nullable: true
        bio:
          type: string
          nullable: true
        custom_status:
          type: string
          nullable: true
        status:
          type: string
          enum:
            - online
            - offline
            - idle
            - dnd
        last_seen_at:
          $ref: '#/components/schemas/DateTime'
        updated_at:
          $ref: '#/components/schemas/DateTime'
        created_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    ServerMember:
      type: object
      required:
        - server_id
        - user_id
        - can_manage_server
        - can_manage_rooms
        - can_invite
      properties:
        server_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
        role:
          type: string
          nullable: true
          example: member
        role_id:
          $ref: '#/components/schemas/UUID'
        can_manage_server:
          type: boolean
        can_manage_rooms:
          type: boolean
        can_invite:
          type: boolean
        joined_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    Server:
      type: object
      required:
        - id
        - owner_id
        - name
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        owner_id:
          $ref: '#/components/schemas/UUID'
        name:
          type: string
        icon_url:
          type: string
        created_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    ServerRole:
      type: object
      required:
        - id
        - server_id
        - name
        - position
        - can_manage_server
        - can_manage_rooms
        - can_invite
        - created_at
        - updated_at
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        server_id:
          $ref: '#/components/schemas/UUID'
        name:
          type: string
        color:
          type: string
          nullable: true
        position:
          type: string
        can_manage_server:
          type: boolean
        can_manage_rooms:
          type: boolean
        can_invite:
          type: boolean
        created_at:
          $ref: '#/components/schemas/DateTime'
        updated_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    Room:
      type: object
      required:
        - id
        - server_id
        - name
        - type
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        server_id:
          $ref: '#/components/schemas/UUID'
        name:
          type: string
        type:
          type: string
          enum:
            - chat
            - rtc
        position:
          type: string
        created_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    DeletedRoomData:
      type: object
      required:
        - id
        - server_id
        - deleted
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        server_id:
          $ref: '#/components/schemas/UUID'
        deleted:
          type: boolean
      additionalProperties: false
    ServerMemberRemovedData:
      type: object
      required:
        - server_id
        - user_id
      properties:
        server_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    ServerRoleUpdatedData:
      type: object
      required:
        - server_id
      properties:
        server_id:
          $ref: '#/components/schemas/UUID'
        role:
          $ref: '#/components/schemas/ServerRole'
        role_id:
          $ref: '#/components/schemas/UUID'
        created:
          type: boolean
        deleted:
          type: boolean
      additionalProperties: false
    FriendRequestEventData:
      type: object
      required:
        - initiator_id
        - friend_id
        - initiator
        - friend
      properties:
        initiator_id:
          $ref: '#/components/schemas/UUID'
        friend_id:
          $ref: '#/components/schemas/UUID'
        initiator:
          $ref: '#/components/schemas/PublicUser'
        friend:
          $ref: '#/components/schemas/PublicUser'
        direct_room:
          $ref: '#/components/schemas/DirectRoom'
      additionalProperties: false
    FriendRelationEventData:
      type: object
      required:
        - user_id
        - friend_id
        - user
        - friend
      properties:
        user_id:
          $ref: '#/components/schemas/UUID'
        friend_id:
          $ref: '#/components/schemas/UUID'
        user:
          $ref: '#/components/schemas/PublicUser'
        friend:
          $ref: '#/components/schemas/PublicUser'
      additionalProperties: false
    FriendRequest:
      type: object
      required:
        - initiator_id
        - friend_id
        - initiator
        - friend
        - created_at
        - updated_at
      properties:
        initiator_id:
          $ref: '#/components/schemas/UUID'
        friend_id:
          $ref: '#/components/schemas/UUID'
        initiator:
          $ref: '#/components/schemas/PublicUser'
        friend:
          $ref: '#/components/schemas/PublicUser'
        created_at:
          $ref: '#/components/schemas/DateTime'
        updated_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    ServerParticipant:
      type: object
      required:
        - user
        - member
        - online
        - subscribed
      properties:
        user:
          $ref: '#/components/schemas/PublicUser'
        member:
          $ref: '#/components/schemas/ServerMember'
        online:
          type: boolean
          description: True when the user currently has an active global WebSocket connection.
        subscribed:
          type: boolean
          description: True when the user subscribed this WebSocket connection to the server.
      additionalProperties: false
    ServerParticipantsData:
      type: object
      required:
        - server_id
        - total
        - online_count
        - participants
      properties:
        server_id:
          $ref: '#/components/schemas/UUID'
        total:
          type: integer
          minimum: 0
        online_count:
          type: integer
          minimum: 0
        participants:
          type: array
          items:
            $ref: '#/components/schemas/ServerParticipant'
      additionalProperties: false
    ServerRoomsSnapshotData:
      type: object
      required:
        - server_id
        - rooms
      properties:
        server_id:
          $ref: '#/components/schemas/UUID'
        rooms:
          type: array
          items:
            $ref: '#/components/schemas/Room'
      additionalProperties: false
    RTCRoomParticipant:
      type: object
      required:
        - user
        - user_id
        - joined_at
        - connection_state
        - track_ids
        - track_count
        - online
      properties:
        user:
          $ref: '#/components/schemas/PublicUser'
        user_id:
          $ref: '#/components/schemas/UUID'
        joined_at:
          $ref: '#/components/schemas/DateTime'
        connection_state:
          type: string
          example: connected
        track_ids:
          type: array
          items:
            type: string
        track_count:
          type: integer
          minimum: 0
        online:
          type: boolean
      additionalProperties: false
    RTCRoomParticipantsData:
      type: object
      required:
        - server_id
        - room_id
        - total
        - participants
      properties:
        server_id:
          $ref: '#/components/schemas/UUID'
        room_id:
          $ref: '#/components/schemas/UUID'
        total:
          type: integer
          minimum: 0
        participants:
          type: array
          items:
            $ref: '#/components/schemas/RTCRoomParticipant'
      additionalProperties: false
    RoomJoinData:
      type: object
      required:
        - server_id
        - room_id
      properties:
        server_id:
          $ref: '#/components/schemas/UUID'
        room_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    RoomLeaveData:
      type: object
      required:
        - room_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        scope:
          type: string
          enum:
            - server
            - direct
          description: Present on SFU leave acknowledgements.
        reason:
          type: string
          description: Optional server-supplied leave reason.
      additionalProperties: false
    HeartbeatPingDatagram:
      type: object
      description: WebTransport QUIC datagram sent by the server every 20 seconds.
      required:
        - type
        - ts
      properties:
        type:
          type: string
          enum:
            - ping
        ts:
          type: integer
          format: int64
          description: Unix timestamp in milliseconds generated by the sender.
      additionalProperties: false
    HeartbeatPongDatagram:
      type: object
      description: WebTransport QUIC datagram sent by the client in response to `HeartbeatPingDatagram`.
      required:
        - type
        - ts
      properties:
        type:
          type: string
          enum:
            - pong
        ts:
          type: integer
          format: int64
          description: The `ts` value copied from the received ping datagram.
      additionalProperties: false
    ChatSendData:
      type: object
      required:
        - room_id
        - content
      properties:
        server_id:
          $ref: '#/components/schemas/UUID'
        room_id:
          $ref: '#/components/schemas/UUID'
        content:
          type: string
          minLength: 1
          maxLength: 2000
          example: hello from websocket
        type:
          type: string
          default: text
        attachment_ids:
          type: array
          maxItems: 5
          items:
            $ref: '#/components/schemas/UUID'
      additionalProperties: false
    MessageRefData:
      type: object
      required:
        - room_id
        - message_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        message_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    MessageEditData:
      allOf:
        - $ref: '#/components/schemas/MessageRefData'
        - type: object
          required:
            - content
          properties:
            content:
              type: string
              minLength: 1
              maxLength: 2000
              example: edited message text
          additionalProperties: false
    Attachment:
      type: object
      required:
        - id
        - kind
        - is_linked
        - filepath
        - original_name
        - size_bytes
        - url
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        owner_id:
          $ref: '#/components/schemas/UUID'
        kind:
          type: string
          example: attachments
        is_linked:
          type: boolean
        message_id:
          $ref: '#/components/schemas/UUID'
        direct_message_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
        server_id:
          $ref: '#/components/schemas/UUID'
        filepath:
          type: string
        original_name:
          type: string
        content_type:
          type: string
          nullable: true
          example: image/png
        size_bytes:
          type: integer
          format: int64
        url:
          type: string
          example: /api/files/attachments/file.png
        created_at:
          $ref: '#/components/schemas/DateTime'
        linked_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    ReactionCount:
      type: object
      required:
        - emoji
        - count
      properties:
        emoji:
          type: string
          example: "+1"
        count:
          type: integer
          minimum: 0
      additionalProperties: false
    Message:
      type: object
      required:
        - id
        - room_id
        - sender_id
        - content
        - type
        - status
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        room_id:
          $ref: '#/components/schemas/UUID'
        sender_id:
          $ref: '#/components/schemas/UUID'
        content:
          type: string
          example: Hello, world!
        type:
          type: string
          example: text
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/Attachment'
        reactions:
          type: array
          items:
            $ref: '#/components/schemas/ReactionCount'
        edited_at:
          $ref: '#/components/schemas/DateTime'
        deleted_at:
          $ref: '#/components/schemas/DateTime'
        deleted_by:
          $ref: '#/components/schemas/UUID'
        pinned_at:
          $ref: '#/components/schemas/DateTime'
        pinned_by:
          $ref: '#/components/schemas/UUID'
        status:
          type: string
          example: sent
        created_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    MessagePage:
      type: object
      required:
        - messages
        - has_more
        - limit
      properties:
        messages:
          type: array
          items:
            $ref: '#/components/schemas/Message'
        next_cursor:
          $ref: '#/components/schemas/UUID'
        has_more:
          type: boolean
        limit:
          type: integer
          minimum: 1
      additionalProperties: false
    MessageEvent:
      type: object
      required:
        - room_id
        - message_id
        - updated_at
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        message_id:
          $ref: '#/components/schemas/UUID'
        message:
          $ref: '#/components/schemas/Message'
        updated_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    MessageStatusUpdate:
      type: object
      required:
        - room_id
        - message_id
        - user_id
        - status
        - read_at
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        message_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
        status:
          type: string
          enum:
            - read
        read_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    MessageReactionUpdate:
      type: object
      required:
        - room_id
        - message_id
        - user_id
        - removed
        - reactions
        - updated_at
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        message_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
        emoji:
          type: string
          example: "+1"
        removed:
          type: boolean
        reactions:
          type: array
          items:
            $ref: '#/components/schemas/ReactionCount'
        updated_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    DirectRoomCreateData:
      type: object
      required:
        - user_ids
      properties:
        name:
          type: string
          nullable: true
          example: project chat
        user_ids:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/UUID'
          description: User IDs to add alongside the authenticated user. One ID creates or returns a 1-on-1 room; two or more IDs create a group direct room.
      additionalProperties: false
    DirectRoom:
      type: object
      required:
        - id
        - is_group
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        name:
          type: string
          nullable: true
          example: project chat
        is_group:
          type: boolean
          example: true
        created_at:
          $ref: '#/components/schemas/DateTime'
        members:
          type: array
          items:
            $ref: '#/components/schemas/PublicUser'
      additionalProperties: false
    RoomMessagesSnapshotRequestData:
      type: object
      required:
        - room_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
        cursor:
          $ref: '#/components/schemas/UUID'
        q:
          type: string
          nullable: true
          example: release notes
      additionalProperties: false
    DirectMessagesSnapshotRequestData:
      type: object
      required:
        - room_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
        cursor:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    DirectRoomLeftData:
      type: object
      required:
        - room_id
        - user_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    DirectMessageSendData:
      type: object
      required:
        - room_id
        - content
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        content:
          type: string
          minLength: 1
          example: private hello
        type:
          type: string
          default: text
        attachment_ids:
          type: array
          maxItems: 5
          items:
            $ref: '#/components/schemas/UUID'
      additionalProperties: false
    DirectMessageRefData:
      type: object
      required:
        - room_id
        - message_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        message_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    DirectMessageEditData:
      allOf:
        - $ref: '#/components/schemas/DirectMessageRefData'
        - type: object
          required:
            - content
          properties:
            content:
              type: string
              minLength: 1
              example: edited private hello
          additionalProperties: false
    DirectReadReceipt:
      type: object
      required:
        - user_id
        - read_at
      properties:
        user_id:
          $ref: '#/components/schemas/UUID'
        read_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    DirectMessage:
      type: object
      required:
        - id
        - room_id
        - sender_id
        - content
        - type
        - status
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        room_id:
          $ref: '#/components/schemas/UUID'
        sender_id:
          $ref: '#/components/schemas/UUID'
        content:
          type: string
        type:
          type: string
          example: text
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/Attachment'
        status:
          type: string
          enum:
            - sent
            - read
          example: sent
        read_by:
          type: array
          items:
            $ref: '#/components/schemas/DirectReadReceipt'
        edited_at:
          $ref: '#/components/schemas/DateTime'
        deleted_at:
          $ref: '#/components/schemas/DateTime'
        deleted_by:
          $ref: '#/components/schemas/UUID'
        created_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    DirectMessagePage:
      type: object
      required:
        - messages
        - has_more
        - limit
      properties:
        messages:
          type: array
          items:
            $ref: '#/components/schemas/DirectMessage'
        next_cursor:
          $ref: '#/components/schemas/UUID'
        has_more:
          type: boolean
        limit:
          type: integer
          minimum: 1
      additionalProperties: false
    DirectMessageStatusUpdate:
      type: object
      required:
        - room_id
        - message_id
        - user_id
        - status
        - read_at
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        message_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
        status:
          type: string
          enum:
            - sent
            - read
        read_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    DirectMessageEvent:
      type: object
      required:
        - room_id
        - message_id
        - updated_at
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        message_id:
          $ref: '#/components/schemas/UUID'
        message:
          $ref: '#/components/schemas/DirectMessage'
        updated_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    DirectCallSession:
      type: object
      required:
        - id
        - room_id
        - initiator_id
        - status
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/UUID'
        room_id:
          $ref: '#/components/schemas/UUID'
        initiator_id:
          $ref: '#/components/schemas/UUID'
        status:
          type: string
          enum:
            - active
            - ended
            - declined
        created_at:
          $ref: '#/components/schemas/DateTime'
        ended_at:
          $ref: '#/components/schemas/DateTime'
      additionalProperties: false
    FriendRequestSnapshot:
      type: object
      required:
        - requests
        - has_more
        - limit
      properties:
        requests:
          type: array
          items:
            $ref: '#/components/schemas/FriendRequest'
        next_cursor:
          type: string
          nullable: true
          description: Opaque cursor returned by the previous page.
        has_more:
          type: boolean
        limit:
          type: integer
          minimum: 1
          maximum: 100
      additionalProperties: false
    FriendRequestsSnapshotRequestData:
      type: object
      properties:
        limit:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
        cursor:
          type: string
          description: Opaque cursor returned by `friend_requests_snapshot.next_cursor`.
      additionalProperties: false
    DirectCallRefData:
      type: object
      required:
        - room_id
        - call_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        call_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    TypingIndicatorData:
      type: object
      required:
        - room_id
        - user_id
        - is_typing
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
        scope:
          type: string
          enum:
            - room
            - direct
          default: room
        is_typing:
          type: boolean
      additionalProperties: false
    SfuTrackStateData:
      type: object
      required:
        - room_id
        - user_id
        - track_id
        - muted
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
        track_id:
          type: string
        kind:
          type: string
          example: audio
        source:
          type: string
          example: microphone
        muted:
          type: boolean
      additionalProperties: false
    SfuActiveSpeaker:
      type: object
      required:
        - user_id
      properties:
        user_id:
          $ref: '#/components/schemas/UUID'
        level:
          type: number
          format: float
      additionalProperties: true
    SfuActiveSpeakersData:
      type: object
      required:
        - room_id
        - speakers
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        sender_id:
          $ref: '#/components/schemas/UUID'
        speakers:
          type: array
          items:
            $ref: '#/components/schemas/SfuActiveSpeaker'
      additionalProperties: false
    SfuMediaStateData:
      type: object
      required:
        - room_id
        - user_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        user_id:
          $ref: '#/components/schemas/UUID'
        camera_enabled:
          type: boolean
          nullable: true
        screen_share_enabled:
          type: boolean
          nullable: true
        microphone_enabled:
          type: boolean
          nullable: true
        screen_audio_enabled:
          type: boolean
          nullable: true
        active_camera_track_id:
          type: string
        active_screen_track_id:
          type: string
        active_audio_track_id:
          type: string
        active_screen_audio_track_id:
          type: string
      additionalProperties: false
    UserPresenceData:
      type: object
      required:
        - user_id
        - status
      properties:
        user_id:
          $ref: '#/components/schemas/UUID'
        status:
          type: string
          example: online
        custom_status:
          type: string
          nullable: true
          example: coding
      additionalProperties: false
    SfuJoinData:
      type: object
      required:
        - room_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    SfuLeaveData:
      type: object
      required:
        - room_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        session_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    SessionAckData:
      type: object
      required:
        - room_id
        - scope
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        scope:
          type: string
          enum:
            - server
            - direct
        session_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    SdpPayload:
      type: object
      required:
        - sdp
        - type
      properties:
        sdp:
          type: string
        type:
          type: string
          enum:
            - offer
            - pranswer
            - answer
            - rollback
      additionalProperties: true
    SfuSignalToServerData:
      type: object
      required:
        - room_id
        - data
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        data:
          $ref: '#/components/schemas/SdpPayload'
      additionalProperties: false
    IceCandidate:
      type: object
      required:
        - candidate
      properties:
        candidate:
          type: string
        sdpMid:
          type: string
          nullable: true
        sdpMLineIndex:
          type: integer
          nullable: true
        usernameFragment:
          type: string
          nullable: true
      additionalProperties: true
    SfuIceToServerData:
      type: object
      required:
        - room_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
        data:
          nullable: true
          oneOf:
            - $ref: '#/components/schemas/IceCandidate'
          description: ICE candidate. Use null for Trickle ICE end-of-candidates.
      additionalProperties: false
    SfuIceRestartData:
      type: object
      required:
        - room_id
      properties:
        room_id:
          $ref: '#/components/schemas/UUID'
      additionalProperties: false
    SfuSignalFromServer:
      type: object
      required:
        - type
        - data
      properties:
        type:
          type: string
        room_id:
          type: string
          description: Present in SFU manager payloads, but may be an empty string in the current implementation.
        data:
          oneOf:
            - $ref: '#/components/schemas/SdpPayload'
            - $ref: '#/components/schemas/IceCandidate'
          nullable: true
          description: SDP payload, ICE candidate, or null for Trickle ICE end-of-candidates.
      additionalProperties: false
    ErrorData:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          example: not_subscribed
        message:
          type: string
          example: You must subscribe to the server first
      additionalProperties: false
  messages:
    SubscribeServer:
      name: SubscribeServer
      title: Subscribe to a server
      summary: Subscribe the current WebSocket connection to server-level state.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - subscribe_server
          data:
            $ref: '#/components/schemas/ServerSubscriptionData'
        additionalProperties: false
      examples:
        - name: subscribeServer
          payload:
            type: subscribe_server
            data:
              server_id: 8c2fd426-b799-4d4d-976d-18a35bf1b57f
    UnsubscribeServer:
      name: UnsubscribeServer
      title: Unsubscribe from a server
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - unsubscribe_server
          data:
            $ref: '#/components/schemas/ServerSubscriptionData'
        additionalProperties: false
    GetServerParticipants:
      name: GetServerParticipants
      title: Request server participants
      summary: Request server member info with current WebSocket online/subscription state.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - get_server_participants
          data:
            $ref: '#/components/schemas/ServerSubscriptionData'
        additionalProperties: false
    GetServerRooms:
      name: GetServerRooms
      title: Request server rooms snapshot
      summary: Request the full room list for a server using the same shape as the REST API.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - get_server_rooms
          data:
            $ref: '#/components/schemas/ServerSubscriptionData'
        additionalProperties: false
    GetRoomMessages:
      name: GetRoomMessages
      title: Request room message history
      summary: Request paginated room messages using the same cursor model as the REST API.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - get_room_messages
          data:
            $ref: '#/components/schemas/RoomMessagesSnapshotRequestData'
        additionalProperties: false
    JoinRoom:
      name: JoinRoom
      title: Join a server room
      summary: Join a room after subscribing to its server.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - join_room
          data:
            $ref: '#/components/schemas/RoomJoinData'
        additionalProperties: false
    LeaveRoom:
      name: LeaveRoom
      title: Leave a room
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - leave_room
          data:
            $ref: '#/components/schemas/RoomLeaveData'
        additionalProperties: false
    GetRTCRoomParticipants:
      name: GetRTCRoomParticipants
      title: Request RTC room participants
      summary: Request active SFU participants for an RTC room.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - get_rtc_room_participants
          data:
            $ref: '#/components/schemas/RoomJoinData'
        additionalProperties: false
    Chat:
      name: Chat
      title: Send a room chat message
      summary: Sends a text message to a joined room. The server persists it and broadcasts a room_chat_message event.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - chat
          nonce:
            type: string
            description: Optional client-generated request nonce echoed by the direct server reply.
          data:
            $ref: '#/components/schemas/ChatSendData'
        additionalProperties: false
      examples:
        - name: chat
          payload:
            type: chat
            nonce: 41e9a6a0f4b84683
            data:
              server_id: 8c2fd426-b799-4d4d-976d-18a35bf1b57f
              room_id: 7ba6fb0c-1e59-486a-bb31-fd73ddd6cbec
              content: hello from websocket
    EditMessage:
      name: EditMessage
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - edit_message
          data:
            $ref: '#/components/schemas/MessageEditData'
        additionalProperties: false
    DeleteMessage:
      name: DeleteMessage
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - delete_message
          data:
            $ref: '#/components/schemas/MessageRefData'
        additionalProperties: false
    MarkMessageRead:
      name: MarkMessageRead
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - mark_message_read
          data:
            $ref: '#/components/schemas/MessageRefData'
        additionalProperties: false
    PinMessage:
      name: PinMessage
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - pin_message
          data:
            $ref: '#/components/schemas/MessageRefData'
        additionalProperties: false
    UnpinMessage:
      name: UnpinMessage
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - unpin_message
          data:
            $ref: '#/components/schemas/MessageRefData'
        additionalProperties: false
    TypingIndicatorCommand:
      name: TypingIndicatorCommand
      title: Send typing indicator
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - typing_indicator
          data:
            type: object
            required:
              - room_id
              - is_typing
            properties:
              room_id:
                $ref: '#/components/schemas/UUID'
              scope:
                type: string
                enum:
                  - room
                  - direct
              is_typing:
                type: boolean
            additionalProperties: false
        additionalProperties: false
    DirectRoomCreate:
      name: DirectRoomCreate
      title: Create direct room
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_room_create
          data:
            $ref: '#/components/schemas/DirectRoomCreateData'
        additionalProperties: false
    GetDirectMessages:
      name: GetDirectMessages
      title: Request direct message history
      summary: Request paginated direct messages using the same cursor model as the REST API.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - get_direct_messages
          data:
            $ref: '#/components/schemas/DirectMessagesSnapshotRequestData'
        additionalProperties: false
    GetFriendRequests:
      name: GetFriendRequests
      title: Request friend requests snapshot
      payload:
        type: object
        required:
          - type
        properties:
          type:
            type: string
            enum:
              - get_friend_requests
          data:
            $ref: '#/components/schemas/FriendRequestsSnapshotRequestData'
        additionalProperties: false
    DirectMessage:
      name: DirectMessage
      title: Send direct message
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_message
          data:
            $ref: '#/components/schemas/DirectMessageSendData'
        additionalProperties: false
    DirectMessageEdit:
      name: DirectMessageEdit
      title: Edit direct message
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_message_edit
          data:
            $ref: '#/components/schemas/DirectMessageEditData'
        additionalProperties: false
    DirectMessageDelete:
      name: DirectMessageDelete
      title: Delete direct message
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_message_delete
          data:
            $ref: '#/components/schemas/DirectMessageRefData'
        additionalProperties: false
    DirectMessageRead:
      name: DirectMessageRead
      title: Mark direct message as read
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_message_read
          data:
            $ref: '#/components/schemas/DirectMessageRefData'
        additionalProperties: false
    DirectCallStart:
      name: DirectCallStart
      title: Start direct call
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_call_start
          data:
            type: object
            required:
              - room_id
            properties:
              room_id:
                $ref: '#/components/schemas/UUID'
            additionalProperties: false
        additionalProperties: false
    DirectCallEnd:
      name: DirectCallEnd
      title: End direct call
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_call_end
          data:
            $ref: '#/components/schemas/DirectCallRefData'
        additionalProperties: false
    DirectCallDecline:
      name: DirectCallDecline
      title: Decline direct call
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_call_decline
          data:
            $ref: '#/components/schemas/DirectCallRefData'
        additionalProperties: false
    SFUJoin:
      name: SFUJoin
      title: Join SFU room
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_join
          data:
            $ref: '#/components/schemas/SfuJoinData'
        additionalProperties: false
    SFULeave:
      name: SFULeave
      title: Leave SFU room
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_leave
          data:
            $ref: '#/components/schemas/SfuLeaveData'
        additionalProperties: false
    SFUOfferToServer:
      name: SFUOfferToServer
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_offer
          data:
            $ref: '#/components/schemas/SfuSignalToServerData'
        additionalProperties: false
    SFUAnswerToServer:
      name: SFUAnswerToServer
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_answer
          data:
            $ref: '#/components/schemas/SfuSignalToServerData'
        additionalProperties: false
    SFUIceCandidateToServer:
      name: SFUIceCandidateToServer
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_ice_candidate
          data:
            $ref: '#/components/schemas/SfuIceToServerData'
        additionalProperties: false
    SFUIceRestartToServer:
      name: SFUIceRestartToServer
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_ice_restart
          data:
            $ref: '#/components/schemas/SfuIceRestartData'
        additionalProperties: false
    SFUParticipantMutedToServer:
      name: SFUParticipantMutedToServer
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_participant_muted
          data:
            $ref: '#/components/schemas/SfuTrackStateData'
        additionalProperties: false
    SFUParticipantUnmutedToServer:
      name: SFUParticipantUnmutedToServer
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_participant_unmuted
          data:
            $ref: '#/components/schemas/SfuTrackStateData'
        additionalProperties: false
    SFUActiveSpeakersToServer:
      name: SFUActiveSpeakersToServer
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_active_speakers
          data:
            $ref: '#/components/schemas/SfuActiveSpeakersData'
        additionalProperties: false
    SFUMediaStateToServer:
      name: SFUMediaStateToServer
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_media_state
          data:
            $ref: '#/components/schemas/SfuMediaStateData'
        additionalProperties: false
    SubscribedServerAck:
      name: SubscribedServerAck
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - subscribed_server
          data:
            $ref: '#/components/schemas/ServerSubscriptionData'
        additionalProperties: false
    UnsubscribedServerAck:
      name: UnsubscribedServerAck
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - unsubscribed_server
          data:
            $ref: '#/components/schemas/ServerSubscriptionData'
        additionalProperties: false
    ServerParticipants:
      name: ServerParticipants
      title: Server participants snapshot
      summary: Sent after `get_server_participants` and broadcast to subscribed users when server subscription state changes.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - server_participants
          data:
            $ref: '#/components/schemas/ServerParticipantsData'
        additionalProperties: false
    RoomMessagesSnapshot:
      name: RoomMessagesSnapshot
      title: Room messages snapshot
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - room_messages_snapshot
          data:
            $ref: '#/components/schemas/MessagePage'
        additionalProperties: false
    ServerUpdated:
      name: ServerUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - server_updated
          data:
            oneOf:
              - $ref: '#/components/schemas/Server'
              - type: object
        additionalProperties: false
    ServerMemberRemoved:
      name: ServerMemberRemoved
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - server_member_removed
          data:
            $ref: '#/components/schemas/ServerMemberRemovedData'
        additionalProperties: false
    ServerRoleUpdated:
      name: ServerRoleUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - server_role_updated
          data:
            $ref: '#/components/schemas/ServerRoleUpdatedData'
        additionalProperties: false
    JoinedRoomAck:
      name: JoinedRoomAck
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - joined_room
          data:
            $ref: '#/components/schemas/RoomJoinData'
        additionalProperties: false
    LeftRoomAck:
      name: LeftRoomAck
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - left_room
          data:
            $ref: '#/components/schemas/RoomLeaveData'
        additionalProperties: false
    RTCRoomParticipants:
      name: RTCRoomParticipants
      title: RTC room participants snapshot
      summary: Sent after `get_rtc_room_participants` and broadcast to room subscribers after SFU join/leave.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - rtc_room_participants
          data:
            $ref: '#/components/schemas/RTCRoomParticipantsData'
        additionalProperties: false
    ServerRoomsSnapshot:
      name: ServerRoomsSnapshot
      title: Server rooms snapshot
      summary: Sent after `get_server_rooms`.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - server_rooms_snapshot
          data:
            $ref: '#/components/schemas/ServerRoomsSnapshotData'
        additionalProperties: false
    RoomCreated:
      name: RoomCreated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - room_created
          data:
            $ref: '#/components/schemas/Room'
        additionalProperties: false
    RoomUpdated:
      name: RoomUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - room_updated
          data:
            oneOf:
              - $ref: '#/components/schemas/Room'
              - type: object
        additionalProperties: false
    RoomDeleted:
      name: RoomDeleted
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - room_deleted
          data:
            $ref: '#/components/schemas/DeletedRoomData'
        additionalProperties: false
    RoomChatMessage:
      name: RoomChatMessage
      title: Room chat message
      summary: Broadcast after a room chat message is created.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - room_chat_message
          data:
            $ref: '#/components/schemas/Message'
        additionalProperties: false
    TypingIndicator:
      name: TypingIndicator
      title: Typing indicator
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - typing_indicator
          data:
            $ref: '#/components/schemas/TypingIndicatorData'
        additionalProperties: false
    MessageUpdated:
      name: MessageUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - message_updated
          data:
            $ref: '#/components/schemas/MessageEvent'
        additionalProperties: false
    MessageDeleted:
      name: MessageDeleted
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - message_deleted
          data:
            $ref: '#/components/schemas/MessageEvent'
        additionalProperties: false
    MessageStatusUpdated:
      name: MessageStatusUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - message_status_updated
          data:
            $ref: '#/components/schemas/MessageStatusUpdate'
        additionalProperties: false
    MessagePinUpdated:
      name: MessagePinUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - message_pin_updated
          data:
            $ref: '#/components/schemas/MessageEvent'
        additionalProperties: false
    MessageReactionUpdated:
      name: MessageReactionUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - message_reaction_updated
          data:
            $ref: '#/components/schemas/MessageReactionUpdate'
        additionalProperties: false
    DirectRoomCreated:
      name: DirectRoomCreated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_room_created
          data:
            $ref: '#/components/schemas/DirectRoom'
        additionalProperties: false
    DirectRoomUpdated:
      name: DirectRoomUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_room_updated
          data:
            $ref: '#/components/schemas/DirectRoom'
        additionalProperties: false
    DirectRoomLeft:
      name: DirectRoomLeft
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_room_left
          data:
            $ref: '#/components/schemas/DirectRoomLeftData'
        additionalProperties: false
    DirectMessagesSnapshot:
      name: DirectMessagesSnapshot
      title: Direct messages snapshot
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_messages_snapshot
          data:
            $ref: '#/components/schemas/DirectMessagePage'
        additionalProperties: false
    DirectMessageCreated:
      name: DirectMessageCreated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_message_created
          data:
            $ref: '#/components/schemas/DirectMessage'
        additionalProperties: false
    DirectMessageUpdated:
      name: DirectMessageUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_message_updated
          data:
            $ref: '#/components/schemas/DirectMessageEvent'
        additionalProperties: false
    DirectMessageDeleted:
      name: DirectMessageDeleted
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_message_deleted
          data:
            $ref: '#/components/schemas/DirectMessageEvent'
        additionalProperties: false
    DirectMessageStatusUpdated:
      name: DirectMessageStatusUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_message_status_updated
          data:
            $ref: '#/components/schemas/DirectMessageStatusUpdate'
        additionalProperties: false
    DirectCallStarted:
      name: DirectCallStarted
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_call_started
          data:
            $ref: '#/components/schemas/DirectCallSession'
        additionalProperties: false
    DirectCallEnded:
      name: DirectCallEnded
      summary: Direct call ended.
      description: >
        Broadcast to all direct-room members when a direct call is explicitly ended
        or when the backing direct SFU room is closed because a participant leaves
        or disconnects. Clients should treat this as the canonical call-ended event;
        late SFU signaling for the closed direct room may be ignored.
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_call_ended
          data:
            $ref: '#/components/schemas/DirectCallSession'
        additionalProperties: false
    DirectCallDeclined:
      name: DirectCallDeclined
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - direct_call_declined
          data:
            $ref: '#/components/schemas/DirectCallSession'
        additionalProperties: false
    FriendRequestReceived:
      name: FriendRequestReceived
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - friend_request_received
          data:
            $ref: '#/components/schemas/FriendRequestEventData'
        additionalProperties: false
    FriendRequestAccepted:
      name: FriendRequestAccepted
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - friend_request_accepted
          data:
            $ref: '#/components/schemas/FriendRequestEventData'
        additionalProperties: false
    FriendRequestsSnapshot:
      name: FriendRequestsSnapshot
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - friend_requests_snapshot
          data:
            $ref: '#/components/schemas/FriendRequestSnapshot'
        additionalProperties: false
    FriendRequestRejected:
      name: FriendRequestRejected
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - friend_request_rejected
          data:
            $ref: '#/components/schemas/FriendRequestEventData'
        additionalProperties: false
    FriendRemoved:
      name: FriendRemoved
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - friend_removed
          data:
            $ref: '#/components/schemas/FriendRelationEventData'
        additionalProperties: false
    UserPresenceUpdated:
      name: UserPresenceUpdated
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - user_presence_updated
          data:
            $ref: '#/components/schemas/UserPresenceData'
        additionalProperties: false
    SFUJoinedAck:
      name: SFUJoinedAck
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_joined
          data:
            $ref: '#/components/schemas/SessionAckData'
        additionalProperties: false
    SFULeftAck:
      name: SFULeftAck
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_left
          data:
            $ref: '#/components/schemas/RoomLeaveData'
        additionalProperties: false
    SFUOfferFromServer:
      name: SFUOfferFromServer
      payload:
        allOf:
          - $ref: '#/components/schemas/SfuSignalFromServer'
          - type: object
            properties:
              type:
                type: string
                enum:
                  - sfu_offer
    SFUAnswerFromServer:
      name: SFUAnswerFromServer
      payload:
        allOf:
          - $ref: '#/components/schemas/SfuSignalFromServer'
          - type: object
            properties:
              type:
                type: string
                enum:
                  - sfu_answer
    SFUIceCandidateFromServer:
      name: SFUIceCandidateFromServer
      payload:
        allOf:
          - $ref: '#/components/schemas/SfuSignalFromServer'
          - type: object
            properties:
              type:
                type: string
                enum:
                  - sfu_ice_candidate
    SFUParticipantMuted:
      name: SFUParticipantMuted
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_participant_muted
          data:
            $ref: '#/components/schemas/SfuTrackStateData'
        additionalProperties: false
    SFUParticipantUnmuted:
      name: SFUParticipantUnmuted
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_participant_unmuted
          data:
            $ref: '#/components/schemas/SfuTrackStateData'
        additionalProperties: false
    SFUActiveSpeakers:
      name: SFUActiveSpeakers
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_active_speakers
          data:
            $ref: '#/components/schemas/SfuActiveSpeakersData'
        additionalProperties: false
    SFUMediaState:
      name: SFUMediaState
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - sfu_media_state
          data:
            $ref: '#/components/schemas/SfuMediaStateData'
        additionalProperties: false
    Error:
      name: Error
      payload:
        type: object
        required:
          - type
          - data
        properties:
          type:
            type: string
            enum:
              - error
          data:
            $ref: '#/components/schemas/ErrorData'
        additionalProperties: false
