{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-docs/voice-2.0/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["partial","admonition"]},"product":"Voice v2 (preview)","type":"markdown"},"seo":{"title":"Integrate an AI Agent with a Bridge (ElevenLabs)","siteUrl":"https://developers.sinch.com","llmstxt":{"title":"Sinch Developer Documentation","description":"LLMs.txt containing a map of all the documentation files for Sinch.","sections":[{"title":"Numbers API","description":"The Numbers API enables you to search for, view, and activate numbers. It's considered a precursor to other APIs in the Sinch product family. The numbers API can be used in tandem with any of our APIs that perform messaging or calling.","includeFiles":["docs/numbers/**/*.md","docs/numbers/**/*.yaml"],"excludeFiles":["docs/numbers/index.md"]},{"title":"Conversation API","description":"Send and receive messages globally on many popular channels with ease and confidence when using Sinch's Conversation API. Conversation API is the preferred API for sending mobile messages on SMS and other social channels with Sinch. It is a simple API with unified error messages, consistent request payloads, and common webhook payloads that are channel-agnostic.","includeFiles":["docs/conversation/**/*.md","docs/conversation/**/*.yaml"],"excludeFiles":["docs/conversation/index.md"]},{"title":"Voice API (legacy/GA)","description":"Which Voice API? If you are building something using voice AI agents, use the Voice API v2. To learn more, you can read [Why migrate to v2?](/docs/voice-2.0/migration.md) or [What's new?](/docs/voice-2.0/whatsnew.md).\n\nThe Voice API works as a big telephony switch. The Voice API handles incoming phone calls (also known as incoming call “legs”), sets up outgoing phone calls (or outgoing call “legs”), and bridges the two. The incoming call leg may come in over a data connection (from a smartphone or web application using the Sinch SDKs) or through a local phone number (from the PSTN network). Similarly, the outgoing call leg can be over data (to another smartphone or web application using the Sinch SDKs) or the PSTN network.","includeFiles":["docs/voice/**/*.md"],"excludeFiles":["docs/voice/index.md"]},{"title":"Voice API v2 (recommended for new builds/preview)","description":"Which Voice API? If you are building something using voice AI agents, use the Voice API v2. To learn more, you can read [Why migrate to v2?](/docs/voice-2.0/migration.md) or [What's new?](/docs/voice-2.0/whatsnew.md).\n\nThe Voice API v2 enables embedding voice calling into applications for virtually limitless use cases. From voice-enabling AI agents to allow real-time conversational AI workflows over the phone, to number masking for privacy, automated appointment reminders, and outreach campaigns with answering machine detection, to bridging calls between PSTN and SIP endpoints, the API offers the flexibility and control needed for modern communications. The Sinch Programmable Voice is a cloud-native global voice platform, composed by multiple Inbound and Outbound voice channels. All these channels can be combined using our powerful markup language called SVAML v2, creating a toolbox capable of building almost limitless use cases. Our SVAML uses the concept of commands, and customers can use a combination of our commands to interact with calls in all channels, in order to model from the most basic to highly complex scenarios, required to fulfill their voice calling needs.","includeFiles":["docs/voice-2.0/**/*.md"],"excludeFiles":["docs/voice-2.0/index.md"]},{"title":"Verification API","description":"The Verification API is a platform for phone number verification. It consists of the API and different software development kits (the Sinch SDKs) that you integrate with your smartphone or web application and cloud based back-end services. Together they enable SMS, Flashcall, Phone Call and Data verification in your application.","includeFiles":["docs/verification/**/*.md","docs/verification/**/*.yaml"],"excludeFiles":["docs/verification/index.md"]},{"title":"Provisioning API","description":"Provisioning API allows you to programmatically set up your senders, accounts and templates on your favorite messaging platforms on the Conversation API. For now, you can create your first WhatsApp channel through Meta's Embedded sign up, you can configure your first SMS App and configure your webhooks. As development continues, we will be adding the most commonly used channels.","includeFiles":["docs/provisioning-api/**/*.md","docs/provisioning-api/**/*.json"],"excludeFiles":["docs/provisioning-api/index.md"]},{"title":"Elastic SIP Trunking API","description":"With Elastic SIP Trunking you can create and manage your SIP trunks and phone numbers programmatically.","includeFiles":["docs/est/**/*.md","docs/est/**/*.yaml"],"excludeFiles":["docs/est/index.md"]},{"title":"Fax API","description":"Send and receive HIPAA compliant faxes on our modern fax platform using our developer-friendly API.","includeFiles":["docs/fax/**/*.md","docs/fax/**/*.yaml"],"excludeFiles":["docs/fax/index.md"]},{"title":"In-app Voice and Video SDK","description":"The In-app Voice and Video SDK enables you to add voice and video calling capabilities directly into your mobile or web application using the Sinch SDKs.","includeFiles":["docs/in-app-calling/**/*.md"],"excludeFiles":["docs/in-app-calling/index.md"]},{"title":"Number Lookup API","description":"The Number Lookup API is designed to provide in-depth information about phone numbers, helping enterprises enhance their communication strategies and prevent fraud. By identifying the type of phone line (for example, mobile, landline, VoIP) and the associated carrier, the API allows businesses to optimize routing, reduce unnecessary costs, and improve customer engagement.","includeFiles":["docs/number-lookup-api-v2/**/*.md","docs/number-lookup-api-v2/**/*.yaml"],"excludeFiles":["docs/number-lookup-api-v2/index.md"]},{"title":"Functions","description":"Serverless compute for voice and messaging. Deploy your code and Sinch routes live calls and messages to it — no infrastructure to run.","includeFiles":["docs/functions/functions/**/*.md","docs/functions/concepts/**/*.md","docs/functions/ai-support.md","docs/functions/reference/**/*.md"],"excludeFiles":["docs/functions/functions/index.md"]},{"title":"CLI","description":"One command line for Sinch numbers, messaging, voice, fax, SIP trunking and porting, and the full Functions lifecycle — scaffold, run locally, deploy, and manage.","includeFiles":["docs/functions/cli/**/*.md"],"excludeFiles":["docs/functions/cli/index.md"]}]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Accordion","attributes":{"title":"Sinch Voice API: Choosing between v1 and v2","expanded":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For all new projects, please use Voice API v2. This is especially important for projects involving:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["AI capabilities like Streams and Voice Relay"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Voice campaigns like Batch calling"]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You should only use the older Voice API v1 if one of the following is a strict requirement:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The project must be in General Availability (GA)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The project requires In-App voice or Conferencing (until these features are available in v2)."]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"integrate-an-ai-agent-with-a-bridge-elevenlabs","__idx":0},"children":["Integrate an AI Agent with a Bridge (ElevenLabs)"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"overview","__idx":1},"children":["Overview"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This tutorial bridges a Sinch voice call into an external AI agent's WebSocket, here an ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://elevenlabs.io"},"children":["ElevenLabs"]}," Conversational AI (\"Eleven Agents\") agent, using a small relay server in the middle. The relay accepts the Sinch ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["STREAM"]}," connection (raw PCM audio in and out) and shuttles that audio to and from the ElevenLabs WebSocket, plus a couple of control messages."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["All the code referenced here lives in the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/sinch/sinch-voice-tutorials"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sinch/sinch-voice-tutorials"]}]}," repository, in this tutorial's folder. Clone it before you start (see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#1-get-the-code"},"children":["Get the code"]},")."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You call your Sinch number's flow, it dials a phone, and the answered call talks to an ElevenLabs voice agent. If you're new to Sinch audio streaming, read ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/stream-audio"},"children":["Stream Call Audio"]}," first for the underlying ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["STREAM"]}," mechanics."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"success","name":"Build this with AI or the CLI"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://dashboard.sinch.com/functions/templates/csharp/11labs-leadgen/deploy"},"children":["Deploy the elevenlabs template"]}]},": it implements exactly this pattern — You place an outbound call through the API. Sinch dials the customer first and only bridges the ElevenLabs agent in once they pick up, so a call nobody answers never reaches ElevenLabs. When the call ends, ElevenLabs sends back a transcript. Fill in the values, then either deploy it as it is or take it into Assistant and change it in chat."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Describe your own instead:"]}," ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://dashboard.sinch.com/functions/builder/new?type=voice"},"children":["open Assistant"]}," and describe what the call should do. It writes, runs and deploys the function for you."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Or use the CLI:"]}," Execute ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sinch functions init 11labs-leadgen"]},", then ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sinch functions dev"]}," so real calls reach your server without a tunnel. Execute ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sinch functions deploy"]}," to put it in production once you're satisfied. See the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/functions/functions/quickstart"},"children":["Sinch Functions quickstart"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"files-in-this-tutorial","__idx":2},"children":["Files in this tutorial"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]}," — WebSocket server that accepts the Sinch stream on one side and connects to ElevenLabs on the other; pipes audio both directions and translates ElevenLabs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["interruption"]}," events into a Sinch ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clear"]}," command (barge-in)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]}," — one-shot script that calls ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v2/projects/{projectId}/calls"]}," to dial a destination and tell Sinch to stream the call audio to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requirements.txt"]}," — Python dependencies."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env.example"]}," — template for the environment variables both scripts read."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["When to use this vs Voice Relay:"]}," Use this bridge when you want a third-party voice-agent SDK (ElevenLabs, OpenAI Realtime, Deepgram Voice Agent, and so on) to run the whole conversation: STT, the LLM, and TTS all live in that vendor. If instead you want Sinch to do speech-to-text and text-to-speech for you and you only exchange plain text with your own backend, use ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/voice-relay"},"children":["Voice Relay"]},"; it is simpler and you never touch raw audio. This bridge is the right tool only when the agent platform owns the voice."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-success-looks-like","__idx":3},"children":["What success looks like"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You run the bridge, run ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]},", your phone (the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DESTINATION_NUMBER"]},") rings, you answer, and you can hold a spoken back-and-forth with the ElevenLabs agent. The bridge logs show ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectRequest"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Answered"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ElevenLabs WebSocket connected"]},", then alternating ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Agent :"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["User :"]}," transcript lines."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"quick-start-minimal-first-success-path","__idx":4},"children":["Quick start (minimal first-success path)"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Order matters: the ElevenLabs credentials and the public WebSocket URL must be in place ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["before"]}," you place the call, and the bridge must be running ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["before"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]}," dials."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"0-get-your-elevenlabs-credentials-first","__idx":5},"children":["0. Get your ElevenLabs credentials first"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You need two things from the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://elevenlabs.io"},"children":["ElevenLabs dashboard"]},":"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ELEVENLABS_API_KEY"]}]}," — your account API key. In the dashboard, open your profile menu and choose ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["API Keys"]}," (or go to Settings → API Keys), then create or copy a key."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ELEVENLABS_AGENT_ID"]}]}," — create a Conversational AI agent under ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Agents"]}," (formerly \"Conversational AI\"), open it, and copy its ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Agent ID"]},". Configure the agent's voice, system prompt, and first message there; the bridge only moves audio, it does not configure the agent."]}]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The agent's input/output audio format is configured ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["on the agent"]}," in the ElevenLabs dashboard (see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#audio-format"},"children":["Audio format"]}," below). Make sure it matches the sample rate you use in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"1-get-the-code","__idx":6},"children":["1. Get the code"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Clone the tutorials repository and change into this tutorial's folder:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"git clone https://github.com/sinch/sinch-voice-tutorials.git\ncd sinch-voice-tutorials/4.3-integrate-ai-agent-bridge   # this tutorial's folder\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["All commands below are run from that folder, which holds ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requirements.txt"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env.example"]},". If you prefer not to clone the whole repository, you can download just this folder from GitHub, but keeping the repo lets you follow the sibling links to ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/stream-audio"},"children":["Stream Call Audio"]}," and ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/voice-relay"},"children":["Voice Relay"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"2-install-dependencies","__idx":7},"children":["2. Install dependencies"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"pip3 install -r requirements.txt\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This installs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["websockets"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["python-dotenv"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requests"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"3-configure-environment-variables","__idx":8},"children":["3. Configure environment variables"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"cp .env.example .env\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Edit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]},":"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Variable"},"children":["Variable"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Description"},"children":["Description"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PORT"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Bridge WebSocket listen port (default ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["8765"]},")"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CONNECT_TIMEOUT_SECONDS"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["How long to wait for the Sinch ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectRequest"]}," before dropping (default ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["10"]},")"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["LOG_LEVEL"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DEBUG"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["INFO"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WARNING"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ERROR"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ELEVENLABS_API_KEY"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["ElevenLabs API key (see step 0)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ELEVENLABS_AGENT_ID"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["ElevenLabs agent ID (see step 0)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PROJECT_ID"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Sinch Voice project ID"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_ID"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_SECRET"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Sinch API credentials (HTTP Basic)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SINCH_NUMBER"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Sinch provisioned phone number (E.164)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DESTINATION_NUMBER"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Phone number to call"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WS_URL"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Public WebSocket URL pointing at ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]}," (e.g. via ngrok)"]}]}]}]}]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Naming note:"]}," this tutorial's code uses ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WS_URL"]},". The sibling tutorial ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/stream-audio"},"children":["Stream Call Audio"]}," uses ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WS_ENDPOINT"]}," for the same concept. They are not interchangeable here: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]}," reads ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WS_URL"]},". If you copied an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}," from 4.2, rename the variable."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"4-start-the-bridge-server","__idx":9},"children":["4. Start the bridge server"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"python3 bridge.py\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["It refuses to start unless ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ELEVENLABS_API_KEY"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ELEVENLABS_AGENT_ID"]}," are set. You should see ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Bridge listening on ws://0.0.0.0:8765"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"5-expose-the-bridge-publicly-with-ngrok","__idx":10},"children":["5. Expose the bridge publicly with ngrok"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In another terminal:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"ngrok http 8765\n# Forwarding  https://abc123.ngrok-free.app -> http://localhost:8765\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Convert the HTTPS forwarding URL to a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["wss://"]}," WebSocket URL and put it in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"header":{"controls":{"copy":{}}},"source":"WS_URL=wss://abc123.ngrok-free.app\n"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Sinch requires ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["wss://"]}," (TLS) reachable from the public internet; a plain ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["http"]},"/",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ws"]}," localhost URL will not work."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"6-place-the-call","__idx":11},"children":["6. Place the call"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In a third terminal:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"python3 stream.py\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Sinch dials ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DESTINATION_NUMBER"]},". When it answers, the audio is bridged to your ElevenLabs agent through ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]},". Pick up and start talking."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"how-it-works","__idx":12},"children":["How it works"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]}," posts a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /v2/projects/{projectId}/calls"]}," with a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dial"]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DESTINATION_NUMBER"]}," and an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["onAnswer"]}," that (a) bridges the phone leg into a bridge named ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge"]},", and (b) issues a second ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dial"]}," to a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["STREAM"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["to"]},", pointing at ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WS_URL"]},", joining the same ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Sinch dials the destination phone. When answered, both legs (phone and stream) share the bridge name ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge"]},", so audio flows bidirectionally."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Sinch opens a WebSocket to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]}," and sends a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectRequest"]},": ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"command\":\"connect\",\"callId\":\"...\", ...}"]},". ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["(Sinch contract, confirmed against the spec and 4.2.)"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]}," replies with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"command\":\"answer\"}"]},", then opens a second WebSocket to ElevenLabs."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Two asyncio tasks ferry audio:",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sinch_to_el"]}," — binary PCM frames from Sinch to base64-wrapped ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"user_audio_chunk\": \"...\"}"]}," JSON to ElevenLabs."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["el_to_sinch"]}," — ElevenLabs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["audio"]}," events to decoded PCM bytes written back to Sinch as binary frames."]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["ElevenLabs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["interruption"]}," events are translated into ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"command\":\"clear\"}"]}," on the Sinch socket; Sinch flushes any audio it had queued so the caller can talk over the agent (barge-in)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["When either side closes, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["asyncio.wait(..., FIRST_COMPLETED)"]}," returns, the other task is cancelled, and the session tears down."]}]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ElevenLabs protocol, verify against ElevenLabs docs."]}," The message shapes the code relies on (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["user_audio_chunk"]}," to send; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["audio"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["audio_event.audio_base_64"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ping"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ping_event.event_id"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agent_response"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["user_transcript"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["interruption"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["conversation_initiation_metadata"]}," received) match the publicly documented ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://elevenlabs.io/docs/eleven-agents/api-reference/eleven-agents/websocket"},"children":["Agent WebSocket"]}," message model at the time of writing, but ElevenLabs evolves this API (it was recently rebranded from \"Conversational AI\" to \"Eleven Agents\"). Treat these shapes, and the audio-format query parameters discussed below, as ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["verify against current ElevenLabs docs"]},", not Sinch facts."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"the-svaml-streampy-posts","__idx":13},"children":["The SVAML ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]}," posts"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For reference, this is the payload ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]}," sends:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"commands\": [\n    {\n      \"command\": \"dial\",\n      \"callName\": \"origin\",\n      \"from\": { \"type\": \"PHONE\", \"phone\": { \"number\": \"<SINCH_NUMBER>\" } },\n      \"to\":   { \"type\": \"PHONE\", \"phone\": { \"number\": \"<DESTINATION_NUMBER>\" } },\n      \"dialTimeoutDurationSeconds\": 30,\n      \"maxCallDurationSeconds\": 180,\n      \"events\": {\n        \"onAnswer\": [\n          { \"command\": \"bridgeCall\", \"bridgeName\": \"bridge\" },\n          {\n            \"command\": \"dial\",\n            \"callName\": \"connect_stream\",\n            \"from\": { \"type\": \"PHONE\", \"phone\": { \"number\": \"<SINCH_NUMBER>\" } },\n            \"to\": {\n              \"type\": \"STREAM\",\n              \"stream\": {\n                \"endpoint\": \"<WS_URL>\",\n                \"streamOptions\": { \"version\": 1, \"codec\": \"PCM\", \"sampleRate\": 8000 },\n                \"callHeaders\": [ { \"key\": \"X-Timeout-Seconds\", \"value\": \"10\" } ]\n              }\n            },\n            \"events\": {\n              \"onAnswer\": [ { \"command\": \"bridgeCall\", \"bridgeName\": \"bridge\" } ]\n            }\n          }\n        ],\n        \"onHangup\": [\n          { \"command\": \"hangup\", \"callName\": \"connect_stream\" }\n        ]\n      }\n    }\n  ]\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"audio-format","__idx":14},"children":["Audio format"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Sinch streams linear PCM at the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sampleRate"]}," you set in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["streamOptions"]},"; the spec accepts ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["8000"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["16000"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["24000"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["44100"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["48000"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["96000"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["codec"]}," is fixed to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PCM"]},". PSTN calls are typically 8 kHz, so 8 kHz is the sensible default and gains nothing from a higher rate on a PSTN-only path."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The two sides must agree on sample rate, or you get chipmunked (too fast) or slowed-down (too slow) audio. Two things must line up:"]},{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["streamOptions.sampleRate"]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]}," (Sinch side)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ElevenLabs ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["agent's"]}," configured input and output audio format (set in the ElevenLabs dashboard). ElevenLabs supports ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pcm_8000"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pcm_16000"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pcm_22050"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pcm_24000"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pcm_44100"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pcm_48000"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ulaw_8000"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To run at 16 kHz, set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["streamOptions.sampleRate"]}," to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["16000"]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["stream.py"]}," and set the agent's input/output format to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pcm_16000"]}," in the ElevenLabs dashboard."]},{"$$mdtype":"Tag","name":"blockquote","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Suspected-wrong, verify against ElevenLabs docs."]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]}," builds the ElevenLabs URL as"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["wss://api.elevenlabs.io/v1/convai/conversation?agent_id=...&output_format=...&input_audio_format=..."]},", defaulting both format query params to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pcm_8000"]}," (they are not part of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env.example"]},")."," ","The current ElevenLabs Agent WebSocket reference lists ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["agent_id"]}]}," as a query parameter but does ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not"]}," document ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["output_format"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["input_audio_format"]}," as query parameters; audio format is configured ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["on the agent"]}," and negotiated via the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["conversation_initiation_metadata"]}," event. These query params may be ignored by the server. ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Configure the format on the agent itself, and verify whether these query overrides are still honored."]}," If audio sounds wrong, the agent-side format is the source of truth. ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["(The base URL ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["wss://api.elevenlabs.io/v1/convai/conversation"]}," and the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["xi-api-key"]}," auth header are the conventional ElevenLabs values; both should be verified against current docs.)"]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"robustness--what-the-code-does-and-does-not-handle","__idx":15},"children":["Robustness — what the code does and does not handle"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]}," is a working demo, not production-hardened. Current behavior:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Sinch socket close / ElevenLabs socket close:"]}," handled. Whichever forwarding task finishes first cancels the other and the session ends cleanly. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectionClosedOK"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectionClosedError"]}," are caught and logged."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectRequest"]}," timeout:"]}," handled (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["CONNECT_TIMEOUT_SECONDS"]},")."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ElevenLabs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ping"]},":"]}," answered with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pong"]}," (keepalive)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["No reconnect / failover:"]}," if the ElevenLabs socket drops mid-call or the service is degraded, the bridge tears the call down; there is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["no bounded reconnect"]}," and no fallback to Voice Relay or TTS. Add one if you need resilience."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["No backpressure / underrun handling:"]}," audio frames are forwarded as fast as they arrive with no buffering policy. Under load you must drop, not buffer indefinitely."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Sinch ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clear"]}," to ElevenLabs:"]}," when Sinch sends ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"command\":\"clear\"}"]}," to the bridge, it is logged as a no-op; ElevenLabs has no documented buffer-clear command in this direction. Barge-in flows the other way (ElevenLabs ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["interruption"]}," to Sinch ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clear"]},"), which is the case that matters."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"production-readiness-checklist","__idx":16},"children":["Production-readiness checklist"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Concern"},"children":["Concern"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"What to do"},"children":["What to do"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Auth for the relay WebSocket"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]}," accepts any WS connection on its port. In production, validate a token in the URL query string or in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callHeaders"]}," (surfaced in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectRequest"]},") before answering."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Per-call state"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The bridge keeps no state across calls. If you need it (analytics, billing, hand-off history), key it by ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callId"]}," from the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectRequest"]},"."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["ElevenLabs costs"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Agent billing is per minute. Cap calls with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["maxCallDurationSeconds"]}," on the SVAML ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["dial"]}," to bound exposure (it is set to 180 s here)."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Failover"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["If ElevenLabs is degraded, the bridge currently drops the call. Consider failing over to ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/voice-relay"},"children":["Voice Relay"]}," or a TTS-only message."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Audio quality"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["If PSTN delivers narrowband audio, 8 kHz is fine. Bump to 16 kHz only when the downstream agent benefits and all three format settings agree."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Barge-in"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["ElevenLabs sends ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["interruption"]}," when the user talks over the agent; the bridge maps it to Sinch ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"command\":\"clear\"}"]}," to flush queued audio. Keep it on."]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prerequisites","__idx":17},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://dashboard.sinch.com"},"children":["Sinch account"]}," with a project, API credentials (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_ID"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["KEY_SECRET"]},"), and a Sinch virtual number."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["An ElevenLabs account with an API key and a configured agent (see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#0-get-your-elevenlabs-credentials-first"},"children":["Quick start step 0"]},")."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Python 3.x."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Git, to clone the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/sinch/sinch-voice-tutorials"},"children":["tutorials repository"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A publicly accessible ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["wss://"]}," URL pointing at ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["bridge.py"]}," (use ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://ngrok.com"},"children":["ngrok"]}," during development)."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-the-openapi-spec-says--at-a-glance","__idx":18},"children":["What the OpenAPI spec says — at a glance"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"em","attributes":{},"children":["(Sinch facts, confirmed against ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["voice-api-v2.openapi.latest.yml"]},".)"]}]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["to: { type: STREAM, stream: { endpoint, streamOptions, callHeaders } }"]}," is the destination type used here. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["endpoint"]}," is required and must be ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ws://"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["wss://"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["streamOptions.codec"]}," is fixed to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PCM"]},"; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["streamOptions.sampleRate"]}," is one of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["8000 | 16000 | 24000 | 44100 | 48000 | 96000"]},", default ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["8000"]},"; ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["streamOptions.version"]}," defaults to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["1"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["callHeaders[]"]}," is a list of up to 16 ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{key, value}"]}," pairs (each value up to 255 chars), surfaced to your WebSocket in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectRequest"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The on-the-wire WebSocket protocol (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConnectRequest"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"command\":\"answer\"}"]}," / binary PCM / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"command\":\"clear\"}"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{\"command\":\"heartbeat\"}"]},") is part of the Sinch Streams product surface, documented alongside ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/voice-2.0/tutorials/stream-audio"},"children":["Stream Call Audio"]},", not in the OpenAPI schema itself."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The ElevenLabs side is documented by ElevenLabs, not Sinch; see the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["verify against ElevenLabs docs"]}," notes above."]}]}]},"headings":[{"value":"Integrate an AI Agent with a Bridge (ElevenLabs)","id":"integrate-an-ai-agent-with-a-bridge-elevenlabs","depth":1},{"value":"Overview","id":"overview","depth":2},{"value":"Files in this tutorial","id":"files-in-this-tutorial","depth":2},{"value":"What success looks like","id":"what-success-looks-like","depth":2},{"value":"Quick start (minimal first-success path)","id":"quick-start-minimal-first-success-path","depth":2},{"value":"0. Get your ElevenLabs credentials first","id":"0-get-your-elevenlabs-credentials-first","depth":3},{"value":"1. Get the code","id":"1-get-the-code","depth":3},{"value":"2. Install dependencies","id":"2-install-dependencies","depth":3},{"value":"3. Configure environment variables","id":"3-configure-environment-variables","depth":3},{"value":"4. Start the bridge server","id":"4-start-the-bridge-server","depth":3},{"value":"5. Expose the bridge publicly with ngrok","id":"5-expose-the-bridge-publicly-with-ngrok","depth":3},{"value":"6. Place the call","id":"6-place-the-call","depth":3},{"value":"How it works","id":"how-it-works","depth":2},{"value":"The SVAML stream.py posts","id":"the-svaml-streampy-posts","depth":2},{"value":"Audio format","id":"audio-format","depth":2},{"value":"Robustness — what the code does and does not handle","id":"robustness--what-the-code-does-and-does-not-handle","depth":2},{"value":"Production-readiness checklist","id":"production-readiness-checklist","depth":2},{"value":"Prerequisites","id":"prerequisites","depth":2},{"value":"What the OpenAPI spec says — at a glance","id":"what-the-openapi-spec-says--at-a-glance","depth":2}],"frontmatter":{"seo":{"title":"Integrate an AI Agent with a Bridge (ElevenLabs)"}},"lastModified":"2026-10-23T15:54:27.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/voice-2.0/tutorials/elevenlabs","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}