{
  "asyncapi": "3.0.0",
  "info": {
    "title": "Voice Stream",
    "version": "1.0.0",
    "description": "WebSocket-based API for bidirectional voice streaming between the Sinch platform and customer endpoints.\n\nThis service enables real-time audio streaming for outbound calls, supporting use cases such as AI-powered voice assistants,\ninteractive voice applications, and custom telephony integrations.\n\n**Key Features:**\n- Bidirectional audio streaming using PCM-encoded audio frames\n- Flexible audio chunk sizes (e.g., 20ms frames or larger buffered chunks)\n- Stream control commands for audio buffer management and connection keep-alive\n- Support for call acceptance, rejection, and busy signaling\n\n**Audio Buffering:**\nThe platform buffers incoming audio and plays it out to the caller. Use the `clear` command to discard buffered audio\nthat has not yet been played, enabling real-time interruptions for interactive applications like chatbots. The maximum buffer\nis 5MBs per call. If exceeded, older data will be discarded.\n\n**Connection Flow:**\n1. Platform initiates connection with `ConnectRequest`\n2. Client responds with `answer`, `busy`, or `reject`\n3. If answered, bidirectional audio streaming begins\n4. Either party can send audio frames as binary WebSocket messages\n5. Client can send control commands (`clear`, `heartbeat`) during the stream\n\n**Connection Loss:**\nThere is no reconnection mechanism. If the WebSocket connection is lost for any reason, the call is immediately considered hung up and cannot be recovered.\n\n**Latency:**\nEnd-to-end audio latency is the sum of network round-trip time and platform buffering delay. Sending smaller frames (e.g. 20ms)\nreduces buffering delay but increases per-frame overhead; larger frames reduce overhead at the cost of higher latency.\nAudio sent to the platform is queued in the playback buffer before being played out to the caller, so a large backlog of\nbuffered audio will delay playback. Use the `clear` command to flush unplayed audio when low latency is required,\nfor example to interrupt ongoing playback in conversational applications."
  },
  "channels": {
    "outboundStream": {
      "address": "customer/defined",
      "messages": {
        "ConnectRequest": {
          "$ref": "#/components/messages/ConnectRequest"
        },
        "ConnectResponse": {
          "$ref": "#/components/messages/ConnectResponse"
        },
        "AudioFrame": {
          "$ref": "#/components/messages/AudioFrame"
        },
        "StreamControl": {
          "$ref": "#/components/messages/StreamControl"
        }
      }
    }
  },
  "operations": {
    "connect": {
      "summary": "Connection initiated from the Sinch Platform",
      "description": "This is the first message sent from the Sinch platform for all outgoing stream connections.\nThis message carries information about the call, including call headers, the Call-ID, Application-ID, etc.\nBased on the information that this command carries, the customer can make a decision if the request should be accepted or not.",
      "action": "send",
      "channel": {
        "$ref": "#/channels/outboundStream"
      },
      "messages": [
        {
          "$ref": "#/channels/outboundStream/messages/ConnectRequest"
        }
      ]
    },
    "connectResponse": {
      "summary": "Client responds with answer/busy/reject command",
      "description": "After receiving the ConnectRequest, the server must respond with one of three commands:\n- **answer**: Accept and establish the call, enabling bidirectional audio streaming\n- **busy**: Indicate the endpoint is busy, which terminates the connection\n- **reject**: Decline the call, which terminates the connection\n\nThis response determines whether the WebSocket connection proceeds to the audio streaming phase or is closed.",
      "action": "receive",
      "channel": {
        "$ref": "#/channels/outboundStream"
      },
      "messages": [
        {
          "$ref": "#/channels/outboundStream/messages/ConnectResponse"
        }
      ]
    },
    "platformToClientAudio": {
      "summary": "Platform sends audio frames to the web-socket server (binary WebSocket messages)",
      "description": "Once the server answers the call, audio can flow from the platform to the server as binary WebSocket frames.",
      "action": "send",
      "channel": {
        "$ref": "#/channels/outboundStream"
      },
      "messages": [
        {
          "$ref": "#/channels/outboundStream/messages/AudioFrame"
        }
      ]
    },
    "serverToPlatformAudio": {
      "summary": "Client sends audio frames to the platform (binary WebSocket messages)",
      "description": "Once the server answers the call, audio can flow from the server to the platform as binary WebSocket frames.",
      "action": "receive",
      "channel": {
        "$ref": "#/channels/outboundStream"
      },
      "messages": [
        {
          "$ref": "#/channels/outboundStream/messages/AudioFrame"
        }
      ]
    },
    "streamCommands": {
      "summary": "Client sends control messages to the platform.",
      "description": "Client can send control commands to manage the audio stream during an active call.\nSupported commands:\n- **clear**: Discard buffered audio that has not yet been played, enabling real-time interruptions\n- **heartbeat**: Keep the connection alive during silence (1-minute no-audio timeout)",
      "action": "receive",
      "channel": {
        "$ref": "#/channels/outboundStream"
      },
      "messages": [
        {
          "$ref": "#/channels/outboundStream/messages/StreamControl"
        }
      ]
    }
  },
  "components": {
    "messages": {
      "ConnectRequest": {
        "payload": {
          "$ref": "#/components/schemas/ConnectRequestPayload"
        }
      },
      "ConnectResponse": {
        "payload": {
          "$ref": "#/components/schemas/ConnectResponsePayload"
        }
      },
      "AudioFrame": {
        "name": "AudioFrame",
        "title": "Audio frame (binary)",
        "summary": "Raw PCM audio bytes sent as a WebSocket binary message",
        "description": "Raw PCM (Pulse Code Modulation) audio bytes transmitted as a binary WebSocket frame.\n\n**Format:** 16-bit signed linear PCM, mono (single channel), little-endian byte order.\n\n**Frame size:** Audio should be sent in 20ms chunks. At 8 kHz this is 320 bytes per frame (160 samples × 2 bytes);\nat 16 kHz it is 640 bytes per frame. Larger chunks are accepted but increase latency.\n\nThe sample rate is determined by the value specified in the SVAML `connectStream` command.",
        "payload": {
          "$ref": "#/components/schemas/AudioFramePayload"
        }
      },
      "StreamControl": {
        "summary": "Commands that can be executed on an ongoing stream.",
        "payload": {
          "$ref": "#/components/schemas/StreamControlPayload"
        }
      }
    },
    "schemas": {
      "ConnectRequestPayload": {
        "type": "object",
        "required": [
          "command",
          "version",
          "callId",
          "applicationKey",
          "codec",
          "sampleRate",
          "callHeaders"
        ],
        "additionalProperties": false,
        "properties": {
          "command": {
            "type": "string",
            "const": "connect",
            "description": "Connect message"
          },
          "version": {
            "type": "integer",
            "const": 1,
            "description": "Protocol version"
          },
          "callId": {
            "type": "string",
            "description": "Call unique identifier"
          },
          "applicationKey": {
            "type": "string",
            "description": "Service unique identifier"
          },
          "codec": {
            "type": "string",
            "const": "PCM",
            "description": "Audio codec used for encoding binary audio frames transmitted over the WebSocket connection.\nAudio frames are 16-bit signed linear PCM, mono (single channel), with little-endian byte order."
          },
          "sampleRate": {
            "type": "integer",
            "enum": [
              8000,
              16000,
              24000,
              44100,
              48000,
              96000
            ],
            "x-enumDescriptions": {
              "8000": "8 kHz (standard for PSTN calls)",
              "16000": "16 kHz (wideband audio)",
              "24000": "24 kHz (super-wideband audio)",
              "44100": "44.1 kHz (CD quality audio)",
              "48000": "48 kHz (professional audio quality)",
              "96000": "96 kHz (high-resolution audio)"
            },
            "description": "Audio sample rate in Hertz (Hz). The value is negotiated at call setup via the SVAML `connectStream` command.\nHigher sample rates provide better audio quality but increase bandwidth and processing requirements.\n- **8000 Hz**: Narrowband telephony quality (standard PSTN)\n- **16000 Hz**: Wideband quality, suitable for most voice applications\n- **24000 Hz**: High-quality voice\n- **44100 Hz**: CD-quality audio\n- **48000 Hz**: Professional audio standard\n- **96000 Hz**: Ultra high-quality audio\n\nFor calls traversing the PSTN, audio is typically sampled at 8 kHz; using a higher value will not improve perceived quality."
          },
          "callHeaders": {
            "type": "object",
            "description": "Arbitrary key/value pairs (header name -> header value).",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "example": {
          "command": "connect",
          "version": 1,
          "callId": "7856b65f-e3b5-4b25-8825-77ee10b57602",
          "applicationKey": "5c90f98a-5002-4f8a-9b02-c1cb552bed95",
          "codec": "PCM",
          "sampleRate": 8000,
          "callHeaders": {
            "key1": "value1",
            "key2": "value2"
          }
        }
      },
      "ConnectResponsePayload": {
        "type": "object",
        "required": [
          "command"
        ],
        "additionalProperties": false,
        "properties": {
          "command": {
            "type": "string",
            "description": "Response to the incoming connection request.",
            "enum": [
              "answer",
              "busy",
              "reject"
            ],
            "x-enumDescriptions": {
              "answer": "Indicates that the call is accepted and answered. This enables the binary stream flow.",
              "busy": "Indicates that the called party was busy. Closes the web-socket connection.",
              "reject": "Indicates that the call was rejected. Closes the web-socket connection."
            }
          }
        },
        "example": {
          "command": "answer"
        }
      },
      "AudioFramePayload": {
        "type": "string",
        "format": "binary",
        "contentMediaType": "application/octet-stream"
      },
      "StreamControlPayload": {
        "type": "object",
        "description": "Actions that can be performed on an ongoing stream",
        "additionalProperties": false,
        "properties": {
          "command": {
            "type": "string",
            "enum": [
              "clear",
              "heartbeat"
            ],
            "x-enumDescriptions": {
              "clear": "Clears the audio buffer on the Sinch platform. Use this command to discard any buffered audio data that has not yet been played out to the caller.",
              "heartbeat": "Indicates that the connection is still active. Use this command during periods of silence to prevent timeout and maintain the stream connection. The no-audio timeout is 1 minute."
            }
          }
        }
      }
    }
  }
}