Skip to main content

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:

VariableDescription
TWILIO_ACCOUNT_SIDTwilio account SID
TWILIO_AUTH_TOKENTwilio auth token (used for webhook signature verification)
OPENAI_API_KEYRequired — the phone channel currently only supports agents whose voice model resolves to OpenAI Realtime
TELEPHONY_ENABLEDSet to true to enable the phone tab in agent settings
BOT_HOSTPublic 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

  1. Open an agent in the Playground or from the Agents list.
  2. Click the Phone tab in the agent configuration panel.
  3. Enter an optional US area code to filter results (e.g. 415).
  4. Click Search — available Twilio numbers appear in a list.
  5. 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:

ParamDefaultDescription
countryUSISO 3166-1 alpha-2 country code
area_codeOptional 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

StatusMeaning
activeAssigned to an agent, ready to receive calls
unassignedProvisioned but not linked to an agent
suspendedDisabled (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.