Phone Channel
Assign a real phone number to any agent so callers can reach it directly by dialing. Twilio streams the call's raw audio to our backend over a WebSocket (Twilio Media Streams), which relays it directly to the OpenAI Realtime API — the same model, system prompt, and tools the agent uses in the playground.
Prerequisites
The following must be configured in the platform backend before phone numbers can be provisioned:
| Variable | Description |
|---|---|
TWILIO_ACCOUNT_SID | Twilio account SID |
TWILIO_AUTH_TOKEN | Twilio auth token (used for webhook signature verification) |
OPENAI_API_KEY | Required — the phone channel currently only supports agents whose voice model resolves to OpenAI Realtime |
TELEPHONY_ENABLED | Set to true to enable the phone tab in agent settings |
BOT_HOST | Public hostname used to build the Twilio webhook/stream URLs on provisioning and outbound calls |
Only a Twilio account with sufficient balance is required — there is no LiveKit or SIP infrastructure in this path.
Assigning a phone number
- Open an agent in the Playground or from the Agents list.
- Click the Phone tab in the agent configuration panel.
- Enter an optional US area code to filter results (e.g.
415). - Click Search — available Twilio numbers appear in a list.
- Click Assign next to the number you want.
The number is purchased from Twilio immediately and linked to this agent. One number per agent.
Releasing a number
Open the Phone tab on the agent that holds the number and click Release. The number is removed from Twilio and unlinked from the agent. Twilio billing for the number stops at the next billing cycle.
How an inbound call works
Caller dials your number
→ Twilio receives call
→ POST /api/telephony/inbound (TwiML webhook)
→ signature validated (production only)
→ agent looked up by E.164 number
→ TwiML returned: <Connect><Stream url="wss://{bot_host}/api/telephony/stream/{agent_id}" /></Connect>
→ Twilio opens a Media Streams WebSocket to /api/telephony/stream/{agent_id}
→ TwilioStreamHandler relays audio 1:1 to the OpenAI Realtime API over its own WebSocket
(Twilio sends/receives g711 mulaw 8kHz; OpenAI Realtime accepts audio/pcmu directly, no transcoding needed)
→ OpenAI Realtime handles STT, the LLM turn, tool calls, and TTS server-side; audio deltas
are streamed back through the same relay to Twilio in real time (with barge-in support)
The webhook at /api/telephony/inbound is public (no auth) because Twilio must be able to call it. It is protected by Twilio request signature validation in production. In non-production environments validation is skipped so you can test with tools like curl or Postman.
If the dialed number is not found in the database or has no agent assigned, Twilio is told to play a "not in service" message and hang up.
Relay and AI provider
There is no separate "worker" process or SIP room — TwilioStreamHandler (app/voice/twilio_stream_handler.py) opens one WebSocket to Twilio and one to OpenAI Realtime for the lifetime of the call and pipes audio/events between them. Today it only supports OpenAI Realtime (gpt-realtime); the agent's configured voice is mapped to a valid OpenAI Realtime voice name (with fallback for deprecated voice names to their nearest equivalent). Agents configured with other providers (e.g. Gemini) are not yet supported on the phone channel.
The same system prompt, tool integrations (including knowledge base search), and language behavior configured on the agent apply during phone calls. Tool calls made mid-call are dispatched through the platform's tool service and the result is fed back to OpenAI Realtime to continue the response.
API reference
All endpoints require a valid session token and PROJECT:VIEW or PROJECT:CREATE / PROJECT:DELETE permissions as indicated.
Search available numbers
GET /api/projects/{project_id}/telephony/numbers/available
Query params:
| Param | Default | Description |
|---|---|---|
country | US | ISO 3166-1 alpha-2 country code |
area_code | — | Optional 3-digit US area code |
Returns a list of available numbers from Twilio. Numbers are not reserved until provisioned.
List project numbers
GET /api/projects/{project_id}/telephony/numbers
Returns all phone numbers provisioned for this project with their assigned agent_id and status.
Provision a number
POST /api/projects/{project_id}/telephony/numbers
{
"phone_number": "+14155551234",
"agent_id": 42
}
Purchases the number from Twilio and links it to the specified agent. The Twilio webhook is automatically set to /api/telephony/inbound on this deployment.
Release a number
DELETE /api/projects/{project_id}/telephony/numbers/{number_id}
Releases the number from Twilio and removes the database record. Returns 204 No Content.
Start an outbound call
POST /api/agents/{agent_id}/telephony/outbound
{
"to_number": "+14155551234"
}
Places an outbound call from the agent's assigned number to to_number, bridged through the same Twilio Media Streams ↔ OpenAI Realtime relay as inbound calls. The agent speaks first (greets the callee) instead of waiting to be spoken to. Returns the Twilio call_sid and initial call status.
End a call
DELETE /api/agents/{agent_id}/telephony/calls/{call_sid}
Hangs up an in-progress call (inbound or outbound) by Twilio call SID.
Phone number status
| Status | Meaning |
|---|---|
active | Assigned to an agent, ready to receive calls |
unassigned | Provisioned but not linked to an agent |
suspended | Disabled (not currently reachable from the UI) |
Limitations
- One phone number per agent. Assigning a second number requires releasing the first.
- US local numbers only (international numbers require additional Twilio configuration).
- Only agents using OpenAI Realtime are supported on the phone channel — other voice providers (e.g. Gemini) do not yet work over Twilio.
- Outbound calling is single-call only — there is no bulk/campaign dialer, calling-window enforcement, or consent/compliance logging.
- The phone channel shares the agent's voice settings (TTS voice, STT language) configured in the Voice Settings tab.