openapi: 3.1.0
info:
  title: Sayna API
  description: Real-time voice processing server with Speech-to-Text (STT) and Text-to-Speech (TTS) services
  contact:
    name: Sayna
    url: https://api.sayna.ai
  license:
    name: Apache-2.0
    identifier: Apache-2.0
  version: 0.1.0
servers:
- url: https://api.sayna.ai
  description: Production API
- url: http://localhost:3001
  description: Local development
paths:
  /:
    get:
      tags:
      - health
      summary: |-
        Health check handler
        Returns a simple JSON response indicating the server is running
      operationId: health_check
      responses:
        '200':
          description: Server is healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
  /livekit/participant:
    delete:
      tags:
      - livekit
      summary: Handler for DELETE /livekit/participant endpoint
      description: |-
        Removes a participant from a LiveKit room, forcibly disconnecting them.
        Access is authorized via `metadata.auth_id` check.

        Note: This does not invalidate the participant's token. To prevent rejoining,
        use short-lived tokens and avoid issuing new tokens to removed participants.

        # Arguments
        * `state` - Shared application state containing LiveKit configuration
        * `auth` - Authentication context from middleware
        * `request` - Request with room name and participant identity

        # Returns
        * `Response` - JSON response with removal status or error

        # Authorization
        - When `auth.id` is present: Requires `room.metadata.auth_id == auth.id`
        - When `auth.id` is absent: Access is allowed (backward-compatible mode)

        # Errors
        * 400 Bad Request - Empty room name or participant identity
        * 404 Not Found - Room/participant not found or access denied (masked)
        * 500 Internal Server Error - LiveKit not configured or removal failed

        # Flow
        1. Validate request fields are not empty
        2. Check room access via metadata.auth_id
        3. Verify participant exists in the room
        4. Remove the participant
        5. Return success or appropriate error
      operationId: remove_participant
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RemoveParticipantRequest'
        required: true
      responses:
        '200':
          description: Participant removed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveParticipantResponse'
        '400':
          description: Invalid request (empty fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveParticipantErrorResponse'
        '404':
          description: Participant not found in room
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveParticipantErrorResponse'
        '500':
          description: LiveKit not configured or removal failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveParticipantErrorResponse'
      security:
      - auth: []
  /livekit/participant/mute:
    post:
      tags:
      - livekit
      summary: Handler for POST /livekit/participant/mute endpoint
      description: |-
        Mutes or unmutes a participant's published track.
        Access is authorized via `metadata.auth_id` check.

        # Arguments
        * `state` - Shared application state containing LiveKit configuration
        * `auth` - Authentication context from middleware
        * `request` - Request with room name, participant identity, track_sid, and muted state

        # Returns
        * `Response` - JSON response with mute status or error

        # Authorization
        - When `auth.id` is present: Requires `room.metadata.auth_id == auth.id`
        - When `auth.id` is absent: Access is allowed (backward-compatible mode)

        # Errors
        * 400 Bad Request - Empty fields in request
        * 404 Not Found - Room/participant not found or access denied (masked)
        * 500 Internal Server Error - LiveKit not configured or mute operation failed
      operationId: mute_participant
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MuteParticipantRequest'
        required: true
      responses:
        '200':
          description: Track muted/unmuted successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MuteParticipantResponse'
        '400':
          description: Invalid request (empty fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveParticipantErrorResponse'
        '404':
          description: Room or participant not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveParticipantErrorResponse'
        '500':
          description: LiveKit not configured or mute failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RemoveParticipantErrorResponse'
      security:
      - auth: []
  /livekit/rooms:
    get:
      tags:
      - livekit
      summary: Handler for GET /livekit/rooms endpoint
      description: |-
        Lists all LiveKit rooms belonging to the authenticated client.
        Rooms are filtered by `metadata.auth_id` for tenant isolation.

        # Arguments
        * `state` - Shared application state containing LiveKit configuration
        * `auth` - Authentication context from middleware

        # Returns
        * `Response` - JSON response with rooms list or error status

        # Filtering
        - When `auth.id` is present: Returns only rooms where `metadata.auth_id == auth.id`
        - When `auth.id` is absent: Returns all rooms (backward-compatible mode)

        # Errors
        * 500 Internal Server Error - LiveKit service not configured or API call failed
      operationId: list_rooms
      responses:
        '200':
          description: Rooms listed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ListRoomsResponse'
        '500':
          description: LiveKit service not configured or failed to list rooms
      security:
      - auth: []
  /livekit/rooms/{room_name}:
    get:
      tags:
      - livekit
      summary: Handler for GET /livekit/rooms/{room_name} endpoint
      description: |-
        Returns detailed information about a specific LiveKit room including
        all current participants. Access is authorized via `metadata.auth_id` check.

        # Arguments
        * `state` - Shared application state containing LiveKit configuration
        * `auth` - Authentication context from middleware
        * `room_name` - Name of the room to retrieve (from path parameter)

        # Returns
        * `Response` - JSON response with room details or error status

        # Authorization
        - When `auth.id` is present: Requires `room.metadata.auth_id == auth.id`
        - When `auth.id` is absent: Access is allowed (backward-compatible mode)

        # Errors
        * 404 Not Found - Room not found or access denied (masked as not found)
        * 500 Internal Server Error - LiveKit service not configured or API call failed
      operationId: get_room_details
      parameters:
      - name: room_name
        in: path
        description: Name of the room to retrieve
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Room details retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RoomDetailsResponse'
        '404':
          description: Room not found or not accessible
        '500':
          description: LiveKit service not configured or failed to get room details
      security:
      - auth: []
  /livekit/token:
    post:
      tags:
      - livekit
      summary: Handler for POST /livekit/token endpoint
      description: |-
        Generates a LiveKit JWT token for a participant to join a specific room.

        When authentication is enabled (`auth.id` is present), this handler:
        1. Creates the room if it doesn't exist
        2. Sets `room.metadata.auth_id` to the authenticated tenant's ID
        3. Issues the token only after metadata is verified/set

        # Arguments
        * `state` - Shared application state containing LiveKit configuration
        * `request` - Token request with room name and participant details

        # Returns
        * `Response` - JSON response with token or error status

        # Errors
        * 400 Bad Request - Invalid request data (empty fields)
        * 403 Forbidden - Room exists with a different tenant's `auth_id`
        * 500 Internal Server Error - LiveKit service not configured, room creation failed, or token generation failed
      operationId: generate_token
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TokenRequest'
        required: true
      responses:
        '200':
          description: Token generated successfully. Room is created if it doesn't exist and metadata.auth_id is set.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        '400':
          description: Invalid request (missing or empty fields)
        '403':
          description: 'Access denied: room exists with a different tenant''s auth_id'
        '500':
          description: LiveKit service not configured, room creation failed, or token generation failed
      security:
      - auth: []
  /recording/{stream_id}:
    get:
      tags:
      - recordings
      summary: Download recording by stream ID from configured object storage
      operationId: download_recording
      parameters:
      - name: stream_id
        in: path
        description: Recording stream identifier
        required: true
        schema:
          type: string
        example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        '200':
          description: Recording retrieved successfully
          headers:
            Content-Disposition:
              schema:
                type: string
              description: Suggested filename for download
            Content-Length:
              schema:
                type: integer
                format: int64
                minimum: 0
              description: Size of the recording in bytes
          content:
            audio/ogg: {}
        '400':
          description: Invalid stream_id format
        '404':
          description: Recording not found
        '503':
          description: Recording storage not configured or unavailable
      security:
      - auth: []
  /sip/call:
    post:
      tags:
      - sip
      summary: Handler for POST /sip/call endpoint
      description: Initiates an outbound SIP call through LiveKit. The optional `sip` object in the request body allows overriding global SIP configuration on a per-request basis. Request body values take priority over global config, enabling different SIP providers or credentials for specific calls.
      operationId: sip_call
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SIPCallRequest'
        required: true
      responses:
        '200':
          description: Call initiated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIPCallResponse'
        '400':
          description: Invalid request (bad phone number or empty fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIPCallErrorResponse'
        '404':
          description: Room not found or not accessible
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIPCallErrorResponse'
        '500':
          description: LiveKit not configured, outbound address missing, or call failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIPCallErrorResponse'
      security:
      - auth: []
  /sip/hooks:
    get:
      tags:
      - sip
      summary: Lists all configured SIP hooks.
      description: |-
        Returns the current list of SIP hooks from runtime state.
        This endpoint requires authentication if `AUTH_REQUIRED=true`.

        # Returns
        * `200 OK` - List of SIP hooks
        * `500 Internal Server Error` - If reading the cache fails
      operationId: list_sip_hooks
      responses:
        '200':
          description: List of SIP hooks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksResponse'
        '500':
          description: Failed to read hooks cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksErrorResponse'
      security:
      - auth: []
    post:
      tags:
      - sip
      summary: Updates SIP hooks.
      description: |-
        Adds or replaces SIP hooks in the cache. Hooks with matching hosts
        (case-insensitive) will be replaced. Hosts defined in the application
        configuration cannot be modified. The changes take effect immediately
        and persist across server restarts.

        **Note**: Secrets are NOT stored in the cache. Runtime-added hooks will
        use the global `hook_secret` from the server configuration.

        # Request Body
        Array of hook entries with `host` and `url` fields.

        # Returns
        * `200 OK` - Updated list of SIP hooks
        * `400 Bad Request` - If validation fails (e.g., duplicate hosts)
        * `500 Internal Server Error` - If writing the cache fails
      operationId: update_sip_hooks
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SipHooksRequest'
        required: true
      responses:
        '200':
          description: Updated list of SIP hooks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksResponse'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksErrorResponse'
        '405':
          description: Host defined in application config cannot be modified
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksErrorResponse'
        '500':
          description: Failed to write hooks cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksErrorResponse'
      security:
      - auth: []
    delete:
      tags:
      - sip
      summary: Deletes SIP hooks by host name.
      description: |-
        Removes the specified hosts from the cache. Hosts defined in the application
        configuration cannot be removed. If a host exists in the original server
        configuration, it will revert to its config value after deletion of a
        cached override. Hosts that only exist in cache will be completely removed.

        The changes take effect immediately and persist across server restarts.

        # Request Body
        Array of host names to remove (case-insensitive).

        # Returns
        * `200 OK` - Updated list of SIP hooks after deletion
        * `400 Bad Request` - If the hosts array is empty
        * `500 Internal Server Error` - If writing the cache fails
      operationId: delete_sip_hooks
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeleteSipHooksRequest'
        required: true
      responses:
        '200':
          description: Updated list of SIP hooks after deletion
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksResponse'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksErrorResponse'
        '405':
          description: Host defined in application config cannot be modified
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksErrorResponse'
        '500':
          description: Failed to write hooks cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SipHooksErrorResponse'
      security:
      - auth: []
  /sip/transfer:
    post:
      tags:
      - sip
      summary: Handler for POST /sip/transfer endpoint
      description: |-
        Initiates a SIP REFER transfer for a participant in a LiveKit room.
        The transfer moves an ongoing SIP call to a different phone number.

        # Arguments
        * `state` - Shared application state containing LiveKit handlers
        * `auth` - Authentication context for room metadata authorization
        * `request` - Transfer request with room name, participant identity, and destination

        # Returns
        * `Response` - JSON response with transfer status or error

        # Errors
        * 400 Bad Request - Invalid phone number format or empty fields
        * 404 Not Found - Room not found, not accessible, or participant not found
        * 500 Internal Server Error - LiveKit not configured or transfer operation failed

        # Flow
        1. Validate the phone number format
        2. Check LiveKit handlers are configured
        3. Check room access via metadata auth_id
        4. Verify the participant exists and is a SIP participant
        5. Execute the SIP transfer
        6. Return success or appropriate error
      operationId: sip_transfer
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SIPTransferRequest'
        required: true
      responses:
        '200':
          description: Transfer initiated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIPTransferResponse'
        '400':
          description: Invalid request (bad phone number or empty fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIPTransferErrorResponse'
        '404':
          description: Room not found, not accessible, or participant not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIPTransferErrorResponse'
        '500':
          description: LiveKit not configured or transfer failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SIPTransferErrorResponse'
      security:
      - auth: []
  /speak:
    post:
      tags:
      - tts
      summary: Handler for the /speak endpoint
      operationId: speak_handler
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SpeakRequest'
        required: true
      responses:
        '200':
          description: Audio generated successfully
          headers:
            x-audio-format:
              schema:
                type: string
              description: Audio format (linear16, mp3, etc.)
            x-sample-rate:
              schema:
                type: integer
                format: int32
                minimum: 0
              description: Sample rate in Hz
          content:
            audio/pcm: {}
        '400':
          description: Invalid request (empty text)
        '500':
          description: TTS synthesis failed
      security:
      - auth: []
  /voices:
    get:
      tags:
      - voices
      summary: Handler for GET /voices - returns available voices per provider
      operationId: list_voices
      responses:
        '200':
          description: Available voices grouped by provider
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: array
                  items:
                    $ref: '#/components/schemas/Voice'
                propertyNames:
                  type: string
        '500':
          description: Internal server error
      security:
      - auth: []
components:
  schemas:
    DeleteSipHooksRequest:
      type: object
      description: |-
        Request body for deleting SIP hooks.

        Contains a list of host names to remove from the SIP hooks cache.
      properties:
        hosts:
          type: array
          items:
            type: string
          description: |-
            List of host names to remove (case-insensitive).
            Hosts that exist in the original config will revert to their config values.
          example:
          - example.com
          - other.com
    HealthResponse:
      type: object
      description: Health check response
      required:
      - status
      properties:
        status:
          type: string
          description: Server status
          example: OK
    IncomingMessage:
      oneOf:
      - type: object
        required:
        - type
        properties:
          audio:
            type:
            - boolean
            - 'null'
            description: Enable audio processing (STT/TTS). Defaults to true if not specified.
          livekit:
            oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/LiveKitWebSocketConfig'
              description: Optional LiveKit configuration for real-time audio streaming
          loading_audio:
            oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/LoadingAudioConfig'
              description: Optional loading-indicator audio configuration.
          stream_id:
            type:
            - string
            - 'null'
            description: |-
              Optional unique identifier for this WebSocket session.
              Used for recording paths and session tracking.
              If not provided, a UUID v4 will be auto-generated by the server.
            example: 550e8400-e29b-41d4-a716-446655440000
          stt_config:
            oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/STTWebSocketConfig'
              description: STT configuration (required only when audio=true)
          tts_config:
            oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/TTSWebSocketConfig'
              description: TTS configuration (required only when audio=true)
          type:
            type: string
            enum:
            - config
      - type: object
        required:
        - text
        - type
        properties:
          allow_interruption:
            type:
            - boolean
            - 'null'
            description: Allow this TTS to be interrupted
          flush:
            type:
            - boolean
            - 'null'
            description: Flush TTS buffer immediately
          text:
            type: string
            description: Text to synthesize
          type:
            type: string
            enum:
            - speak
      - type: object
        required:
        - type
        properties:
          type:
            type: string
            enum:
            - clear
      - type: object
        required:
        - message
        - role
        - type
        properties:
          debug:
            description: Optional debug metadata
          message:
            type: string
            description: Message content
          role:
            type: string
            description: Message role (e.g., "user", "assistant")
          topic:
            type:
            - string
            - 'null'
            description: Optional topic/channel
          type:
            type: string
            enum:
            - send_message
      - type: object
        description: |-
          Transfer the current SIP call to another phone number.

          This command initiates a SIP REFER transfer for the current participant
          in the LiveKit room. The participant identity and room name are derived
          from the WebSocket connection state.
        required:
        - transfer_to
        - type
        properties:
          transfer_to:
            type: string
            description: |-
              The destination phone number to transfer the call to.

              Accepts phone numbers in various formats:
              - International format with `+` prefix (e.g., "+1234567890")
              - National format without prefix (e.g., "1234567890")
              - Internal extensions (e.g., "1234")

              Validation is performed by the handler, not during deserialization.
            example: '+1234567890'
          type:
            type: string
            enum:
            - sip_transfer
      - type: object
        description: |-
          Update runtime configuration for VAD and turn detection.

          This message allows dynamic adjustment of VAD and turn detection parameters
          during an active WebSocket session. Only parameters that are provided will
          be updated; omitted parameters retain their current values.

          **Note**: These settings affect the shared turn detector and may impact
          other active sessions using the same server instance.
        required:
        - type
        properties:
          turn_detect:
            oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/TurnDetectConfigUpdate'
              description: Turn detection configuration updates (probability threshold)
          type:
            type: string
            enum:
            - update_config
          vad:
            oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/VADConfigUpdate'
              description: VAD configuration updates (silence threshold, etc.)
      - type: object
        description: Begin looping the configured loading-indicator audio into the LiveKit room.
        required:
        - type
        properties:
          type:
            type: string
            enum:
            - loading_start
      - type: object
        description: Stop the loading-indicator audio loop (with a short fade-out).
        required:
        - type
        properties:
          type:
            type: string
            enum:
            - loading_stop
      description: WebSocket message types for incoming messages
    ListRoomsResponse:
      type: object
      description: |-
        Response containing the list of LiveKit rooms

        # Example
        ```json
        {
          "rooms": [
            {
              "name": "room-1",
              "num_participants": 2,
              "creation_time": 1703123456
            },
            {
              "name": "room-2",
              "num_participants": 0,
              "creation_time": 1703123789
            }
          ]
        }
        ```
      required:
      - rooms
      properties:
        rooms:
          type: array
          items:
            $ref: '#/components/schemas/RoomInfo'
          description: List of rooms belonging to the authenticated client (filtered by metadata.auth_id)
    LiveKitWebSocketConfig:
      type: object
      description: LiveKit configuration for WebSocket messages
      required:
      - room_name
      properties:
        enable_recording:
          type: boolean
          description: Enable recording for this session
        listen_participants:
          type: array
          items:
            type: string
          description: |-
            List of participant identities to listen to for audio tracks and data messages. (All participants by default)

            **Behavior**:
            - If **empty** (default): Audio tracks and data messages from **all participants** will be processed
            - If **populated**: Only audio tracks and data messages from participants whose identities
              are in this list will be processed; others will be ignored
        room_name:
          type: string
          description: Room name to join or create
          example: conversation-room-123
        sayna_participant_identity:
          type:
          - string
          - 'null'
          description: Sayna AI participant identity (defaults to "sayna-ai")
          example: sayna-ai
        sayna_participant_name:
          type:
          - string
          - 'null'
          description: Sayna AI participant display name (defaults to "Sayna AI")
          example: Sayna AI
    LoadingAudioConfig:
      type: object
      description: Loading-indicator audio configuration supplied in the `config` message.
      required:
      - data
      properties:
        channels:
          type:
          - integer
          - 'null'
          format: int32
          description: Channel count for raw PCM (1 = mono, 2 = stereo); defaults to 1. Ignored for WAV.
          example: 1
          minimum: 0
        data:
          type: string
          description: Base64-encoded audio bytes — a complete WAV file or raw 16-bit PCM.
        format:
          type:
          - string
          - 'null'
          description: 'Audio format: "wav" or "pcm". If omitted, the server auto-detects.'
          example: wav
        sample_rate:
          type:
          - integer
          - 'null'
          format: int32
          description: Sample rate in Hz. Required for raw PCM; ignored for WAV (header is authoritative).
          example: 16000
          minimum: 0
        volume:
          type:
          - number
          - 'null'
          format: float
          description: Playback volume from 0.0 (silent) to 1.0 (authored level). Default 1.0; out-of-range clamped.
          example: 0.3
    MuteParticipantRequest:
      type: object
      description: |-
        Request body for muting/unmuting a participant's track

        # Example
        ```json
        {
          "room_name": "conversation-room-123",
          "participant_identity": "user-alice-456",
          "track_sid": "TR_abc123",
          "muted": true
        }
        ```
      required:
      - room_name
      - participant_identity
      - track_sid
      - muted
      properties:
        muted:
          type: boolean
          description: True to mute, false to unmute
          example: true
        participant_identity:
          type: string
          description: The identity of the participant whose track to mute
          example: user-alice-456
        room_name:
          type: string
          description: The LiveKit room name where the participant is connected
          example: conversation-room-123
        track_sid:
          type: string
          description: The session ID of the track to mute/unmute
          example: TR_abc123
    MuteParticipantResponse:
      type: object
      description: |-
        Response for a successful mute/unmute operation

        # Example
        ```json
        {
          "room_name": "conversation-room-123",
          "participant_identity": "user-alice-456",
          "track_sid": "TR_abc123",
          "muted": true
        }
        ```
      required:
      - room_name
      - participant_identity
      - track_sid
      - muted
      properties:
        muted:
          type: boolean
          description: Current muted state
          example: true
        participant_identity:
          type: string
          description: The identity of the participant
          example: user-alice-456
        room_name:
          type: string
          description: The room name
          example: conversation-room-123
        track_sid:
          type: string
          description: The session ID of the track
          example: TR_abc123
    OutgoingMessage:
      oneOf:
      - type: object
        required:
        - stream_id
        - type
        properties:
          livekit_room_name:
            type:
            - string
            - 'null'
            description: Optional LiveKit room name that was created
          livekit_url:
            type:
            - string
            - 'null'
            description: Optional LiveKit URL to connect to
          sayna_participant_identity:
            type:
            - string
            - 'null'
            description: Optional identity of the AI agent participant in the room
          sayna_participant_name:
            type:
            - string
            - 'null'
            description: Optional display name of the AI agent participant
          stream_id:
            type: string
            description: |-
              Unique identifier for this WebSocket session.
              Either provided by the client in config or auto-generated by server (UUID v4).
            example: 550e8400-e29b-41d4-a716-446655440000
          type:
            type: string
            enum:
            - ready
      - type: object
        required:
        - transcript
        - is_final
        - is_speech_final
        - confidence
        - type
        properties:
          confidence:
            type: number
            format: float
            description: Confidence score (0.0 to 1.0)
          is_final:
            type: boolean
            description: Whether this is the final version of the transcript
          is_speech_final:
            type: boolean
            description: Whether speech has ended
          transcript:
            type: string
            description: Transcribed text
          type:
            type: string
            enum:
            - stt_result
      - type: object
        required:
        - message
        - type
        properties:
          message:
            $ref: '#/components/schemas/UnifiedMessage'
            description: Unified message structure containing text/data from various sources
          type:
            type: string
            enum:
            - message
      - type: object
        required:
        - participant
        - type
        properties:
          participant:
            $ref: '#/components/schemas/ParticipantConnectedInfo'
            description: Information about the participant who connected
          type:
            type: string
            enum:
            - participant_connected
      - type: object
        required:
        - participant
        - type
        properties:
          participant:
            $ref: '#/components/schemas/ParticipantDisconnectedInfo'
            description: Information about the participant who disconnected
          type:
            type: string
            enum:
            - participant_disconnected
      - type: object
        description: |-
          Track subscription notification

          Emitted when Sayna subscribes to a participant's track in the LiveKit room.
          This indicates that Sayna has started processing the participant's media.
        required:
        - track
        - type
        properties:
          track:
            $ref: '#/components/schemas/TrackSubscribedInfo'
            description: Information about the subscribed track
          type:
            type: string
            enum:
            - track_subscribed
      - type: object
        description: TTS playback completion notification
        required:
        - timestamp
        - type
        properties:
          timestamp:
            type: integer
            format: int64
            description: Timestamp when completion occurred (milliseconds since epoch)
            minimum: 0
          type:
            type: string
            enum:
            - tts_playback_complete
      - type: object
        description: |-
          VAD event notification

          Emitted when VAD detects speech activity changes:
          - `speech_start`: User started speaking
          - `silence_detected`: Brief silence detected
          - `speech_resumed`: User resumed speaking after brief silence
          - `turn_end`: Silence exceeded threshold, turn complete
        required:
        - event
        - timestamp_ms
        - type
        properties:
          event:
            type: string
            description: 'Event type: "speech_start", "silence_detected", "speech_resumed", "turn_end"'
            example: turn_end
          silence_duration_ms:
            type:
            - integer
            - 'null'
            format: int64
            description: Silence duration in milliseconds (only present for silence_detected and turn_end events)
            minimum: 0
          timestamp_ms:
            type: integer
            format: int64
            description: Timestamp when the event occurred (milliseconds since epoch)
            example: 1704067200000
            minimum: 0
          type:
            type: string
            enum:
            - vad_event
      - type: object
        required:
        - message
        - type
        properties:
          message:
            type: string
            description: Error message
          type:
            type: string
            enum:
            - error
      - type: object
        description: |-
          SIP transfer specific error

          This message is sent when a SIP transfer operation fails.
          Clients can subscribe to this specific error type to handle
          SIP transfer failures separately from other errors.
        required:
        - message
        - type
        properties:
          message:
            type: string
            description: Error message describing why the transfer failed
          type:
            type: string
            enum:
            - sip_transfer_error
      description: WebSocket message types for outgoing messages
    ParticipantConnectedInfo:
      type: object
      description: Participant connection information
      required:
      - identity
      - room
      - timestamp
      properties:
        identity:
          type: string
          description: Participant's unique identity
        name:
          type:
          - string
          - 'null'
          description: Participant's display name (if available)
        room:
          type: string
          description: Room identifier
        timestamp:
          type: integer
          format: int64
          description: Timestamp when the connection occurred
          minimum: 0
    ParticipantDisconnectedInfo:
      type: object
      description: Participant disconnection information
      required:
      - identity
      - room
      - timestamp
      properties:
        identity:
          type: string
          description: Participant's unique identity
        name:
          type:
          - string
          - 'null'
          description: Participant's display name (if available)
        room:
          type: string
          description: Room identifier
        timestamp:
          type: integer
          format: int64
          description: Timestamp when the disconnection occurred
          minimum: 0
    ParticipantInfo:
      type: object
      description: |-
        Detailed information about a participant in a LiveKit room

        # Example
        ```json
        {
          "sid": "PA_abc123",
          "identity": "user-alice-456",
          "name": "Alice Smith",
          "state": "ACTIVE",
          "kind": "STANDARD",
          "joined_at": 1703123456,
          "metadata": "{\"role\": \"host\"}",
          "attributes": {}
        }
        ```
      required:
      - sid
      - identity
      - name
      - state
      - kind
      - joined_at
      - metadata
      - attributes
      - is_publisher
      properties:
        attributes:
          type: object
          description: User-specified attributes for the participant
          additionalProperties:
            type: string
          propertyNames:
            type: string
        identity:
          type: string
          description: Unique identifier provided when connecting
          example: user-alice-456
        is_publisher:
          type: boolean
          description: Whether the participant is currently publishing audio/video
          example: true
        joined_at:
          type: integer
          format: int64
          description: Timestamp when participant joined (Unix timestamp in seconds)
          example: 1703123456
        kind:
          type: string
          description: 'Participant kind: STANDARD, AGENT, SIP, EGRESS, or INGRESS'
          example: STANDARD
        metadata:
          type: string
          description: User-specified metadata for the participant
          example:
            role: host
        name:
          type: string
          description: Display name of the participant
          example: Alice Smith
        sid:
          type: string
          description: Unique session ID for this participant (generated by LiveKit)
          example: PA_abc123
        state:
          type: string
          description: 'Participant state: JOINING, JOINED, ACTIVE, or DISCONNECTED'
          example: ACTIVE
    Pronunciation:
      type: object
      description: Pronunciation replacement configuration
      required:
      - word
      - pronunciation
      properties:
        pronunciation:
          type: string
          description: Pronunciation to use instead
          example: A P I
        word:
          type: string
          description: Word to replace
          example: API
    ProviderAuthInput:
      allOf:
      - type: object
        description: Provider-specific auth fields.
      description: |-
        Raw provider auth input received from API callers.

        The outer provider field determines how these fields are interpreted.
    RemoveParticipantErrorResponse:
      type: object
      description: |-
        Error response for participant removal failures

        # Example
        ```json
        {
          "error": "Participant 'user-123' not found in room",
          "code": "PARTICIPANT_NOT_FOUND"
        }
        ```
      required:
      - error
      - code
      properties:
        code:
          type: string
          description: Machine-readable error code
          example: PARTICIPANT_NOT_FOUND
        error:
          type: string
          description: Human-readable error message
          example: Participant not found in room
    RemoveParticipantRequest:
      type: object
      description: |-
        Request body for removing a participant from a LiveKit room

        # Example
        ```json
        {
          "room_name": "conversation-room-123",
          "participant_identity": "user-alice-456"
        }
        ```
      required:
      - room_name
      - participant_identity
      properties:
        participant_identity:
          type: string
          description: The identity of the participant to remove
          example: user-alice-456
        room_name:
          type: string
          description: The LiveKit room name where the participant is connected
          example: conversation-room-123
    RemoveParticipantResponse:
      type: object
      description: |-
        Response for a successful participant removal

        # Example
        ```json
        {
          "status": "removed",
          "room_name": "conversation-room-123",
          "participant_identity": "user-alice-456"
        }
        ```
      required:
      - status
      - room_name
      - participant_identity
      properties:
        participant_identity:
          type: string
          description: The identity of the removed participant
          example: user-alice-456
        room_name:
          type: string
          description: The room name
          example: conversation-room-123
        status:
          type: string
          description: Status of the removal operation
          example: removed
    RoomDetailsResponse:
      type: object
      description: |-
        Detailed information about a LiveKit room including participants

        # Example
        ```json
        {
          "sid": "RM_xyz789",
          "name": "conversation-room-123",
          "num_participants": 2,
          "max_participants": 10,
          "creation_time": 1703123456,
          "metadata": "{\"auth_id\": \"tenant-123\"}",
          "active_recording": false,
          "participants": [...]
        }
        ```
      required:
      - sid
      - name
      - num_participants
      - max_participants
      - creation_time
      - metadata
      - active_recording
      - participants
      properties:
        active_recording:
          type: boolean
          description: Whether a recording is currently active
          example: false
        creation_time:
          type: integer
          format: int64
          description: Room creation time (Unix timestamp in seconds)
          example: 1703123456
        max_participants:
          type: integer
          format: int32
          description: Maximum allowed participants (0 = no limit)
          example: 10
          minimum: 0
        metadata:
          type: string
          description: User-specified metadata for the room
          example: ''
        name:
          type: string
          description: The room name
          example: conversation-room-123
        num_participants:
          type: integer
          format: int32
          description: Number of current participants in the room
          example: 2
          minimum: 0
        participants:
          type: array
          items:
            $ref: '#/components/schemas/ParticipantInfo'
          description: List of participants currently in the room
        sid:
          type: string
          description: Unique session ID for the room (generated by LiveKit)
          example: RM_xyz789
    RoomInfo:
      type: object
      description: |-
        Information about a LiveKit room

        # Example
        ```json
        {
          "name": "conversation-room-123",
          "num_participants": 2,
          "creation_time": 1703123456
        }
        ```
      required:
      - name
      - num_participants
      - creation_time
      properties:
        creation_time:
          type: integer
          format: int64
          description: Room creation time (Unix timestamp in seconds)
          example: 1703123456
        name:
          type: string
          description: The room name
          example: conversation-room-123
        num_participants:
          type: integer
          format: int32
          description: Number of current participants in the room
          example: 2
          minimum: 0
    SIPCallErrorResponse:
      type: object
      description: |-
        Error response for SIP call failures

        # Example
        ```json
        {
          "error": "Outbound address not configured",
          "code": "OUTBOUND_ADDRESS_NOT_CONFIGURED"
        }
        ```
      required:
      - error
      - code
      properties:
        code:
          type: string
          description: Machine-readable error code
          example: OUTBOUND_ADDRESS_NOT_CONFIGURED
        error:
          type: string
          description: Human-readable error message
          example: Outbound address not configured
    SIPCallRequest:
      type: object
      description: |-
        Request body for initiating an outbound SIP call

        # Example
        ```json
        {
          "room_name": "call-room-123",
          "participant_name": "John Doe",
          "participant_identity": "caller-456",
          "from_phone_number": "+15105550123",
          "to_phone_number": "+15551234567",
          "sip": {
            "outbound_address": "sip.provider.com",
            "auth_username": "user123",
            "auth_password": "secret"
          }
        }
        ```

        # SIP Configuration Priority

        The `sip` object is optional. When provided, its fields take priority over global
        server configuration values. This allows per-request customization of SIP settings.

        **Priority order**: Request body `sip` config > Global server config

        - `outbound_address`: Overrides `sip.outbound_address` from config
        - `auth_username`: Overrides `sip.outbound_auth_username` from config
        - `auth_password`: Overrides `sip.outbound_auth_password` from config

        If neither the request body nor global config provides an `outbound_address`,
        the call will fail with `OUTBOUND_ADDRESS_NOT_CONFIGURED` error.
      required:
      - room_name
      - participant_name
      - participant_identity
      - from_phone_number
      - to_phone_number
      properties:
        from_phone_number:
          type: string
          description: |-
            Phone number the call will originate from.
            Must be configured in your SIP provider.
            Supports international format (+1234567890).
          example: '+15105550123'
        participant_identity:
          type: string
          description: Identity for the SIP participant in the room
          example: caller-456
        participant_name:
          type: string
          description: Display name for the SIP participant in the room
          example: John Doe
        room_name:
          type: string
          description: The LiveKit room name to connect the call to
          example: call-room-123
        sip:
          oneOf:
          - type: 'null'
          - $ref: '#/components/schemas/SIPCallSipConfig'
            description: |-
              Optional per-request SIP configuration overrides.
              When provided, these values take priority over the global server configuration.
              This allows using different SIP providers or credentials for specific calls.
        to_phone_number:
          type: string
          description: |-
            Phone number to dial.
            Supports international format (+1234567890), national format (07123456789),
            or extensions (1234).
          example: '+15551234567'
    SIPCallResponse:
      type: object
      description: |-
        Response for a successful SIP call initiation

        # Example
        ```json
        {
          "status": "initiated",
          "room_name": "call-room-123",
          "participant_identity": "caller-456",
          "participant_id": "PA_abc123",
          "sip_call_id": "SC_xyz789"
        }
        ```
      required:
      - status
      - room_name
      - participant_identity
      - participant_id
      - sip_call_id
      properties:
        participant_id:
          type: string
          description: The unique participant ID assigned by LiveKit
          example: PA_abc123
        participant_identity:
          type: string
          description: The identity of the SIP participant in the room
          example: caller-456
        room_name:
          type: string
          description: The room name where the call was connected
          example: call-room-123
        sip_call_id:
          type: string
          description: The unique SIP call ID for tracking
          example: SC_xyz789
        status:
          type: string
          description: Status of the call request ("initiated")
          example: initiated
    SIPCallSipConfig:
      type: object
      description: |-
        Per-request SIP configuration overrides

        Allows overriding the global SIP configuration on a per-request basis.
        When specified, these values take priority over the global server configuration.

        # Priority
        Request body values > Global config values

        # Example
        ```json
        {
          "outbound_address": "sip.example.com:5060",
          "auth_username": "user123",
          "auth_password": "secret456"
        }
        ```
      properties:
        auth_password:
          type:
          - string
          - 'null'
          description: |-
            SIP authentication password override.
            When provided, overrides the global `sip.outbound_auth_password` config.
          example: secure_password_456
        auth_username:
          type:
          - string
          - 'null'
          description: |-
            SIP authentication username override.
            When provided, overrides the global `sip.outbound_auth_username` config.
          example: sip_user_123
        outbound_address:
          type:
          - string
          - 'null'
          description: |-
            SIP server address override for outbound calls.
            When provided, overrides the global `sip.outbound_address` config.
            Format: hostname or hostname:port (e.g., "sip.example.com" or "sip.example.com:5060")
          example: sip.provider.com:5060
    SIPTransferErrorResponse:
      type: object
      description: |-
        Error response for SIP transfer failures

        # Example
        ```json
        {
          "error": "Participant 'sip_123' not found or is not a SIP participant",
          "code": "PARTICIPANT_NOT_FOUND"
        }
        ```
      required:
      - error
      - code
      properties:
        code:
          type: string
          description: Machine-readable error code
          example: PARTICIPANT_NOT_FOUND
        error:
          type: string
          description: Human-readable error message
          example: Participant not found or is not a SIP participant
    SIPTransferRequest:
      type: object
      description: |-
        Request body for initiating a SIP call transfer

        # Example
        ```json
        {
          "room_name": "call-room-123",
          "participant_identity": "sip_participant_456",
          "transfer_to": "+15551234567"
        }
        ```
      required:
      - room_name
      - participant_identity
      - transfer_to
      properties:
        participant_identity:
          type: string
          description: |-
            The identity of the SIP participant to transfer.
            This can be obtained by listing participants in the room via LiveKit API.
          example: sip_participant_456
        room_name:
          type: string
          description: The LiveKit room name where the SIP participant is connected
          example: call-room-123
        transfer_to:
          type: string
          description: |-
            The phone number to transfer the call to.
            Supports international format (+1234567890), national format (07123456789),
            or internal extensions (1234).
          example: '+15551234567'
    SIPTransferResponse:
      type: object
      description: |-
        Response for a successful SIP transfer initiation

        Note: A successful response indicates the transfer has been initiated,
        not that it has completed. The actual transfer may take several seconds.

        # Example
        ```json
        {
          "status": "initiated",
          "room_name": "call-room-123",
          "participant_identity": "sip_participant_456",
          "transfer_to": "tel:+15551234567"
        }
        ```
      required:
      - status
      - room_name
      - participant_identity
      - transfer_to
      properties:
        participant_identity:
          type: string
          description: The identity of the participant being transferred
          example: sip_participant_456
        room_name:
          type: string
          description: The room name where the transfer was initiated
          example: call-room-123
        status:
          type: string
          description: Status of the transfer request ("initiated" or "completed")
          example: initiated
        transfer_to:
          type: string
          description: 'The normalized phone number with tel: prefix'
          example: tel:+15551234567
    STTWebSocketConfig:
      type: object
      description: STT configuration for WebSocket messages.
      required:
      - provider
      - language
      - sample_rate
      - channels
      - punctuation
      - encoding
      - model
      properties:
        auth:
          oneOf:
          - type: 'null'
          - $ref: '#/components/schemas/ProviderAuthInput'
            description: |-
              Optional provider auth override for this session.

              Examples by provider:
              - Deepgram / ElevenLabs / Cartesia: `{ "api_key": "..." }`
              - Google: `{ "credentials": "/path/to/creds.json" }` or
                `{ "credentials": { ...service account json... } }`
              - Azure: `{ "api_key": "...", "region": "eastus" }`
        channels:
          type: integer
          format: int32
          description: Number of audio channels (1 for mono, 2 for stereo)
          example: 1
          minimum: 0
        encoding:
          type: string
          description: Encoding of the audio
          example: linear16
        language:
          type: string
          description: Language code for transcription (e.g., "en-US", "es-ES")
          example: en-US
        model:
          type: string
          description: Model to use for transcription
          example: nova-2
        provider:
          type: string
          description: Provider name (e.g., "deepgram")
          example: deepgram
        punctuation:
          type: boolean
          description: Enable punctuation in results
          example: true
        sample_rate:
          type: integer
          format: int32
          description: Sample rate of the audio in Hz
          example: 16000
          minimum: 0
    SipHookEntry:
      type: object
      description: |-
        A single SIP hook entry.

        Note: The `auth_id` field is conditionally required based on `AUTH_REQUIRED`:
        - When `AUTH_REQUIRED=true`: `auth_id` must be provided and cannot be empty
        - When `AUTH_REQUIRED=false`: `auth_id` may be empty (unauthenticated mode)
      required:
      - host
      - url
      - auth_id
      properties:
        auth_id:
          type: string
          description: |-
            Tenant identifier for this hook (written to LiveKit room metadata).
            Required when AUTH_REQUIRED=true; may be empty when AUTH_REQUIRED=false.
            When empty, room metadata updates are skipped.
          example: tenant-123
        host:
          type: string
          description: Host pattern for matching SIP domains (case-insensitive)
          example: example.com
        url:
          type: string
          description: HTTPS URL to forward webhook events to
          example: https://webhook.example.com/events
    SipHooksErrorResponse:
      type: object
      description: Error response for SIP hooks operations.
      required:
      - error
      properties:
        error:
          type: string
          description: Error message describing what went wrong
          example: 'Duplicate host detected: example.com'
    SipHooksRequest:
      type: object
      description: |-
        Request body for updating SIP hooks.

        Contains a list of SIP webhook configurations to add or replace.
      properties:
        hooks:
          type: array
          items:
            $ref: '#/components/schemas/SipHookEntry'
          description: |-
            List of SIP hooks to add or replace.
            Hooks with matching hosts (case-insensitive) will be replaced.
    SipHooksResponse:
      type: object
      description: Response body for SIP hooks operations.
      required:
      - hooks
      properties:
        hooks:
          type: array
          items:
            $ref: '#/components/schemas/SipHookEntry'
          description: List of all configured SIP hooks
    SpeakRequest:
      type: object
      description: Request body for the speak endpoint
      required:
      - text
      - tts_config
      properties:
        text:
          type: string
          description: The text to synthesize
          example: Hello, world!
        tts_config:
          $ref: '#/components/schemas/TTSWebSocketConfig'
          description: TTS configuration, including an optional provider auth override.
    TTSWebSocketConfig:
      type: object
      description: TTS configuration for WebSocket messages.
      required:
      - provider
      - model
      properties:
        audio_format:
          type:
          - string
          - 'null'
          description: Audio format preference
          example: linear16
        auth:
          oneOf:
          - type: 'null'
          - $ref: '#/components/schemas/ProviderAuthInput'
            description: |-
              Optional provider auth override for this session or request.

              Examples by provider:
              - Deepgram / ElevenLabs / Cartesia: `{ "api_key": "..." }`
              - Google: `{ "credentials": "/path/to/creds.json" }` or
                `{ "credentials": { ...service account json... } }`
              - Azure: `{ "api_key": "...", "region": "eastus" }`
        connection_timeout:
          type:
          - integer
          - 'null'
          format: int64
          description: Connection timeout in seconds
          example: 30
          minimum: 0
        model:
          type: string
          description: Model to use for TTS
          example: aura-asteria-en
        pronunciations:
          type: array
          items:
            $ref: '#/components/schemas/Pronunciation'
          description: Pronunciation replacements to apply before TTS
        provider:
          type: string
          description: Provider name (e.g., "deepgram")
          example: deepgram
        request_timeout:
          type:
          - integer
          - 'null'
          format: int64
          description: Request timeout in seconds
          example: 60
          minimum: 0
        sample_rate:
          type:
          - integer
          - 'null'
          format: int32
          description: Sample rate preference
          example: 24000
          minimum: 0
        speaking_rate:
          type:
          - number
          - 'null'
          format: float
          description: Speaking rate (0.25 to 4.0, 1.0 is normal)
          example: 1.0
        voice_id:
          type:
          - string
          - 'null'
          description: Voice ID or name to use for synthesis
          example: aura-asteria-en
    TokenRequest:
      type: object
      description: |-
        Request body for generating a LiveKit token

        # Example
        ```json
        {
          "room_name": "conversation-room-123",
          "participant_name": "Alice Smith",
          "participant_identity": "user-alice-456"
        }
        ```
      required:
      - room_name
      - participant_name
      - participant_identity
      properties:
        participant_identity:
          type: string
          description: Unique identifier for the participant (e.g., "user-123")
          example: user-alice-456
        participant_name:
          type: string
          description: Display name for the participant (e.g., "John Doe")
          example: Alice Smith
        room_name:
          type: string
          description: The LiveKit room name to generate a token for
          example: conversation-room-123
    TokenResponse:
      type: object
      description: |-
        Response containing the generated LiveKit token

        # Example
        ```json
        {
          "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
          "room_name": "conversation-room-123",
          "participant_identity": "user-alice-456",
          "livekit_url": "ws://localhost:7880"
        }
        ```
      required:
      - token
      - room_name
      - participant_identity
      - livekit_url
      properties:
        livekit_url:
          type: string
          description: The LiveKit server URL to connect to
          example: ws://localhost:7880
        participant_identity:
          type: string
          description: Echo back the participant identity for client confirmation
          example: user-alice-456
        room_name:
          type: string
          description: Echo back the room name for client confirmation
          example: conversation-room-123
        token:
          type: string
          description: The generated JWT token for LiveKit
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    TrackSubscribedInfo:
      type: object
      description: Track subscription information
      required:
      - identity
      - track_kind
      - track_sid
      - room
      - timestamp
      properties:
        identity:
          type: string
          description: Participant's unique identity (owner of the track)
        name:
          type:
          - string
          - 'null'
          description: Participant's display name (if available)
        room:
          type: string
          description: Room identifier
        timestamp:
          type: integer
          format: int64
          description: Timestamp when the subscription occurred
          minimum: 0
        track_kind:
          type: string
          description: 'Track kind: "audio" or "video"'
        track_sid:
          type: string
          description: Track SID (publication identifier)
    TurnDetectConfigUpdate:
      type: object
      description: Turn detection configuration update for runtime adjustments
      properties:
        threshold:
          type:
          - number
          - 'null'
          format: float
          description: |-
            Turn completion probability threshold (0.0 to 1.0).
            When the smart-turn model returns a probability >= this threshold,
            the turn is considered complete and speech_final is emitted.
          example: 0.5
    UnifiedMessage:
      type: object
      description: Unified message structure for all incoming messages from various sources
      required:
      - identity
      - topic
      - room
      - timestamp
      properties:
        data:
          type:
          - string
          - 'null'
          description: Binary data encoded as base64 (optional)
        identity:
          type: string
          description: Participant/sender identity
        message:
          type:
          - string
          - 'null'
          description: Text message content (optional)
        room:
          type: string
          description: Room/space identifier
        timestamp:
          type: integer
          format: int64
          description: Timestamp when the message was received
          minimum: 0
        topic:
          type: string
          description: Topic/channel for the message
    VADConfigUpdate:
      type: object
      description: VAD configuration update for runtime adjustments
      properties:
        silence_duration_ms:
          type:
          - integer
          - 'null'
          format: int64
          description: |-
            Silence duration threshold in milliseconds to trigger turn detection.
            When silence exceeds this duration, the smart-turn model is queried.
          example: 300
          minimum: 0
    Voice:
      type: object
      required:
      - id
      - sample
      - name
      - accent
      - gender
      - language
      properties:
        accent:
          type: string
          description: Accent or dialect
          example: American
        gender:
          type: string
          description: Gender of the voice
          example: Female
        id:
          type: string
          description: Voice ID or canonical name
          example: aura-asteria-en
        language:
          type: string
          description: Language supported by the voice
          example: English
        name:
          type: string
          description: Display name of the voice
          example: Asteria
        sample:
          type: string
          description: URL to sample audio
          example: https://example.com/sample.mp3
  securitySchemes:
    auth:
      type: http
      scheme: bearer
      bearerFormat: Token
      description: 'Authentication token for protected endpoints. Can be provided as `Authorization: Bearer <token>` or `?api_key=<token>`. Required when AUTH_REQUIRED is enabled.'
tags:
- name: health
  description: Health check endpoints
- name: voices
  description: TTS voice management
- name: tts
  description: Text-to-speech synthesis
- name: livekit
  description: LiveKit room and token management
- name: recordings
  description: Recording download operations
- name: sip
  description: SIP webhook configuration management
- name: websocket
  description: WebSocket API for real-time communication
