# SIP trunks

Connect your own PBX or voice AI platform to your Pozy numbers over SIP, so it receives inbound calls directly and places outbound calls with your number.

A SIP trunk takes Pozy out of the audio path for a number. Inbound calls go straight to your SIP host, and your system can place outbound calls through the trunk presenting the number as caller ID. Use it when a PBX, a contact-centre platform or a voice AI platform should own the calls, while the number, its KYC, SMS and WhatsApp messaging stay on Pozy.

## Forwarding or a trunk

Both connect a number to a SIP destination. They differ in who controls the call.

| | Forwarding (`forwardTo: "sip:..."`) | SIP trunk |
| --- | --- | --- |
| Inbound calls reach your system | Yes, bridged by Pozy | Yes, delivered directly |
| Your system can place outbound calls | No | Yes, with your number as caller ID |
| Pozy voicemail, IVR, business hours, recording, browser calling | Available | Paused while the number is on the trunk |
| Call webhooks and history in Pozy | Yes | No: your platform's own call events are the source |
| WhatsApp calling on the number | Works | Not available |
| SMS and WhatsApp messaging | Unaffected | Unaffected |

If you only need inbound calls answered, [forwarding](https://pozy.ai/docs/telephony/voice-calls#route-inbound-calls) is simpler. Choose a trunk when your system must also dial out.

### Create the trunk
`POST /v1/phone-numbers/sip-trunks` with a `label` and your `sipHost`. Set `sipPort` and `transport` (`tls`, `tcp` or `udp`) if your host needs them.

```bash cURL
curl -X POST https://api.pozy.ai/v1/phone-numbers/sip-trunks \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Support PBX", "sipHost": "pbx.acme.example.com", "sipPort": 5061, "transport": "tls" }'
```
```ts Node
const trunkRes = await fetch("https://api.pozy.ai/v1/phone-numbers/sip-trunks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ label: "Support PBX", sipHost: "pbx.acme.example.com", sipPort: 5061, transport: "tls" }),
});
const trunk = await trunkRes.json();
if (!trunkRes.ok) throw new Error(trunk.error);
// Store trunk.digestPassword now. It is only returned here.
console.log(trunk.id, trunk.termination.uri, trunk.termination.username);
```
```python Python
import os, requests

resp = requests.post(
    "https://api.pozy.ai/v1/phone-numbers/sip-trunks",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    json={"label": "Support PBX", "sipHost": "pbx.acme.example.com", "sipPort": 5061, "transport": "tls"},
    timeout=30,
)
trunk = resp.json()
print(trunk["id"], trunk["termination"], trunk["digestPassword"])
```

```json
{
  "id": "66f6a7b8c9d0e1f2a3b4c5d6",
  "label": "Support PBX",
  "sipHost": "pbx.acme.example.com",
  "sipPort": 5061,
  "transport": "tls",
  "termination": { "uri": "sip.example-termination.net", "username": "pz66f6a7b8c9d0e1f2" },
  "numbersAttached": 0,
  "digestPassword": "rK9v...Qw2"
}
```

> **Warning:**
> 
> `digestPassword` is shown only in this response. Store it in your secrets manager. If you lose it, rotate with `POST /v1/phone-numbers/sip-trunks/{id}/rotate-credentials`, which invalidates the old password at once.

### Configure your system
In your PBX or voice platform, point outbound calls at `termination.uri` and authenticate with SIP digest using `termination.username` and the `digestPassword`. No IP allowlisting is needed on either side. Register numbers in E.164 with the leading `+`: the trunk uses `+E.164` in both directions.

### Free the number
A number on a trunk cannot also run Pozy calling. Turn calling off with `DELETE /v1/phone-numbers/{id}/voice`, and turn off WhatsApp calling if it is on.

### Attach the number
`POST /v1/phone-numbers/{id}/sip-trunk` with the `trunkId`. From that moment, inbound calls go to your `sipHost`.

```bash
curl -X POST https://api.pozy.ai/v1/phone-numbers/6650d2e1b3c4d5e6f7081923/sip-trunk \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "trunkId": "66f6a7b8c9d0e1f2a3b4c5d6" }'
```

Detach any time with `DELETE /v1/phone-numbers/{id}/sip-trunk`. Pozy routing and voice features come back immediately.

## One trunk or several

Attach as many numbers as you like to one trunk. Several trunks can point at the same host, each with its own credentials and spend cap. That keeps environments or clients apart: production and staging, or one trunk per agency client.

## Security and media

| Topic | Behaviour |
| --- | --- |
| Signalling | TLS by default (TLS 1.2 or later at the edge). TCP and UDP are available when your host needs them. |
| Media | SRTP is paired with TLS. G.711 (PCMU and PCMA) is native. Any transcoding happens in the carrier core, not on your side. |
| Authentication | SIP digest. Credentials are generated by Pozy, shown once, rotatable, and unique per trunk. |
| Caller ID | Outbound calls can only present numbers hosted in your workspace (or verified onto it). Other `From` numbers are rejected, so the trunk cannot spoof numbers you do not own. |
| Transfers | Inbound SIP REFER is honoured, so your system can transfer callers mid-call. |
| Emergency calls | Not supported |

## Caps

Each trunk carries protective limits. Contact support@pozy.ai to raise them.

| Cap | Default |
| --- | --- |
| Concurrent calls | 10 |
| Daily spend | A ceiling per trunk |
| Destination rate | A per-minute rate cap, which blocks premium-rate routes |
| Destinations | Countries where Pozy sells numbers |

Calls through a trunk bill per minute at the route's normal rate. [Branded calling](https://pozy.ai/docs/telephony/branded-calling) still applies to calls from an attached number.

## Errors

| Status | Code | Cause | Fix |
| --- | --- | --- | --- |
| `409` | `invalid_resource_state` | On attach: the number still has calling or WhatsApp calling on, is mid WhatsApp verification, or is not active | Turn those off, wait for `active`, then attach |
| `409` | `invalid_resource_state` | On voice settings or release: the number is attached to a trunk | Detach it first |
| `403` | `feature_not_available` | Telephony billing is not set up for the workspace | Add a payment method, or contact support@pozy.ai |

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/phone-numbers/sip-trunks`](https://pozy.ai/docs/api/listSipTrunks) | List SIP trunks |
| [`POST /v1/phone-numbers/sip-trunks`](https://pozy.ai/docs/api/createSipTrunk) | Create a SIP trunk |
| [`GET /v1/phone-numbers/sip-trunks/{id}`](https://pozy.ai/docs/api/getSipTrunk) | Get a SIP trunk |
| [`DELETE /v1/phone-numbers/sip-trunks/{id}`](https://pozy.ai/docs/api/deleteSipTrunk) | Delete a SIP trunk |
| [`POST /v1/phone-numbers/sip-trunks/{id}/rotate-credentials`](https://pozy.ai/docs/api/rotateSipTrunkCredentials) | Rotate a SIP trunk's password |
| [`POST /v1/phone-numbers/{id}/sip-trunk`](https://pozy.ai/docs/api/attachNumberToSipTrunk) | Attach a number to a SIP trunk |
| [`DELETE /v1/phone-numbers/{id}/sip-trunk`](https://pozy.ai/docs/api/detachNumberFromSipTrunk) | Detach a number from its SIP trunk |
| [`DELETE /v1/phone-numbers/{id}/voice`](https://pozy.ai/docs/api/disableVoiceOnNumber) | Disable phone calling on a number |
