# SIP Trunks overview

A SIP trunk is a phone line for your phone system that runs over the internet instead of copper.
Your office phone system, called a PBX (private branch exchange), sends and receives calls using
SIP, the signalling protocol most IP phone systems speak. A trunk connects that PBX to Signals, and
Signals connects it to the public phone network in Nigeria and beyond.

You keep your PBX, your extensions and your call flows. Signals carries the calls between your PBX
and the outside world, enforces the limits you set, and tells you what happened to each call.

## What an Intelligent SIP Trunk is on Signals

On Signals, a trunk is a configuration object you create with the API or the console. It describes
how your PBX connects and what it is allowed to do. Each trunk has an ID that starts with `trk_`,
for example `trk_01992f3a-6c1e-7d42-9b8a-5e7f0c2d1a94`.

A trunk brings together these parts:

| Part | What it controls | Field or endpoint |
| --- | --- | --- |
| Direction | Whether the trunk carries calls into your PBX, out of it, or both. | `direction`: `inbound`, `outbound` or `both` |
| Authentication | How Signals recognises calls that come from your PBX. | `auth_mode`: `ip_allow_list` or `digest` |
| Termination targets | Where Signals delivers inbound calls: the SIP addresses of your PBX. | `targets` on create, then `/v1/trunks/{trunk_id}/termination-uris` |
| Media | Which audio codecs the trunk offers and whether audio is encrypted. | `codecs`, `media_security` |
| Limits | How many calls run at once, how fast new calls start, and how long a call may last. | `limits.concurrency`, `limits.cps`, `limits.max_call_duration_seconds` |
| Outbound policy | Which destinations your PBX may call, spend caps and recording. | `outbound_policy_id` on create, then `PUT /v1/trunks/{trunk_id}/outbound-policy` |
| Numbers | Which phone numbers ring this trunk, and which numbers it may use as caller ID. | `number_block_ids` on create, then block routes under `/v1/numbers/blocks` |
| Subtrunks | Child configurations under one trunk, each with tighter limits. | `/v1/trunks/{trunk_id}/subtrunks` |
| Tests and diagnostics | Checks that the trunk's configuration is complete, and evidence from real calls. | `/v1/trunks/{trunk_id}/tests`, `/diagnostics`, `/metrics` |

### Direction

An `inbound` trunk receives calls to your numbers and delivers them to your PBX. An `outbound`
trunk lets your PBX place calls to the phone network. A `both` trunk does both. A trunk that takes
inbound calls (`inbound` or `both`) needs at least one termination target, or the API refuses it.

### Authentication

Signals must know that a call really comes from your PBX before it carries it. You choose one of
two methods per trunk:

- **IP allow list** (`ip_allow_list`): Signals accepts calls from the public IP addresses you list.
- **Digest** (`digest`): your PBX answers a username and password challenge. You choose the realm
  with `digest_realm`, Signals generates the username and password, and the create response shows
  the password once as `digest_password`.

[Authentication and security](/sip-trunks/authentication-and-security) explains both, along with
TLS and encrypted media.

### Termination targets

A termination target is a SIP address of your PBX, such as `sip:203.0.113.10:5061`, with a
transport (`udp`, `tcp` or `tls`), a `priority` and a `weight`. Signals tries targets with the
lowest `priority` value first. Targets that share a priority split calls by `weight`. Signals does
not probe your targets with SIP OPTIONS; it judges target health from real call attempts.

### Media

`codecs` lists the audio codecs the trunk offers: `PCMA`, `PCMU`, `G729`, `G722` and `OPUS`. G.711
(`PCMA` and `PCMU`) is always on; add the others you want. Signals offers them in a fixed order
(OPUS, G722, G729, PCMA, PCMU), so the order you send does not matter. A codec may appear only once.

The media security setting decides whether audio is encrypted: `rtp` sends plain RTP, and
`sdes_srtp_required` requires SRTP. In both cases audio flows through the Signals media address
listed under [platform endpoints](#platform-endpoints).

### Limits

Limits protect you from runaway traffic and unexpected cost:

- `concurrency` is the number of calls that may be active at the same time, inbound and outbound
  together.
- `cps` is the number of new outbound calls that may start each second. Inbound calls are not
  refused for CPS.
- `max_call_duration_seconds` is the longest a single call may last.

If you omit `limits` when you create a trunk, Signals uses 100 concurrent calls, 10 calls per second
and 7,200 seconds (two hours). Signals checks limits at every level that applies to a call: your
organization, the subaccount if there is one, the trunk, and the outbound policy. The tightest
limit wins.

### Outbound policy

An outbound policy is a named, reusable set of rules for outbound calls: which countries and
prefixes your PBX may call, spend caps and recording. You attach one policy to a trunk, either in
the create request with `outbound_policy_id` or later. A change to the policy reaches every trunk
that uses it within a minute. A trunk without a policy refuses every outbound call. See
[Outbound policies](/sip-trunks/outbound-policies).

### Numbers

You buy phone numbers in contiguous blocks and route them, or slices of them, to a trunk. Calls to
those numbers arrive on the trunk. The numbers routed to a trunk are also the caller IDs it may
present on outbound calls. See [Numbers and routing](/sip-trunks/numbers-and-routing).

## Saving changes

Every change you make is live once the API accepts it. There is no draft to publish.

- **Validation happens at the save.** Creating or editing a trunk, a credential, a termination
  target or a policy either fails with a `4xx` error or succeeds with `200` (`201` on create) and
  the resource as stored.
- **A change that would leave the trunk unable to run is refused.** For example, an invalid
  inbound URI, an invalid source list, or limits above your organization's limits fail with
  `422 Unprocessable Content` and a problem detail that starts with `the trunk would not apply:`.
- **Signals applies accepted changes to the network for you.** Changes take effect within a minute.
  If a change cannot be applied, PressOne staff are alerted and the previous configuration keeps
  serving. You do not wait for it or poll for it.

### Revisions and ETags

Each trunk has a `revision`, an integer that goes up with every saved change. Responses carry it in
the `ETag` header as a quoted integer, for example `"3"`. Edits require that value in an `If-Match`
header, so two people cannot overwrite each other's changes:

- A missing `If-Match` returns `428 Precondition Required`.
- A value that no longer matches returns `412 Precondition Failed`. Read the trunk again to get the
  current ETag.
- A malformed value returns `400 Bad Request`.

The revision is only a concurrency token. See
[Requests and errors](/getting-started/requests-and-errors) for the general rules.

## Trunk status

The `status` field tells you where a trunk is in its life:

| Status | Meaning |
| --- | --- |
| `enabled` | Admitting new calls. |
| `disabled` | Not admitting new calls. |
| `suspended` | Stopped from admitting new calls with a suspend. |
| `releasing` | A release was requested and the network is removing the trunk. |
| `released` | Removed from the network. |

`POST /v1/trunks/{trunk_id}/suspend` stops new calls within a minute. Calls already in progress
continue. `POST /v1/trunks/{trunk_id}/resume` allows new calls again.

`DELETE /v1/trunks/{trunk_id}` releases a trunk. You confirm the exact trunk name in the body. The
API refuses with `409` if the trunk still has active calls (`trunk_not_drained`) or still has routes,
subtrunks or policies attached (`trunk_referenced`).

## Tests and diagnostics

- `POST /v1/trunks/{trunk_id}/tests` with `"kind": "config_validation"` checks the trunk's
  configuration: that it has an active authentication method, termination targets where needed,
  and admission approval. It answers `202 Accepted`; read the result from
  `GET /v1/trunks/{trunk_id}/tests/{test_id}` and its `checks`. The `rtp_reachability` and
  `test_call` kinds are part of the contract but return `422 diagnostic_test_unsupported` today.
- `GET /v1/trunks/{trunk_id}/diagnostics` returns audio quality measured on real calls over the last
  24 hours: MOS, packet loss, jitter, and counts of one-way-audio and no-audio calls.
- `GET /v1/trunks/{trunk_id}/metrics` returns measured call outcomes for a time window.
- The trunk's `endpoint_evidence` lists each termination target with its state (`healthy`,
  `degraded`, `idle` or `unknown`), based only on call attempts in the last 24 hours.

## Platform endpoints

Your PBX needs the public addresses of Signals: where to send SIP, which ports to use, and which
address audio comes from. Read them from the API rather than hard-coding them:

```bash
curl https://api.signals.pressone.africa/v1/platform/endpoints \
  -H "Authorization: Bearer $SIGNALS_API_KEY"
```

```json
{
  "version": 1,
  "as_of": "2026-09-07T00:00:00Z",
  "signaling_fqdn": "sip.trunk.pressone.co",
  "signaling_nodes": ["198.51.100.10"],
  "signaling_ports": [
    { "transport": "udp", "port": 5060 },
    { "transport": "tcp", "port": 5060 },
    { "transport": "tls", "port": 5061 }
  ],
  "signaling_port": 5061,
  "tls": { "realm": "sip.trunk.pressone.co", "trust_chain": [] },
  "media_anchors": ["198.51.100.10"],
  "media_cidrs": ["198.51.100.10/32"],
  "rtp_port_range": "10000-20000",
  "rtp_ports": { "start": 10000, "end": 20000 }
}
```

| Field | Meaning |
| --- | --- |
| `signaling_fqdn` | The Signals SIP domain. Each trunk also gets its own name under it in the trunk's `signaling_fqdn`, for example `office-pbx.sip.trunk.pressone.co`. |
| `signaling_nodes` | The public IP addresses that send and receive SIP. Allow them in your firewall. |
| `signaling_ports` | SIP over UDP and TCP on 5060, and SIP over TLS on 5061. `signaling_port` repeats the TLS port. |
| `tls.realm` | The TLS name of the Signals SIP service. |
| `media_anchors`, `media_cidrs` | The addresses audio (RTP) comes from and goes to. |
| `rtp_ports` | The UDP ports Signals uses for audio, 10000 to 20000. |

:::note
The RTP port range is the set of UDP ports to allow in your firewall for audio from the Signals
media address. It is not a limit on how many calls you can run. Concurrency is set by your trunk
and outbound policy limits.
:::

The `version` and `as_of` fields change when PressOne updates these values. Check them when you
plan firewall changes.

## Who can do what

API keys and console users carry a role. Admin and engineer roles can create and edit trunks,
policies and number routes. The viewer role can read trunks and policies. See
[Authentication](/getting-started/authentication).

## Next steps

- [Quickstart](/sip-trunks/quickstart): create and test a trunk with curl.
- [Connect your PBX](/sip-trunks/connect-your-pbx): settings for Asterisk, FreePBX and 3CX.
- [SIP Trunking API reference](/api/sip-trunking) and [Platform API reference](/api/platform).
