# Trunks

> Configure SIP trunks: addressing, codecs, inbound rules, AI handoff.

A **trunk** is a named SIP/RTP egress (and ingress) profile. The public
SDK refers to trunks by `trunk_id` when dialing; this page is how that
`trunk_id` gets created and configured.

> **WARNING:**
> No SDK wrapper. Build the request as a serde envelope and write it on a
> fresh QUIC bidirectional stream. See [Raw RPC Format](/rpc/overview).

## `AddTrunk` / `UpdateTrunk`

`AddTrunk` and `UpdateTrunk` accept the same DTO. Use `Add` for first-time
provisioning; `Update` for in-place edits. Both replace the full record
(no patch semantics) — read with `GetTrunk` first if you only want to
change a subset of fields.

### Request DTO (`AddTrunkRequest` / `UpdateTrunkRequest`)

| Field                          | Type             | Default        | Notes                                              |
| ------------------------------ | ---------------- | -------------- | -------------------------------------------------- |
| `admin_token`                  | `string`         |                | Admin JWT.                                         |
| `trunk_id`                     | `string`         |                | Stable identifier; this is what SDKs reference.    |
| `channel_limit`                | `uint32`         | `50`           | Max concurrent calls on the trunk.                 |
| `internal_sip_ip`              | `string`         |                | Gateway-side SIP bind.                             |
| `external_sip_ip`              | `string`         |                | Public IP advertised in SIP headers (NAT).         |
| `internal_rtp_ip`              | `string`         |                | Gateway-side RTP bind.                             |
| `external_rtp_ip`              | `string`         |                | Public IP for RTP; usually equals `external_sip_ip`. |
| `gateway_ip`                   | `string`         |                | Far-end peer (carrier / SBC).                      |
| `sbc_ip`                       | `string`         |                | If routing through an SBC, its address.            |
| `source_pbx_port`              | `uint16`         | `5060`         | Local SIP source port.                             |
| `destination_sbc_port`         | `uint16`         | `5060`         | Far-end SIP port.                                  |
| `display_name`                 | `string`         |                | Caller-ID `display-name` for outbound INVITEs.     |
| `domain`                       | `string`         |                | SIP `From` domain.                                 |
| `proxy`                        | `string`         |                | Outbound proxy URI, if any.                        |
| `require_registration`         | `bool`           | `false`        | If `true`, gateway registers with `sip_username`/`sip_password`. |
| `sip_username`                 | `string`         |                | Registrar credentials.                             |
| `sip_password`                 | `string`         |                | Registrar credentials.                             |
| `register_expires_sec`         | `uint32`         | `3600`         | REGISTER `Expires` value.                          |
| `codec_preferences`            | `vector<string>` | `[PCMU, PCMA, OPUS]` | Offer order in SDP.                          |
| `rtp_start_port`               | `uint16`         | `16384`        | RTP port range start.                              |
| `rtp_end_port`                 | `uint16`         | `32768`        | RTP port range end.                                |
| `media_ip_cidrs`               | `vector<string>` | `[]`           | Allow-listed media peers; non-matching SDPs are rejected. |
| `reject_incoming_calls`        | `bool`           | `true`         | Default-deny inbound; flip to `false` when you wire `inbound_rule`. |
| `reject_sip_code`              | `uint16`         | `603`          | SIP response code used when rejecting.             |
| `reject_audio_file`            | `string`         |                | URL of audio to play before rejecting (if `200 OK`-then-hangup mode). |
| `ignore_early_media`           | `bool`           | `false`        | Suppress 183 Session Progress audio.               |
| `terminate_on_hold`            | `bool`           | `false`        | Hang up the call instead of going on hold.         |
| `inbound_rule`                 | `int32`          | `0`            | One of `InboundRule` (0=REJECT, 1=PLAY_AND_HANGUP, 2=NOTIFY_AND_HANGUP, 3=HANDLE_AI). |
| `inbound_audio_url`            | `string`         |                | Used by `PLAY_AND_HANGUP`.                         |
| `inbound_webhook_url`          | `string`         |                | Used by `NOTIFY_AND_HANGUP`.                       |
| `inbound_ai_websocket_url`     | `string`         |                | Used by `HANDLE_AI`.                               |
| `inbound_ai_quic_url`          | `string`         |                | Used by `HANDLE_AI` for QUIC-side AI bridges.      |
| `api_bearer_token`             | `string`         |                | Per-trunk webhook signing token.                   |
| `ai_websocket_url`             | `string`         |                | Default AI endpoint for outbound calls on this trunk. |
| `ai_quic_url`                  | `string`         |                | Same, QUIC variant.                                |
| `auto_bargein_mode`            | `string`         | `"energy"`     | `"energy"`, `"vad"`, `"semantic"`, or `"off"`.     |
| `auto_bargein_aggressiveness`  | `int32`          | `2`            | `0`–`3`. Higher = barge sooner.                    |
| `agent_id`                     | `string`         |                | Optional bound agent DAG id (resolved server-side). |

Returns `Empty` on success.

## `RemoveTrunk`

| Field         | Type     |
| ------------- | -------- |
| `admin_token` | `string` |
| `trunk_id`    | `string` |

Returns `Empty`. Existing in-flight calls keep running until they hang up
naturally; new originations against a removed `trunk_id` fail with
`ERR_INVALID_TRUNK`.

## `GetTrunk`

Request:

| Field         | Type     |
| ------------- | -------- |
| `admin_token` | `string` |
| `trunk_id`    | `string` |

Response (`GetTrunkResponse`):

| Field    | Type             | Notes                                |
| -------- | ---------------- | ------------------------------------ |
| `found`  | `bool`           | `false` ⇒ no trunk by that id.       |
| `config` | `ApiTrunkConfig` | Sanitised view (no SIP credentials). |

`ApiTrunkConfig`:

| Field            | Type     | Notes                                                |
| ---------------- | -------- | ---------------------------------------------------- |
| `trunk_id`       | `string` |                                                      |
| `channel_limit`  | `uint32` |                                                      |
| `display_name`   | `string` |                                                      |
| `domain`         | `string` |                                                      |
| `is_cb_tripped`  | `bool`   | Circuit-breaker state.                               |
| `current_asr`    | `double` | Live answer-seizure ratio %.                         |
| `current_acd`    | `uint32` | Live average call duration (seconds).                |
| `current_mos`    | `double` | Live mean opinion score estimate.                    |

The credential-bearing fields (`sip_password`, `api_bearer_token`, …) are
never returned. To rotate them, do another `UpdateTrunk` with the new
values.
