> ## Documentation Index
> Fetch the complete documentation index at: https://nenyax.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Telephony Setup

> Connect Twilio phone numbers to Nenyax via LiveKit SIP — mostly automated.

# Telephony Setup

Connect real phone numbers to your Nenyax voice agents using Twilio and LiveKit SIP. Nenyax **auto-provisions** most of the SIP infrastructure — you just need accounts and credentials.

<Note>
  Telephony is **optional**. Browser-based voice testing via the Preview page works without any Twilio or SIP setup.
</Note>

## How It Works

```
Inbound:  Caller --> Twilio --> Elastic SIP Trunk --> LiveKit SIP --> Nenyax Agent
Outbound: Dashboard --> LiveKit SIP --> Twilio Outbound Trunk --> Phone
```

* **Inbound**: Someone dials your Twilio number → Twilio routes via Elastic SIP Trunk to LiveKit → LiveKit dispatches the agent → agent looks up the called number and loads the right config
* **Outbound**: You click "Call" in the dashboard → backend creates a LiveKit room → LiveKit dials out through Twilio → agent joins the conversation

**One agent worker handles all phone numbers** — it dynamically loads the right personality, voice, and instructions based on which number was called.

***

## What's Automated

When you buy or assign a number through the dashboard/API, Nenyax **automatically**:

| Resource                       | Auto-Created                                     |
| ------------------------------ | ------------------------------------------------ |
| LiveKit Inbound SIP Trunk      | Yes — "Nenyax Inbound"                           |
| LiveKit Dispatch Rule          | Yes — routes to `nenyax-agent`                   |
| LiveKit Outbound SIP Trunk     | Yes — "Nenyax Outbound" (on first outbound call) |
| Twilio Elastic SIP Trunk       | Yes — "Nenyax Inbound" with LiveKit origination  |
| Twilio ↔ Number association    | Yes — links your number to the SIP trunk         |
| Twilio termination credentials | Yes — for outbound calls via LiveKit             |

You **don't** need to manually create TwiML Bins, SIP trunks, dispatch rules, or credential lists.

***

## Setup (3 Steps)

### Step 1: Create Accounts

You need two accounts:

1. **[Twilio](https://www.twilio.com/try-twilio)** — free trial works (trial can only call verified numbers)
2. **[LiveKit Cloud](https://cloud.livekit.io)** — free tier available

<Warning>
  **Twilio trial accounts** can only make outbound calls to phone numbers you've verified. Go to **Twilio Console → Phone Numbers → Verified Caller IDs** to add your number. Also enable **Geographic Permissions** for the countries you want to call.
</Warning>

### Step 2: Add API Keys via Dashboard

Open the **Nenyax Dashboard** → **API Keys** (under Build in the sidebar) and configure:

1. **LiveKit** — API Key, API Secret, Server URL (from [LiveKit Cloud](https://cloud.livekit.io))
2. **Twilio** — Account SID, Auth Token (from [Twilio Console](https://console.twilio.com))

Use the **Test Connection** button on each card to verify your credentials.

<Note>
  All keys are encrypted and stored in the database. No `.env` files needed for provider credentials. The agent service auto-fetches keys from the backend on startup.
</Note>

### Step 3: Buy a Number and Assign to an Agent

Start the backend, then use the dashboard or API:

**Option A: Via Dashboard**

1. Open the **Nenyax Dashboard** → **Agents** → select your agent
2. Go to the **Phone** tab → **Search** for available numbers
3. **Buy** a number — it's automatically provisioned and assigned

**Option B: Via API**

```bash theme={null}
# Search for available numbers
curl "http://localhost:8000/api/telephony/numbers/search?area_code=415&country=US"

# Buy and assign to an agent (auto-provisions all SIP infrastructure)
curl -X POST http://localhost:8000/api/telephony/numbers/buy \
  -H "Content-Type: application/json" \
  -H "x-user-id: your-user-id" \
  -d '{"phone_number": "+14155551234", "agent_id": "your-agent-uuid"}'
```

That's it. The backend will automatically:

1. Purchase the number on Twilio
2. Create/reuse the LiveKit inbound SIP trunk
3. Create/reuse the LiveKit dispatch rule
4. Create/reuse the Twilio Elastic SIP trunk with LiveKit origination
5. Associate the number with the trunk

***

## Test It

### Inbound Call

1. Make sure the **backend** and **agent** services are running
2. Call your Twilio number from any phone
3. You should hear the agent's greeting within a few seconds
4. Check the agent terminal for logs:
   ```
   SIP participant detected — trunk phone number: +14155551234
   Resolved SIP call to agent: <agent-id>
   Starting session with system_prompt...
   ```

### Outbound Call

1. Open the dashboard → select an agent with a phone number
2. Click **Make a Call** → enter a destination number → **Call**
3. The backend auto-provisions the outbound trunk on the first call
4. Your phone should ring within a few seconds

<Warning>
  **Twilio trial accounts** can only place outbound calls to verified numbers. Add the destination number at **Twilio Console → Phone Numbers → Verified Caller IDs**.
</Warning>

***

## Managing Numbers

### Assign an Existing Twilio Number

If you already own a Twilio number and want to use it with Nenyax:

**Via Dashboard:** Go to **Agents** → select agent → **Phone** tab → enter the number → **Assign**. Then click **Test & Setup Pipeline** to verify and provision the full SIP routing.

**Via API:**

```bash theme={null}
curl -X POST http://localhost:8000/api/telephony/numbers/assign \
  -H "Content-Type: application/json" \
  -H "x-user-id: your-user-id" \
  -d '{"phone_number": "+14155551234", "agent_id": "your-agent-uuid"}'

# Then provision the SIP pipeline:
curl -X POST http://localhost:8000/api/telephony/numbers/provision \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "your-agent-uuid"}'
```

<Note>
  Numbers bought through the dashboard are auto-provisioned. The **Test & Setup Pipeline** button is mainly needed when assigning numbers purchased directly from Twilio. If the Twilio SIP Trunk step shows a warning, clicking **Test & Setup Pipeline** again will attempt to resolve the Twilio SID automatically and complete the association.
</Note>

### Release a Number

```bash theme={null}
curl -X POST http://localhost:8000/api/telephony/numbers/release \
  -H "Content-Type: application/json" \
  -H "x-user-id: your-user-id" \
  -d '{"agent_id": "your-agent-uuid"}'
```

This unassigns the number from the agent, removes it from LiveKit SIP routing, and disassociates it from the Twilio Elastic SIP Trunk. The number is **not** deleted from your Twilio account — you keep ownership and can re-assign it later.

### Retry Failed Provisioning

If a number was bought but SIP provisioning failed:

```bash theme={null}
curl -X POST http://localhost:8000/api/telephony/numbers/provision \
  -H "Content-Type: application/json" \
  -H "x-user-id: your-user-id" \
  -d '{"agent_id": "your-agent-uuid"}'
```

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Call rings but agent doesn't pick up">
    **Most common cause:** The agent service isn't running.

    1. Start the agent: `cd agent && python agent.py dev`
    2. Verify in LiveKit Cloud → Telephony → Dispatch Rules that a rule exists with `nenyax-agent`
    3. Check the agent terminal for errors (missing API keys, etc.)
  </Accordion>

  <Accordion title="Call immediately disconnects">
    The agent is crashing on startup. Check the agent terminal for errors:

    * **Missing API keys**: AssemblyAI, Google (Gemini), or Resemble AI key not configured at **API Keys**
    * **TTS credits exhausted**: Check your Resemble AI balance at [app.resemble.ai/hub/billing](https://app.resemble.ai/hub/billing)
    * **Key fetch failed**: Check the agent terminal for "Could not fetch keys from backend" — ensure the backend is running
  </Accordion>

  <Accordion title="Inbound call doesn't ring / goes to voicemail">
    Twilio isn't routing to LiveKit:

    1. Go to **Twilio Console → Elastic SIP Trunking** — verify "Nenyax Inbound" trunk exists
    2. Check the trunk has an **Origination URI** pointing to LiveKit (e.g. `sip:2w0eusjtqsa.sip.livekit.cloud`)
    3. Check your phone number is **associated** with the trunk (under Phone Numbers tab)
    4. Check **Twilio → Calls Log** to see if the call attempt was received
    5. If the trunk wasn't auto-created, try the retry endpoint: `POST /api/telephony/numbers/provision`
  </Accordion>

  <Accordion title="Outbound call initiates but phone doesn't ring">
    The outbound SIP trunk may not be configured correctly:

    1. Check **Twilio Console → Elastic SIP Trunking** — verify the trunk has a **Termination SIP URI** set
    2. Check **LiveKit Cloud → Telephony → SIP Trunks** — verify "Nenyax Outbound" exists with the correct Twilio termination address
    3. Check backend logs for `Failed to provision outbound trunk` errors
    4. **Twilio trial accounts**: The destination number must be verified at **Twilio Console → Verified Caller IDs**
    5. **Geographic permissions**: Enable the destination country at **Twilio Console → Voice → Geographic Permissions**
  </Accordion>

  <Accordion title="Agent picks up but uses default config (wrong voice/STT)">
    For **inbound calls**: The phone number isn't assigned to an agent in the database. Go to **Dashboard** → **Agents** → **Phone** tab → assign the number.

    For **outbound calls**: The room metadata might be missing the agent ID. Check that you're using the latest backend code — the outbound call endpoint must include `metadata={"agentId": "..."}` when creating the LiveKit room.

    * Check backend logs for `No agent found for phone +1234...` messages
    * Verify the number is in E.164 format (e.g. `+14155551234`)
  </Accordion>

  <Accordion title="Number purchase failed or provisioning incomplete">
    If the buy succeeded but SIP setup failed:

    1. Check the backend logs for warning messages about provisioning
    2. Use the retry endpoint: `POST /api/telephony/numbers/provision` with `{"agent_id": "..."}`
    3. This re-runs the full provisioning chain without re-purchasing the number
  </Accordion>
</AccordionGroup>

***

## Architecture Details

### Auto-Provisioned Resources

All SIP resources are created with a **reuse-first** strategy — the system checks for existing resources by name before creating new ones:

| Resource                 | Name              | Purpose                                     |
| ------------------------ | ----------------- | ------------------------------------------- |
| LiveKit Inbound Trunk    | "Nenyax Inbound"  | Receives SIP calls from Twilio              |
| LiveKit Dispatch Rule    | "Nenyax Dispatch" | Routes calls to `nenyax-agent` worker       |
| LiveKit Outbound Trunk   | "Nenyax Outbound" | Sends SIP calls to Twilio                   |
| Twilio Elastic SIP Trunk | "Nenyax Inbound"  | Routes calls between Twilio and LiveKit     |
| Twilio Credential List   | "Nenyax LiveKit"  | Authenticates LiveKit → Twilio for outbound |

### How the SIP Domain is Resolved

The SIP domain is auto-derived from your LiveKit Server URL:

```
LiveKit URL: wss://2w0eusjtqsa.livekit.cloud
  --> SIP domain: 2w0eusjtqsa.sip.livekit.cloud
```

No manual configuration needed. If your LiveKit project uses a custom domain, you can override it by saving `twilio_sip_domain` via the settings API.

### Provisioning Code

The provisioning logic lives in `backend/app/services/telephony_provisioning.py`. Key functions:

* `ensure_inbound_trunk()` — Creates/reuses LiveKit inbound trunk
* `ensure_dispatch_rule()` — Creates/reuses dispatch rule
* `ensure_outbound_trunk()` — Creates/reuses outbound trunk + Twilio credentials
* `ensure_twilio_elastic_trunk()` — Creates/reuses Twilio Elastic SIP Trunk

***

## Reference Links

* [LiveKit SIP Trunking](https://docs.livekit.io/sip/)
* [LiveKit + Twilio Setup](https://docs.livekit.io/sip/trunk/twilio/)
* [LiveKit Inbound Trunk](https://docs.livekit.io/sip/trunk/inbound/)
* [LiveKit Outbound Trunk](https://docs.livekit.io/sip/trunk/outbound/)
* [LiveKit Dispatch Rules](https://docs.livekit.io/sip/dispatch-rules/)
* [Twilio Elastic SIP Trunking](https://www.twilio.com/docs/sip-trunking)
