Phone numbers

Take and place phone calls through your voice agents

Voice agents can answer inbound calls and place outbound calls over SIP. A carrier (LiveKit, Twilio, or your own SIP trunk) provides the phone number; LiveKit bridges the SIP call into a room; your agent joins that room as a participant, exactly like a web caller. This page explains how to import a number, bind it to an agent, and place your first outbound call.

Three ways to import a number

From LiveKit (trial path)

LiveKit offers US phone numbers for quick local testing. LiveKit owns the carrier relationship - you just request the number and it appears in your workspace. These numbers are inbound-only and US-only today, and are not number-portable if you later switch carriers.

$curl -X POST https://api.speechify.ai/v1/phone-numbers \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "e164": "+12025551234",
> "source": "livekit",
> "label": "Trial inbound",
> "agent_id": "agent_01HS..."
> }'

From Twilio (production)

Twilio numbers support inbound and outbound calls globally. This is the recommended path for production deployments.

Steps:

  1. Sign up at twilio.com. After email verification you land on the Twilio Console dashboard.
  2. Buy a phone number: go to Phone NumbersManageBuy a number. Filter by country, capability (Voice), and area code. Click Buy - numbers typically cost $1/month.
  3. Copy your Account SID and Auth Token from the top of the Twilio Console dashboard. The Auth Token is hidden by default - click the eye icon to reveal it.
  4. Import the number via our API or the console Phone Numbers tab:
$curl -X POST https://api.speechify.ai/v1/phone-numbers \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "e164": "+12025551234",
> "source": "twilio",
> "label": "Support line",
> "agent_id": "agent_01HS...",
> "twilio": {
> "account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
> "auth_token": "your_twilio_auth_token"
> }
> }'

On success we provision an Elastic SIP Trunk on your Twilio account pointing at LiveKit’s SIP endpoint (<project-id>.sip.livekit.cloud). Inbound calls to the number flow through that trunk into a LiveKit room, where your agent is dispatched automatically.

Setting up Twilio Elastic SIP Trunking (manual path)

The import flow above handles this automatically. If you prefer to configure Twilio yourself:

  1. In the Twilio Console, go to Elastic SIP TrunkingTrunksCreate new SIP Trunk. Give it a name (e.g. “Speechify production”).

  2. Under Origination on the trunk page, add an Origination URI:

    sip:<your-livekit-project-id>.sip.livekit.cloud

    Set priority 10 and weight 10. This is where Twilio sends inbound calls.

  3. Under Numbers, click Add a Number and attach the phone number you bought.

  4. Under General, enable PSTN Transfer. This is required for the transfer_to_number system tool - without it, blind transfers will fail silently.

  5. Note the trunk’s SIP URI (e.g. myaccount.pstn.twilio.com). You will need this when creating an outbound trunk for placing calls.

  6. Create a SIP trunk in our system pointing at that address:

    $curl -X POST https://api.speechify.ai/v1/sip-trunks \
    > -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
    > -H "Content-Type: application/json" \
    > -d '{
    > "name": "Twilio production",
    > "kind": "twilio",
    > "direction": "both",
    > "sip_address": "myaccount.pstn.twilio.com",
    > "auth_username": "your_sip_username",
    > "auth_password": "your_sip_password",
    > "transport": "auto",
    > "media_encryption": "allow"
    > }'

From SIP trunk (BYOC)

Any SIP provider works: Telnyx, Plivo, Bandwidth, Vonage, or a self-hosted Asterisk/FreeSWITCH instance. You configure the trunk once; all numbers attached to it share the same carrier credentials.

TLS transport is incompatible with SIP REFER (cold transfer). If you need transfer_to_number, configure transport: "udp" or transport: "tcp" on the trunk.

Telnyx example:

  1. In the Telnyx portal, go to SIP TrunkingCreate SIP Connection. Under Inbound set the SIP URI to point at LiveKit’s endpoint.
  2. Under Outbound Voice Profiles, create a profile and associate your numbers.
  3. Note the SIP address (e.g. sip.telnyx.com) and your SIP credentials.
$# Create the trunk
$curl -X POST https://api.speechify.ai/v1/sip-trunks \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "name": "Telnyx BYOC",
> "kind": "byoc",
> "direction": "both",
> "sip_address": "sip.telnyx.com",
> "auth_username": "your_telnyx_sip_user",
> "auth_password": "your_telnyx_sip_password",
> "allowed_addresses": ["216.32.234.0/24"],
> "transport": "udp",
> "media_encryption": "allow"
> }'
$
$# Import your existing number against the trunk
$curl -X POST https://api.speechify.ai/v1/phone-numbers \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "e164": "+12025551234",
> "source": "byoc",
> "trunk_id": "trunk_01HS...",
> "agent_id": "agent_01HS..."
> }'

allowed_addresses accepts individual IPs and CIDR blocks. Leave it empty to accept calls from any source (useful during initial setup; tighten it before going live).

Binding a number to an agent

Each number has an agent_id field. When a call arrives the dispatch rule looks up the number, finds its bound agent, and dispatches that agent’s worker into the room. One agent per number in v1.

Update the binding at any time:

$curl -X PATCH https://api.speechify.ai/v1/phone-numbers/pn_01HS... \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{ "agent_id": "agent_01HS..." }'

Pass "agent_id": null to unbind without deleting the number.

Receiving inbound calls

When someone dials your number:

  1. The carrier routes the SIP INVITE to LiveKit’s SIP endpoint.
  2. LiveKit matches the called number against the dispatch rule and creates a room (one room per call, named after the called number).
  3. The agent worker is auto-dispatched into the room via the dispatch rule’s RoomAgentDispatch config - no webhook round-trip needed.
  4. Inside the room the agent prompt has two system variables populated automatically:
VariableValue
{{system__caller_id}}Caller’s E.164 number (e.g. +41791234567)
{{system__called_number}}Your E.164 number that was dialled (e.g. +12025551234)

These are available in the prompt via {{variable}} substitution (see Dynamic Variables).

Placing outbound calls

POST to /v1/outbound-calls with the destination and the agent that should handle the call:

$curl -X POST https://api.speechify.ai/v1/outbound-calls \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "agent_id": "agent_01HS...",
> "to": "+41791234567",
> "caller_id_number": "+12025551234"
> }'

The response comes back immediately with a conversation_id. Poll GET /v1/conversations/{conversation_id} for the status transition: pending (ringing) → active (answered) → completed.

Outbound requires a Twilio or BYOC trunk. LiveKit-native numbers are inbound-only.

Dynamic variables per call

Inject per-call context into the agent prompt via dynamic_variables. The agent prompt can reference these with {{variable_name}} substitution:

$curl -X POST https://api.speechify.ai/v1/outbound-calls \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "agent_id": "agent_01HS...",
> "to": "+41791234567",
> "dynamic_variables": {
> "customer_name": "\"Alice\"",
> "order_id": "\"ORD-8821\"",
> "outstanding_balance": "142.50"
> }
> }'

Keys must not start with system__. Types must match the variable declarations on the agent.

DTMF auto-navigation

Use dtmf_prefix to navigate an IVR before the agent starts speaking. The digits are dialed after the call is answered.

1{
2 "agent_id": "agent_01HS...",
3 "to": "+18005551234",
4 "dtmf_prefix": "1ww2"
5}

w is a half-second pause; W is a one-second pause. The example above presses 1, waits one second, then presses 2 - useful for navigating “press 1 for support” menus automatically.

Caller ID rotation

For multi-number campaigns, override the caller ID per call with caller_id_number. This must be a number you own (one with outbound capability in your workspace):

1{
2 "agent_id": "agent_01HS...",
3 "to": "+41791234567",
4 "caller_id_number": "+12025559999"
5}

Transferring calls

The transfer_to_number system tool hands the caller off to a different PSTN number via SIP REFER. Enable it on your agent as a system tool. Requires PSTN Transfer enabled on the Twilio trunk (see setup steps above). TLS transport is incompatible with REFER - use UDP or TCP if you need transfers.

See the system tools guide for the full transfer_to_number config (coming with M1.4).

Testing your setup

No US phone? Here is a step-by-step using a Twilio trial and a software SIP client:

  1. Twilio trial account - Sign up at twilio.com (free, includes $15 credit). Verify your email and phone number.
  2. Buy a US number - In the Twilio Console go to Phone NumbersManageBuy a number. Filter by US and Voice. A local number costs $1/month, covered by trial credit.
  3. Import the number - Follow the Twilio import steps above. Paste your Account SID, Auth Token, and the E.164 number. Bind it to an agent.
  4. Install Linphone - Download from linphone.org. It is a free SIP softphone for Mac, Windows, and Linux.
  5. Get a free SIP account - Sign up at onsip.com or sip2sip.info for a free SIP URI (e.g. alice@onsip.com). Configure Linphone with those credentials under PreferencesSIP Accounts.
  6. Place the call - In Linphone, dial your Twilio number (e.g. +12025551234). Your agent answers. You hear its first message.
  7. Test outbound - Use the “Test call” button on the agent detail page in the console. Enter your mobile number (e.g. +41791234567). Your phone rings; when you answer, the agent speaks.

Twilio trial accounts can only call verified phone numbers. Verify your mobile under Phone NumbersVerified Caller IDs before testing outbound.

Limits

ResourceCap
Phone numbers per workspace100
SIP trunks per workspace20
LiveKit-native numbersUS-only, inbound-only
Static egress IPsNot available on LiveKit Cloud - enterprise customers needing IP ACLs for their carrier should contact support about the self-hosted LiveKit migration path

What comes next

  • M1.2 - Outbound call primitives (shipped with this release)
  • M1.4 - System tools catalog: end_call, transfer_to_number, play_keypad_touch_tone config reference
  • M1.6 - Batch calling: dispatch outbound campaigns from a CSV list via the API