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

> ## Agent Instructions
> Unmute compiles to exactly three targets. Pipecat and LiveKit are code targets: compile writes a Python project you run. SLNG is a hosted target: compile writes a deployment body and SLNG runs the agent, so it has no `unmute dev`. Those three are the only values `provider` accepts in `targets.yaml`. Deepgram and ElevenLabs appear in these docs as model vendors, which is not the same thing as a target, and `slng` is both.
> The Go structs in `internal/spec` and `internal/ir` are the schema truth. Check a field against them, or run `unmute validate`, rather than against what you remember.

# Phone calls

> How a phone call reaches your agent, which routes exist, and what the transport decides.

A phone call reaches your agent over a route: a target, a transport, and a
carrier. Unmute picks the route from what your target declares and generates the
code for it.

On this page:

* [Declare a phone channel](#declare-a-phone-channel) - `channels:` and `capacity:`, key by key
* [Declare the route](#declare-the-route) - the connection file, and the target that names it
* [The routes](#the-routes) - the four that exist, and where each one deploys
* [What a working phone route takes](#what-a-working-phone-route-takes) - the two carrier-side steps compiling does not do
* [What the transport decides](#what-the-transport-decides) - why a transfer lives on one route and not another
* [Point the carrier at the deployment](#point-the-carrier-at-the-deployment) - the last step, and why there is no local phone loop

## Declare a phone channel

```yaml agent.yaml theme={null}
channels:
  web:
    kind: realtime_audio
  phone:
    kind: telephony
    inbound: true
    outbound: true
```

`inbound` and `outbound` say which directions this agent supports. They are
separate, because most routes support them differently.

### Every key a channel takes

One key is required on any channel. A telephony channel adds two more, and
takes two optional ones.

<ParamField path="kind" type="string" required>
  Accepts `realtime_audio` or `telephony`. No kind is inferred.
</ParamField>

<ParamField path="inbound" type="boolean">
  Accepts `true` or `false` for telephony only. Omitted does not enable inbound calls. At
  least one of `inbound` and `outbound` must be `true`.
</ParamField>

<ParamField path="outbound" type="boolean">
  Accepts `true` or `false` for telephony only. Omitted does not enable outbound calls.
  Required as `true` for warm transfer or voicemail handling.
</ParamField>

<ParamField path="required_controls" type="list of strings">
  Telephony only. Accepts `cold_transfer`, `warm_transfer`, `dtmf_send`, `dtmf_receive`,
  `hold`, `hangup`, `voicemail_detection`, and `ivr_navigation`; the route must support
  each requested control. Omit for no extra explicit requirements.
</ParamField>

<ParamField path="on_voicemail" type="string">
  Accepts `hangup` or `leave_message` where supported by the route. Requires `kind:
      telephony` and `outbound: true`. Omit for no package-defined voicemail action.
</ParamField>

Two rules arrive with that block, and validation enforces both:

* **A warm transfer needs `outbound: true` on the channel.** A warm transfer
  dials the destination itself, so the agent places a call even on a line
  people only ring in on. Without it: `channel "phone" needs outbound: true; a
  warm transfer places a call to its destination`. Cold transfer does not need
  it, because it hands over the caller's existing leg rather than making a
  second one.
* **`capacity.peak_starts_per_second` becomes required, and must be positive.**
  It is optional on a browser-only package and required the moment any channel
  is `telephony`, because calls arrive in bursts and each one starts a session.
  Without it: `capacity.peak_starts_per_second must be positive for telephony`.

```yaml agent.yaml theme={null}
capacity:
  peak_sessions: 5
  max_sessions: 10
  peak_starts_per_second: 1
  avg_session_duration: 5m
```

### Every key capacity takes

`capacity:` is your traffic estimate. It is required for every code target and
for every package with a telephony channel, and the compiler sizes workers and
quotas from it.

<ParamField path="peak_sessions" type="positive integer" required>
  Concurrent sessions you expect at peak.
</ParamField>

<ParamField path="max_sessions" type="positive integer" required>
  The ceiling you want to support. It cannot be lower than `peak_sessions`.
</ParamField>

<ParamField path="peak_starts_per_second" type="positive number">
  How fast calls arrive at peak. Required the moment any channel is
  `telephony`, optional otherwise. One is a fine answer for a first line.
</ParamField>

<ParamField path="avg_session_duration" type="duration, e.g. 5m" required>
  How long an average call lasts, as a positive Go duration.
</ParamField>

## Declare the route

The route lives in a connection file: the mechanism, the carrier, and the
account settings as environment variable names, never values.

```yaml connections/twilio_voice.yaml theme={null}
transport: cloud-websocket
carrier: twilio
environment:
  account_sid: TWILIO_ACCOUNT_SID
  auth_token: TWILIO_AUTH_TOKEN
  from_number: TWILIO_PHONE_NUMBER
```

The target names it and says nothing else about telephony:

```yaml targets.yaml theme={null}
targets:
  pipecat:
    provider: pipecat
    version: "1.10.0"
    connection: twilio_voice
```

So one file is the whole route, and that is the file you open when you want to
know how a call reaches this agent.

A connection has a full route like the one above or a receive-only
`cloud-websocket` route with no credentials.
[`connections/<name>.yaml`](/reference/connections-yaml) explains both shapes.

One target selects exactly one route and one connection. To use two carriers, or
two mechanisms, declare two targets with a connection file each. Each compiles to
its own `build/<target>/` directory.

### Every key a connection takes

Three keys, and the third holds names rather than values.

<ParamField path="transport" type="sip | connector | cloud-websocket | daily-sip" required>
  The mechanism that carries the call. Your target's provider decides which of
  the four it accepts, and a pairing it does not have is refused with the ones
  it does.
</ParamField>

<ParamField path="carrier" type="twilio | telnyx | plivo" required>
  The carrier account behind the route. Telnyx and Plivo reach an agent through
  the LiveKit `sip` route only.
</ParamField>

<ParamField path="environment" type="route key to environment variable name">
  Which variable holds each of the route's account values. The keys on the left
  are fixed by the route: `account_sid`, `auth_token`, `from_number`,
  `sip_address`, `sip_username`, `sip_password`. [Which environment keys a
  route accepts](/reference/connections-yaml#which-environment-keys-a-route-accepts)
  has the set per route, and a key from another route is refused. The names on
  the right are yours, and every one is UPPER\_SNAKE.
</ParamField>

A connection writes no `kind:`. Every transport in the catalog is telephony, so
`transport:` has already said it.

### Every key a target takes

A target says where the agent runs. These are the keys a phone agent uses, and
[`targets.yaml`](/reference/targets-yaml) has the rest.

<ParamField path="provider" type="livekit | pipecat | slng" required>
  Which orchestrator this target compiles to. Only `livekit` and `pipecat`
  carry a phone call.
</ParamField>

<ParamField path="version" type="exact x.y.z">
  The framework version pinned into the emitted project. Required for a code
  target, written with all three numbers, and installed exactly as written.
</ParamField>

<ParamField path="connection" type="connection file stem">
  Which connection carries this target's calls, named without the folder and
  without the `.yaml`. Required for a LiveKit or Pipecat phone agent, and
  refused on a package with no phone use.
</ParamField>

<ParamField path="deployment_region" type="platform region name, or a list of them">
  Where the platform deploys the agent. Pipecat takes exactly one. LiveKit
  takes several, and emits one create command per region.
</ParamField>

<ParamField path="warm_instances" type="integer">
  How many instances the platform holds ready, so a call is not waiting on a
  cold container. Pipecat only, and LiveKit refuses it.
</ParamField>

## The routes

| Target  | Transport         | Carrier               | How the call arrives                                                                                         |
| ------- | ----------------- | --------------------- | ------------------------------------------------------------------------------------------------------------ |
| Pipecat | `cloud-websocket` | Twilio                | Pipecat Cloud terminates the carrier's media stream itself. Nothing of yours is hosted.                      |
| Pipecat | `daily-sip`       | Twilio                | your carrier forwards the call into a Daily room through a helper you host, and Pipecat Cloud runs the agent |
| LiveKit | `sip`             | Twilio, Telnyx, Plivo | a SIP trunk carries the call into LiveKit SIP                                                                |
| LiveKit | `connector`       | Twilio                | a generated bridge turns Twilio Media Streams into a LiveKit room                                            |

Exotel is not listed for LiveKit SIP: no adapter, so this route is refused at validation.

### Where each route deploys

| Route                     | Deploys to                                                           |
| ------------------------- | -------------------------------------------------------------------- |
| Pipecat `cloud-websocket` | Pipecat Cloud                                                        |
| Pipecat `daily-sip`       | Pipecat Cloud, plus the public helper you host                       |
| LiveKit `sip`             | LiveKit Cloud, or a LiveKit deployment of your own                   |
| LiveKit `connector`       | the bridge container you host, connected to a LiveKit Server you run |

Every route above deploys to a managed platform, so a phone call reaches this
agent only once it is deployed. There is no route with nothing to deploy.

The Pipecat Daily helper exposes one public `/call` webhook. It requires the
exact HTTPS base URL in `UNMUTE_PUBLIC_URL` (an optional path is allowed) and
verifies Twilio's signature over the complete form before it uses the Pipecat
Cloud key to start an agent. A missing or invalid signature returns HTTP 403 and
starts nothing.

## What a working phone route takes

Compiling is one of three parts, and the other two are things you do in someone
else's console or CLI. Neither is optional, and skipping either gives you a
number that rings and never connects.

|                         | Pipecat `cloud-websocket`       | LiveKit `sip`                                                                                         |
| ----------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **1. Carrier**          | point the number at a TwiML Bin | attach the number to an Elastic SIP trunk, and point that trunk's origination at LiveKit              |
| **2. Platform**         | `pipecat cloud deploy`          | `lk agent create`                                                                                     |
| **3. Platform records** | none                            | `bash telephony-setup.sh`, which claims the number in your LiveKit project and routes it to the agent |

Step 3 is the one people miss, partly because two different things get called a
<Tooltip tip="A phone line set up between two systems. This route needs two: one on the phone company's side that carries the call, one on LiveKit's side that claims the number.">trunk</Tooltip>. The Elastic SIP trunk is Twilio's, and it carries the call. The inbound
trunk is LiveKit's, and it says the number belongs to your project. LiveKit
rejects a call whose number no inbound trunk claims, however correct the Twilio
side is.

[Twilio setup](/telephony/twilio) covers part 1, and
[inbound calls](/telephony/inbound-calls) walks all three in order.

## What the transport decides

The transport is not a detail. It decides what the agent can do on a call.

* **SIP** hands over a call leg with its own signalling, so the leg can be
  moved. That is why cold transfer, warm transfer, and voicemail detection live
  on the LiveKit `sip` route.
* **A media stream over a websocket** hands over audio frames. Call control
  happens over the carrier's REST API instead, so a transfer is either a
  different mechanism or not possible at all.

[Transfers](/transfers/overview) covers which route can reach a human, and how.

## Point the carrier at the deployment

Your project is deployed by the time you reach this page, so what is left is
carrier-side: finish the setup for your route, then call the number. There is
no local phone loop. [`unmute dev`](/dev/overview) runs the agent in your
browser, and that loop covers the prompt, the tools, and the models, but it
stops exactly where the phone leg starts.

## Where to go next

<Columns cols={2}>
  <Card title="Outbound calls" icon="phone-outgoing" href="/telephony/outbound-calls">
    Make the agent dial out, and give the call its values.
  </Card>

  <Card title="Inbound calls" icon="phone-incoming" href="/telephony/inbound-calls">
    Take a real call, once the route is deployed.
  </Card>

  <Card title="Get your Twilio details" icon="key" href="/telephony/twilio">
    Which console value fills which name.
  </Card>
</Columns>
