openapi: 3.1.0 info: title: VoiceBlender API description: | VoiceBlender bridges SIP and WebRTC voice calls with multi-party audio mixing, real-time speech-to-text, text-to-speech, AI agent integration, recording, and webhook-based event delivery. x-config-vars: - name: INSTANCE_ID default: (auto-generated UUID) description: Instance identifier included in all API responses and webhook events - name: HTTP_ADDR default: :8080 description: REST API listen address - name: SIP_BIND_IP default: 127.0.0.1 description: IP used in SDP, Contact, and Via headers - name: SIP_LISTEN_IP default: (same as SIP_BIND_IP) description: UDP socket bind IP - name: SIP_PORT default: "5060" description: SIP listen port - name: SIP_HOST default: voiceblender description: SIP User-Agent name - name: ICE_SERVERS default: stun:stun.l.google.com:19302 description: STUN/TURN URLs for WebRTC ICE, comma-separated - name: RECORDING_DIR default: /tmp/recordings description: Local directory for recording output files - name: LOG_LEVEL default: info description: 'Log verbosity: debug, info, warn, error' - name: WEBHOOK_URL default: "" description: Global webhook URL for event delivery (fallback when no per-leg or per-room webhook is set) - name: WEBHOOK_SECRET default: "" description: HMAC-SHA256 signing secret for the global webhook - name: ELEVENLABS_API_KEY default: "" description: API key for ElevenLabs TTS, STT, and Agent provider - name: VAPI_API_KEY default: "" description: API key for VAPI Agent provider - name: DEEPGRAM_API_KEY default: "" description: API key for Deepgram STT and TTS - name: AZURE_SPEECH_KEY default: "" description: Subscription key for Azure Cognitive Speech Services (TTS and STT) - name: AZURE_SPEECH_REGION default: eastus description: Azure region for Speech Services (e.g. eastus, westeurope) - name: S3_BUCKET default: "" description: S3 bucket name for recording uploads - name: S3_REGION default: us-east-1 description: AWS region for S3 - name: S3_ENDPOINT default: "" description: Custom S3-compatible endpoint (e.g. MinIO) - name: S3_PREFIX default: "" description: Key prefix applied to all S3 objects - name: TTS_CACHE_ENABLED default: "false" description: Enable disk-backed TTS audio cache; cached audio persists across restarts - name: TTS_CACHE_DIR default: /tmp/tts_cache description: Directory for cached TTS audio files (used when TTS_CACHE_ENABLED=true) - name: TTS_CACHE_INCLUDE_API_KEY default: "false" description: Include API key in TTS cache key; set true if different keys map to different voice clones - name: RTP_PORT_MIN default: "10000" description: Minimum UDP port for RTP/RTCP media - name: RTP_PORT_MAX default: "20000" description: Maximum UDP port for RTP/RTCP media version: 1.0.0 license: name: MIT servers: - url: http://localhost:8080/v1 description: Local development server tags: - name: Legs description: Voice call legs (SIP or WebRTC) - name: Rooms description: Multi-party audio conference rooms - name: WebRTC description: WebRTC peer connection establishment - name: Observability description: Metrics and health endpoints paths: /legs: post: operationId: createLeg summary: Originate an outbound SIP call tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateLegRequest' responses: '201': description: Leg created content: application/json: schema: $ref: '#/components/schemas/Leg' '400': description: Invalid JSON, bad SIP URI, unknown codec, or unsupported type content: application/json: schema: $ref: '#/components/schemas/Error' get: operationId: listLegs summary: List all active legs tags: - Legs responses: '200': description: Array of legs content: application/json: schema: type: array items: $ref: '#/components/schemas/Leg' /legs/{id}: parameters: - $ref: '#/components/parameters/LegId' get: operationId: getLeg summary: Get a single leg tags: - Legs responses: '200': description: Leg details content: application/json: schema: $ref: '#/components/schemas/Leg' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: deleteLeg summary: Hang up a leg tags: - Legs responses: '200': description: Leg hung up content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/answer: parameters: - $ref: '#/components/parameters/LegId' post: operationId: answerLeg summary: Answer a ringing or early-media inbound SIP leg tags: - Legs responses: '200': description: Answer initiated content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Not a SIP inbound leg content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg is not in ringing or early_media state content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/early-media: parameters: - $ref: '#/components/parameters/LegId' post: operationId: earlyMediaLeg summary: Enable early media on a ringing inbound SIP leg description: Sends SIP 183 Session Progress with SDP and sets up the media pipeline. The leg transitions to `early_media` state, allowing audio playback and room participation before the call is answered. tags: - Legs responses: '200': description: Early media enabled content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Not a SIP inbound leg content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg is not in ringing state content: application/json: schema: $ref: '#/components/schemas/Error' '500': description: Media setup failed content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/mute: parameters: - $ref: '#/components/parameters/LegId' post: operationId: muteLeg summary: Mute a leg description: A muted leg's audio is excluded from the room mix and speaking events are suppressed. Taps (recording/STT) still receive the muted leg's own audio. tags: - Legs responses: '200': description: Leg muted content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: unmuteLeg summary: Unmute a leg tags: - Legs responses: '200': description: Leg unmuted content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/hold: parameters: - $ref: '#/components/parameters/LegId' post: operationId: holdLeg summary: Put a SIP call on hold description: Sends a re-INVITE with `sendonly` SDP direction. The RTP timeout is paused while held, and a 2-hour auto-hangup timer starts. tags: - Legs responses: '200': description: Call held content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Not a SIP leg content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg is not in connected state, or already held content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: unholdLeg summary: Resume a held SIP call description: Sends a re-INVITE with `sendrecv` SDP direction. tags: - Legs responses: '200': description: Call resumed content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Not a SIP leg content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg is not held content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/dtmf: parameters: - $ref: '#/components/parameters/LegId' post: operationId: sendDTMF summary: Send DTMF digits on a leg tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DTMFRequest' responses: '200': description: Digits sent content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or empty digits content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '500': description: DTMF writer unavailable content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/play: parameters: - $ref: '#/components/parameters/LegId' post: operationId: playLeg summary: Start audio playback to a leg tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PlaybackRequest' responses: '200': description: Playback started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or volume out of range content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg has no audio writer content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/play/{playbackID}: parameters: - $ref: '#/components/parameters/LegId' - $ref: '#/components/parameters/PlaybackId' patch: operationId: volumePlayLeg summary: Change the volume of an active leg playback tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/VolumeRequest' responses: '200': description: Volume updated content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or volume out of range content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Playback not found content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: stopPlayLeg summary: Stop audio playback on a leg tags: - Legs responses: '200': description: Playback stopped content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: No playback in progress content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/tts: parameters: - $ref: '#/components/parameters/LegId' post: operationId: ttsLeg summary: Synthesize speech and play it on a leg description: Synthesizes the provided text using the configured TTS provider and plays the audio on the leg. When `TTS_CACHE_ENABLED=true`, identical requests (same text, voice, model, language, and prompt) are stored on disk in `TTS_CACHE_DIR` and persist across restarts, without calling the external provider. tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TTSRequest' responses: '200': description: TTS playback started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON, missing text/voice, or volume out of range content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg has no audio writer content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No API key provided for the selected provider content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/record: parameters: - $ref: '#/components/parameters/LegId' post: operationId: recordLeg summary: Start recording a leg to a WAV file description: For SIP legs, recording is stereo (left=incoming, right=outgoing). For legs in a room, stereo at 16kHz (left=participant audio, right=mixed-minus-self). tags: - Legs requestBody: content: application/json: schema: $ref: '#/components/schemas/RecordingRequest' responses: '200': description: Recording started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid storage type, S3 not configured, or invalid S3 credentials content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg has no audio reader or room not found content: application/json: schema: $ref: '#/components/schemas/Error' '500': description: Failed to create recording file content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: stopRecordLeg summary: Stop recording a leg tags: - Legs responses: '200': description: Recording stopped content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: No recording in progress content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/stt: parameters: - $ref: '#/components/parameters/LegId' post: operationId: sttLeg summary: Start real-time speech-to-text on a leg tags: - Legs requestBody: content: application/json: schema: $ref: '#/components/schemas/STTRequest' responses: '200': description: STT started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg not connected, STT already running, or no audio reader content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No ElevenLabs API key provided content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: stopSTTLeg summary: Stop speech-to-text on a leg tags: - Legs responses: '200': description: STT stopped content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: No STT in progress content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/agent/elevenlabs: parameters: - $ref: '#/components/parameters/LegId' post: operationId: agentLegElevenLabs summary: Attach an ElevenLabs ConvAI agent to a leg description: Bridges audio bidirectionally with an ElevenLabs conversational AI agent. Standalone legs use direct audio; legs in a room use mixer taps. tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ElevenLabsAgentRequest' responses: '200': description: Agent started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or missing agent_id content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg not connected, agent already attached, or no audio reader/writer content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No ElevenLabs API key provided content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/agent/vapi: parameters: - $ref: '#/components/parameters/LegId' post: operationId: agentLegVAPI summary: Attach a VAPI agent to a leg description: Bridges audio bidirectionally with a VAPI conversational AI agent. Standalone legs use direct audio; legs in a room use mixer taps. tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/VAPIAgentRequest' responses: '200': description: Agent started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or missing assistant_id content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg not connected, agent already attached, or no audio reader/writer content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No VAPI API key provided content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/agent/pipecat: parameters: - $ref: '#/components/parameters/LegId' post: operationId: agentLegPipecat summary: Attach a Pipecat bot to a leg description: Bridges audio bidirectionally with a self-hosted Pipecat bot via WebSocket. Standalone legs use direct audio; legs in a room use mixer taps. tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PipecatAgentRequest' responses: '200': description: Agent started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or missing websocket_url content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg not connected, agent already attached, or no audio reader/writer content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/agent/deepgram: parameters: - $ref: '#/components/parameters/LegId' post: operationId: agentLegDeepgram summary: Attach a Deepgram Voice Agent to a leg description: Bridges audio bidirectionally with a Deepgram Voice Agent. Standalone legs use direct audio; legs in a room use mixer taps. tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DeepgramAgentRequest' responses: '200': description: Agent started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Leg not connected, agent already attached, or no audio reader/writer content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No Deepgram API key provided content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/agent/message: parameters: - $ref: '#/components/parameters/LegId' post: operationId: agentLegMessage summary: Inject a message into a running agent session on a leg description: Sends a context message or instruction to the running agent. Supported by Deepgram (InjectAgentMessage), Pipecat (TextFrame), and VAPI (control URL). Returns 501 for ElevenLabs. tags: - Legs requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AgentMessageRequest' responses: '200': description: Message sent content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or missing message content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: No agent attached to this leg content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Agent session not running content: application/json: schema: $ref: '#/components/schemas/Error' '501': description: Provider does not support message injection content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/agent: parameters: - $ref: '#/components/parameters/LegId' delete: operationId: stopAgentLeg summary: Detach the agent from a leg tags: - Legs responses: '200': description: Agent stopped content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: No agent attached to this leg content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/amd: parameters: - $ref: '#/components/parameters/LegId' post: operationId: startAMDLeg summary: Start answering machine detection on a connected leg description: >- Start AMD on an already-connected SIP leg. This is an alternative to including the `amd` object in the POST /v1/legs request — use this endpoint when AMD was not enabled at call creation time. All AMD parameters are optional; omitted or zero values use built-in defaults. An empty request body enables AMD with all defaults. tags: - Legs requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/AMDParams' responses: "200": description: AMD started content: application/json: schema: properties: status: type: string example: started "400": description: Invalid AMD params or not a SIP leg content: application/json: schema: $ref: '#/components/schemas/Error' "404": description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' "409": description: Leg is not in connected state content: application/json: schema: $ref: '#/components/schemas/Error' /legs/{id}/ice-candidates: parameters: - $ref: '#/components/parameters/LegId' post: operationId: addICECandidate summary: Send a remote ICE candidate to a WebRTC leg (trickle ICE) tags: - WebRTC responses: '200': description: Candidate added content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or leg is not a WebRTC leg content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' '500': description: Failed to add ICE candidate content: application/json: schema: $ref: '#/components/schemas/Error' get: operationId: getICECandidates summary: Get server-side ICE candidates for a WebRTC leg (trickle ICE) tags: - WebRTC responses: '200': description: Buffered ICE candidates content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Leg is not a WebRTC leg content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' /rooms: post: operationId: createRoom summary: Create a room tags: - Rooms requestBody: content: application/json: schema: $ref: '#/components/schemas/RoomCreateRequest' responses: '201': description: Room created content: application/json: schema: $ref: '#/components/schemas/Room' '409': description: Room ID already exists content: application/json: schema: $ref: '#/components/schemas/Error' get: operationId: listRooms summary: List all rooms with participants tags: - Rooms responses: '200': description: Array of rooms content: application/json: schema: type: array items: $ref: '#/components/schemas/Room' /rooms/{id}: parameters: - $ref: '#/components/parameters/RoomId' get: operationId: getRoom summary: Get a room with participants tags: - Rooms responses: '200': description: Room details content: application/json: schema: $ref: '#/components/schemas/Room' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: deleteRoom summary: Delete a room tags: - Rooms responses: '200': description: Room deleted content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/legs: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: addLegToRoom summary: Add or move a leg to a room description: Add a leg to a room (auto-creates room if it doesn't exist). If the leg is already in a different room, it is atomically moved to the target room. A ringing inbound SIP leg is automatically answered before being added. tags: - Rooms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AddLegRequest' responses: '200': description: Leg added or moved content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON, leg not found, or leg not connected content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/legs/{legID}: parameters: - $ref: '#/components/parameters/RoomId' - name: legID in: path required: true schema: type: string description: Leg ID delete: operationId: removeLegFromRoom summary: Remove a leg from a room tags: - Rooms responses: '200': description: Leg removed content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Room or leg not found content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/play: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: playRoom summary: Play audio to a room tags: - Rooms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PlaybackRequest' responses: '200': description: Playback started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or volume out of range content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Room has no participants content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/play/{playbackID}: parameters: - $ref: '#/components/parameters/RoomId' - $ref: '#/components/parameters/PlaybackId' patch: operationId: volumePlayRoom summary: Change the volume of an active room playback tags: - Rooms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/VolumeRequest' responses: '200': description: Volume updated content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or volume out of range content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Playback not found content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: stopPlayRoom summary: Stop room playback tags: - Rooms responses: '200': description: Playback stopped content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: No playback in progress content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/tts: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: ttsRoom summary: Synthesize speech and play it into a room description: Synthesizes the provided text using the configured TTS provider and plays the audio into the room. When `TTS_CACHE_ENABLED=true`, identical requests (same text, voice, model, language, and prompt) are stored on disk in `TTS_CACHE_DIR` and persist across restarts, without calling the external provider. tags: - Rooms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TTSRequest' responses: '200': description: TTS playback started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON, missing text/voice, or volume out of range content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Room has no participants content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No API key provided for the selected provider content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/record: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: recordRoom summary: Start recording the room mix to a WAV file description: Records the full room mix at 16kHz, 16-bit, mono. tags: - Rooms requestBody: content: application/json: schema: $ref: '#/components/schemas/RecordingRequest' responses: '200': description: Recording started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid storage type, S3 not configured, or invalid S3 credentials content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Room has no participants content: application/json: schema: $ref: '#/components/schemas/Error' '500': description: Failed to create recording file content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: stopRecordRoom summary: Stop room recording tags: - Rooms responses: '200': description: Recording stopped content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: No recording in progress content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/stt: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: sttRoom summary: Start speech-to-text on all room participants tags: - Rooms requestBody: content: application/json: schema: $ref: '#/components/schemas/STTRequest' responses: '200': description: STT started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: STT already running or room has no participants content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No ElevenLabs API key provided content: application/json: schema: $ref: '#/components/schemas/Error' delete: operationId: stopSTTRoom summary: Stop speech-to-text on a room tags: - Rooms responses: '200': description: STT stopped content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: No STT in progress content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/agent/elevenlabs: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: agentRoomElevenLabs summary: Attach an ElevenLabs ConvAI agent to a room description: The agent joins as a virtual participant, hearing all participants (mixed-minus-self) and speaking to everyone. tags: - Rooms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ElevenLabsAgentRequest' responses: '200': description: Agent started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or missing agent_id content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Agent already attached to this room content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No ElevenLabs API key provided content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/agent/vapi: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: agentRoomVAPI summary: Attach a VAPI agent to a room description: The agent joins as a virtual participant, hearing all participants (mixed-minus-self) and speaking to everyone. tags: - Rooms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/VAPIAgentRequest' responses: '200': description: Agent started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or missing assistant_id content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Agent already attached to this room content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No VAPI API key provided content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/agent/pipecat: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: agentRoomPipecat summary: Attach a Pipecat bot to a room description: The bot joins as a virtual participant via WebSocket, hearing all participants (mixed-minus-self) and speaking to everyone. tags: - Rooms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PipecatAgentRequest' responses: '200': description: Agent started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or missing websocket_url content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Agent already attached to this room content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/agent/deepgram: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: agentRoomDeepgram summary: Attach a Deepgram Voice Agent to a room description: The agent joins as a virtual participant, hearing all participants (mixed-minus-self) and speaking to everyone. tags: - Rooms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DeepgramAgentRequest' responses: '200': description: Agent started content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Agent already attached to this room content: application/json: schema: $ref: '#/components/schemas/Error' '503': description: No Deepgram API key provided content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/agent/message: parameters: - $ref: '#/components/parameters/RoomId' post: operationId: agentRoomMessage summary: Inject a message into a running agent session on a room description: Sends a context message or instruction to the running agent. Supported by Deepgram (InjectAgentMessage), Pipecat (TextFrame), and VAPI (control URL). Returns 501 for ElevenLabs. tags: - Rooms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AgentMessageRequest' responses: '200': description: Message sent content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or missing message content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: No agent attached to this room content: application/json: schema: $ref: '#/components/schemas/Error' '409': description: Agent session not running content: application/json: schema: $ref: '#/components/schemas/Error' '501': description: Provider does not support message injection content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/agent: parameters: - $ref: '#/components/parameters/RoomId' delete: operationId: stopAgentRoom summary: Detach the agent from a room tags: - Rooms responses: '200': description: Agent stopped content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '404': description: No agent attached to this room content: application/json: schema: $ref: '#/components/schemas/Error' /rooms/{id}/ws: parameters: - $ref: '#/components/parameters/RoomId' get: operationId: wsRoom summary: WebSocket audio stream for a room description: Upgrades to a WebSocket connection and joins the room as a bidirectional audio participant. The client sends and receives 16kHz 16-bit signed little-endian PCM audio (mono), base64-encoded in JSON text frames. Each audio frame is 640 bytes (20ms). tags: - Rooms responses: '101': description: WebSocket upgrade successful. Server sends a `connected` message followed by mixed-minus-self audio frames. '404': description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' /webrtc/offer: post: operationId: webrtcOffer summary: Establish a WebRTC leg via SDP offer/answer tags: - WebRTC requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/WebRTCOfferRequest' responses: '200': description: SDP answer with leg ID content: application/json: schema: $ref: '#/components/schemas/StatusResponse' '400': description: Invalid JSON or invalid SDP offer content: application/json: schema: $ref: '#/components/schemas/Error' '500': description: Peer connection, track creation, or answer generation failed content: application/json: schema: $ref: '#/components/schemas/Error' /metrics: get: operationId: getMetrics summary: Prometheus metrics description: | Returns Prometheus-format metrics (text/plain exposition format). Includes VoiceBlender-specific metrics and standard Go runtime metrics. tags: - Observability responses: '200': description: Prometheus text exposition format content: text/plain: schema: type: string /debug/pprof/: get: operationId: pprofIndex summary: pprof index description: | Index of available Go runtime profiles. Only available when built with `-tags pprof` (e.g. `go build -tags pprof ./...`). tags: - Observability responses: '200': description: HTML index page listing available profiles content: text/html: schema: type: string /debug/pprof/profile: get: operationId: pprofCPU summary: CPU profile description: | 30-second CPU profile (duration configurable via ?seconds= query param). Only available when built with `-tags pprof`. tags: - Observability parameters: - name: seconds in: query schema: type: integer default: 30 description: Profile duration in seconds responses: '200': description: pprof binary profile content: application/octet-stream: schema: type: string format: binary /debug/pprof/heap: get: operationId: pprofHeap summary: Heap memory profile description: | Heap memory snapshot. Only available when built with `-tags pprof`. tags: - Observability responses: '200': description: pprof binary profile content: application/octet-stream: schema: type: string format: binary /debug/pprof/goroutine: get: operationId: pprofGoroutine summary: Goroutine stack traces description: | All goroutine stack traces. Only available when built with `-tags pprof`. tags: - Observability responses: '200': description: pprof binary profile content: application/octet-stream: schema: type: string format: binary components: parameters: LegId: name: id in: path required: true schema: type: string description: Leg ID RoomId: name: id in: path required: true schema: type: string description: Room ID PlaybackId: name: playbackID in: path required: true schema: type: string description: Playback ID responses: LegNotFound: description: Leg not found content: application/json: schema: $ref: '#/components/schemas/Error' RoomNotFound: description: Room not found content: application/json: schema: $ref: '#/components/schemas/Error' schemas: Leg: type: object properties: instance_id: type: string description: Instance identifier id: type: string description: Unique leg identifier (UUID) type: type: string description: Leg type enum: - sip_inbound - sip_outbound - webrtc state: type: string description: Leg state enum: - ringing - early_media - connected - held - hung_up room_id: type: string description: Room ID if the leg is in a room, empty otherwise muted: type: boolean description: Whether the leg is muted (cannot be heard by others) deaf: type: boolean description: Whether the leg is deaf (cannot hear others) held: type: boolean description: Whether the call is on hold (SIP legs only) sip_headers: type: object additionalProperties: type: string description: X-* headers from the inbound INVITE. Only present on sip_inbound legs. required: - id - type - state - muted - deaf - held Room: type: object properties: instance_id: type: string description: Instance identifier id: type: string description: Room identifier participants: type: array items: $ref: '#/components/schemas/Leg' description: Legs currently in this room required: - id - participants Error: type: object properties: instance_id: type: string description: Instance identifier error: type: string description: Error message required: - error StatusResponse: type: object properties: instance_id: type: string description: Instance identifier status: type: string required: - status AddLegRequest: type: object properties: leg_id: type: string description: ID of the leg to add required: - leg_id AgentMessageRequest: type: object properties: message: type: string description: Context or instruction to inject into the running agent session required: - message CreateLegRequest: type: object properties: type: type: string description: Leg type enum: - sip uri: type: string description: SIP URI to dial from: type: string description: Caller ID — sets the user part of the SIP From header (e.g. "+15551234567", "alice") privacy: type: string description: SIP Privacy header value (e.g. "id", "none") ring_timeout: type: integer description: Seconds to wait for answer; 0 = no timeout default: 0 max_duration: type: integer description: Maximum call duration in seconds after connect. Automatically hung up when reached. 0 or omitted = no limit. default: 0 codecs: type: array items: type: string enum: - PCMU - PCMA - G722 - opus description: Codec preference order headers: type: object additionalProperties: type: string description: Custom SIP headers to include in the outbound INVITE (e.g. X-Correlation-ID) room_id: type: string description: Room ID to auto-add the leg to once media is ready (early_media or connected). If the room does not exist, it is automatically created. auth: description: SIP digest authentication credentials. If the remote challenges with 401/407, sipgo will retry with these credentials. nullable: true allOf: - $ref: '#/components/schemas/SIPAuth' webhook_url: type: string description: Route all events for this leg exclusively to this URL instead of global webhooks. format: uri webhook_secret: type: string description: HMAC-SHA256 signing secret for the per-leg webhook. amd: description: >- Enable Answering Machine Detection on this outbound call. Disabled by default — omit the field entirely to skip AMD. Include the object to enable; all inner fields are optional and default to sensible values when omitted or set to zero (e.g. `"amd": {}` enables AMD with all defaults). allOf: - $ref: '#/components/schemas/AMDParams' required: - type - uri AMDParams: type: object description: >- Answering Machine Detection configuration. All fields are optional — omitted or zero-valued fields use built-in defaults. Pass an empty object (`{}`) to enable AMD with all defaults. properties: initial_silence_timeout: type: integer description: Max milliseconds of silence before declaring no_speech. default: 2500 greeting_duration: type: integer description: Speech duration threshold (ms) above which answerer is classified as machine. default: 1500 after_greeting_silence: type: integer description: Silence duration (ms) after initial speech to declare human. default: 800 total_analysis_time: type: integer description: Max analysis window in milliseconds. default: 5000 minimum_word_length: type: integer description: Minimum speech burst duration (ms) to count as a word. default: 100 beep_timeout: type: integer description: "Max time (ms) to wait for the voicemail beep after machine detection. 0 = disabled." default: 0 RoomCreateRequest: type: object properties: id: type: string description: Custom room ID (auto-generated UUID if omitted) webhook_url: type: string description: Route all events for this room exclusively to this URL instead of global webhooks. format: uri webhook_secret: type: string description: HMAC-SHA256 signing secret for the per-room webhook. required: - id DTMFRequest: type: object properties: digits: type: string description: 'DTMF digits to send (0-9, *, #)' required: - digits DeepgramAgentRequest: type: object properties: settings: type: object additionalProperties: type: string description: Full Deepgram agent settings object (agent.listen, agent.think, agent.speak, etc.). When omitted, sensible defaults are used (nova-3 STT, gpt-4o-mini LLM, aura-2-asteria-en TTS). greeting: type: string description: Agent greeting message language: type: string description: Language code (e.g. "en", "es") api_key: type: string description: API key override (falls back to DEEPGRAM_API_KEY env var) ElevenLabsAgentRequest: type: object properties: agent_id: type: string description: ElevenLabs agent ID first_message: type: string description: Override the agent's first message language: type: string description: Language code (e.g. "en", "es") dynamic_variables: type: object additionalProperties: type: string description: Key-value pairs passed to the agent as dynamic variables api_key: type: string description: API key override (falls back to ELEVENLABS_API_KEY env var) required: - agent_id PipecatAgentRequest: type: object properties: websocket_url: type: string description: WebSocket URL of the Pipecat bot (e.g. ws://my-bot:8765) format: uri required: - websocket_url PlaybackRequest: type: object properties: url: type: string description: URL of the audio file (mutually exclusive with tone) format: uri tone: type: string description: 'Built-in telephone tone name. Format: {country}_{type} or bare {type} (defaults to US). Types: ringback, busy, dial, congestion. Countries: us, gb, de, fr, au, jp, it, in, br, pl, ru. Examples: us_ringback, gb_busy, dial.' mime_type: type: string description: MIME type (e.g. audio/wav). Required when using url. repeat: type: integer description: Number of times to repeat playback (url only) default: 0 volume: type: integer description: Volume adjustment in dB (-8 to 8) default: 0 minimum: -8 maximum: 8 required: - url - tone - mime_type - repeat - volume oneOf: - required: - url - mime_type - required: - tone RecordingRequest: type: object properties: storage: type: string description: '"file" (default) — local disk, "s3" — upload to S3 after recording stops' enum: - file - s3 multi_channel: type: boolean description: When true, record each participant to a separate mono WAV file in addition to the full mix. Only applies to room recordings. default: false s3_bucket: type: string description: S3 bucket name. Overrides S3_BUCKET env var. Required if env var is not set. s3_region: type: string description: AWS region. Overrides S3_REGION env var. Default us-east-1. s3_endpoint: type: string description: Custom S3 endpoint (MinIO, etc.). Overrides S3_ENDPOINT env var. s3_prefix: type: string description: Key prefix (e.g. recordings/). Overrides S3_PREFIX env var. s3_access_key: type: string description: AWS access key ID. Overrides default credential chain. s3_secret_key: type: string description: AWS secret access key. Must be set together with s3_access_key. required: - storage - multi_channel - s3_bucket - s3_region - s3_endpoint - s3_prefix - s3_access_key - s3_secret_key STTRequest: type: object properties: language: type: string description: Language code (e.g. "en", "es") partial: type: boolean description: Emit partial (non-final) transcripts default: false provider: type: string description: 'STT provider: "elevenlabs" (default), "deepgram", or "azure"' enum: - elevenlabs - deepgram - azure api_key: type: string description: API key override (falls back to ELEVENLABS_API_KEY, DEEPGRAM_API_KEY, or AZURE_SPEECH_KEY env var depending on provider) required: - language - partial TTSRequest: type: object properties: text: type: string description: Text to synthesize voice: type: string description: 'Provider-specific voice identifier. ElevenLabs: voice name or ID. AWS Polly: voice ID (e.g. Joanna, Matthew). Google Cloud: voice name — either full format (e.g. en-US-Neural2-F) or short name for Gemini models (e.g. Achernar, Kore). Deepgram: model name (e.g. aura-2-asteria-en). Azure: voice name (e.g. en-US-JennyNeural, pl-PL-MarekNeural).' model_id: type: string description: 'Provider-specific model/engine. ElevenLabs: model ID. AWS Polly: engine (standard, neural, long-form, generative; default neural). Google Cloud: model name (e.g. gemini-2.5-pro-tts, chirp3-hd).' language: type: string description: Language code (e.g. "en-US", "pl-pl"). Required for Google Gemini TTS voices that use short names (e.g. Achernar). Auto-extracted from full voice names like en-US-Neural2-F. prompt: type: string description: Style/tone instruction for promptable voice models (Google Gemini TTS only). E.g. "Read aloud in a warm, welcoming tone." volume: type: integer description: Volume adjustment in dB (-8 to 8) default: 0 minimum: -8 maximum: 8 provider: type: string description: 'TTS provider: "elevenlabs" (default), "aws", "google", "deepgram", or "azure"' enum: - elevenlabs - aws - google - deepgram - azure api_key: type: string description: 'ElevenLabs: API key override (falls back to ELEVENLABS_API_KEY env var). AWS: optional ACCESS_KEY:SECRET_KEY override (falls back to default AWS credential chain). Google Cloud: optional API key override (falls back to Application Default Credentials). Deepgram: API key override (falls back to DEEPGRAM_API_KEY env var). Azure: subscription key override (falls back to AZURE_SPEECH_KEY env var).' required: - text - voice - model_id - volume VAPIAgentRequest: type: object properties: assistant_id: type: string description: VAPI assistant ID first_message: type: string description: Override the agent's first message variable_values: type: object additionalProperties: type: string description: Key-value pairs passed as VAPI variable values (assistantOverrides.variableValues) api_key: type: string description: API key override (falls back to VAPI_API_KEY env var) required: - assistant_id VolumeRequest: type: object properties: volume: type: integer description: Volume adjustment (-8 to 8, ~3dB per step, 0 = unchanged) minimum: -8 maximum: 8 required: - volume WebRTCOfferRequest: type: object properties: sdp: type: string description: SDP offer from the browser required: - sdp SIPAuth: type: object properties: username: type: string description: SIP auth username password: type: string description: SIP auth password required: - username - password WebhookEvent: type: object description: Event envelope delivered via HTTP POST to registered webhook URLs. Event-specific fields are flattened into the top-level object (no "data" wrapper). Includes X-Signature-256 header when a secret is configured. properties: type: $ref: '#/components/schemas/WebhookEventType' timestamp: type: string format: date-time instance_id: type: string description: Instance identifier required: - type - timestamp WebhookEventType: type: string enum: - leg.ringing - leg.early_media - leg.connected - leg.disconnected - leg.joined_room - leg.left_room - leg.muted - leg.unmuted - leg.hold - leg.unhold - dtmf.received - speaking.started - speaking.stopped - playback.started - playback.finished - playback.error - tts.started - tts.finished - tts.error - recording.started - recording.finished - room.created - room.deleted - stt.text - agent.connected - agent.disconnected - agent.user_transcript - agent.agent_response ICECandidateInit: type: object properties: candidate: type: string description: ICE candidate string sdpMid: type: string description: Media stream identification tag sdpMLineIndex: type: integer description: Index of the media description required: - candidate x-webhooks: leg.ringing: post: summary: SIP call ringing (inbound or outbound) requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier leg_type: type: string description: Leg type (e.g. sip_inbound, sip_outbound) uri: type: string description: Dialed SIP URI (outbound only) from: type: string description: Caller URI (inbound) or From header value (outbound, if set) to: type: string description: Callee URI (inbound only) sip_headers: type: object additionalProperties: type: string description: X-* custom SIP headers, if present leg.early_media: post: summary: Outbound leg received 183 Session Progress with SDP; media pipeline active requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier leg_type: type: string description: Leg type (e.g. sip_outbound) leg.connected: post: summary: Leg answered/connected requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier leg_type: type: string description: Leg type (e.g. sip_inbound, sip_outbound, webrtc) leg.disconnected: post: summary: Leg hung up (CDR-style nested structure) requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier cdr: type: object properties: reason: type: string description: Disconnect reason. Common SIP failures are mapped to named reasons; unmapped 4xx/5xx/6xx codes appear as sip_{code}. enum: - api_hangup - remote_bye - caller_cancel - ring_timeout - max_duration - busy - unavailable - not_found - forbidden - unauthorized - timeout - cancelled - not_acceptable - service_unavailable - declined - rtp_timeout - session_expired - invite_failed - connect_failed - ice_failure duration_total: type: number description: Seconds from leg creation to disconnect duration_answered: type: number description: Seconds from answer to disconnect (0 if never answered) required: - reason - duration_total - duration_answered quality: description: RTP quality metrics. Omitted for WebRTC legs or unanswered legs with no media. type: object nullable: true properties: mos_score: type: number description: Mean Opinion Score (1.0–5.0) estimated via simplified E-model (ITU-T G.107) from packet loss and jitter rtp_packets_received: type: integer description: Total inbound RTP audio packets received rtp_packets_lost: type: integer description: Estimated lost packets based on sequence number gaps rtp_jitter_ms: type: number description: Inter-arrival jitter in milliseconds (RFC 3550 §A.8) required: - mos_score - rtp_packets_received - rtp_packets_lost - rtp_jitter_ms leg.joined_room: post: summary: Leg added to a room requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier leg.left_room: post: summary: Leg removed from a room requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier leg.muted: post: summary: Leg muted requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier leg.unmuted: post: summary: Leg unmuted requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier leg.hold: post: summary: Leg put on hold (local or remote) requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier leg_type: type: string description: 'Hold direction: "local" (we put them on hold) or "remote" (they put us on hold)' leg.unhold: post: summary: Leg taken off hold (local or remote) requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier leg_type: type: string description: 'Hold direction: "local" or "remote"' dtmf.received: post: summary: DTMF digit received requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier digit: type: string description: DTMF digit received speaking.started: post: summary: Participant started speaking description: Fires for any connected leg. When the leg is in a room, room_id is included. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier (present only when the leg is in a room) speaking.stopped: post: summary: Participant stopped speaking description: Fires for any connected leg. When the leg is in a room, room_id is included. requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier (present only when the leg is in a room) playback.started: post: summary: Playback began requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier playback_id: type: string description: Playback identifier playback.finished: post: summary: Playback ended requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier playback_id: type: string description: Playback identifier playback.error: post: summary: Playback failed requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier playback_id: type: string description: Playback identifier error: type: string description: Error message tts.started: post: summary: TTS synthesis began playing requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier tts_id: type: string description: TTS playback identifier tts.finished: post: summary: TTS synthesis finished playing requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier tts_id: type: string description: TTS playback identifier tts.error: post: summary: TTS synthesis or playback failed requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier tts_id: type: string description: TTS playback identifier error: type: string description: Error message recording.started: post: summary: Recording began requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier file: type: string description: Recording file path or S3 URI recording.finished: post: summary: Recording ended requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier file: type: string description: Recording file path or S3 URI multi_channel_file: type: string channels: type: object additionalProperties: $ref: '#/components/schemas/ChannelInfo' room.created: post: summary: Room created requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: room_id: type: string description: Room identifier room.deleted: post: summary: Room deleted requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: room_id: type: string description: Room identifier stt.text: post: summary: Speech-to-text transcript requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier text: type: string description: Transcribed text is_final: type: boolean description: Whether this is a final or partial transcript agent.connected: post: summary: Agent connected to provider requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier conversation_id: type: string description: Provider-assigned conversation identifier agent.disconnected: post: summary: Agent session ended requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier agent.user_transcript: post: summary: User speech transcribed by agent requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier text: type: string description: User speech text agent.agent_response: post: summary: Agent generated a response requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier room_id: type: string description: Room identifier text: type: string description: Agent response text amd.result: post: summary: Answering machine detection completed requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier result: type: string enum: [human, machine, no_speech, not_sure] description: "Detection result: human, machine, no_speech, or not_sure" initial_silence_ms: type: integer description: Milliseconds of silence before first speech greeting_duration_ms: type: integer description: Milliseconds of speech in the greeting total_analysis_ms: type: integer description: Total milliseconds of analysis before determination amd.beep: post: summary: Voicemail beep tone detected after machine classification requestBody: content: application/json: schema: allOf: - $ref: '#/components/schemas/WebhookEvent' - properties: leg_id: type: string description: Leg identifier beep_ms: type: integer description: Milliseconds from machine detection to beep tone detection