# Phone numbers and SIP

> Connect your phone lines to MoreVoice over SIP, as a trunk or a registration account, set their limits, and see the numbers your account can answer and call from.

MoreVoice reaches the phone network over **SIP**: you bring the lines your carrier gives you, and MoreVoice answers and places calls on them. A **connection** is one such line. The **phone numbers** of your account are the numbers those connections answer and call from. Once a line is connected, [inbound routes](https://docs.morevoice.ai/guides/inbound-calls/) decide who answers each number.

> **Live mode only**
>
> Connections carry real calls, so they exist in live mode only: a test key gets `403` with the code [`live_only`](https://docs.morevoice.ai/guides/errors-and-limits/#live-only). Test mode needs no line at all: its calls run on a virtual carrier with [test numbers](https://docs.morevoice.ai/get-started/test-mode/#test-numbers).

## Connections

There are two kinds:

| Kind (`type`) | How calls reach MoreVoice |
| --- | --- |
| **SIP trunk** (`trunk`) | Your carrier sends calls straight to MoreVoice's address. It usually trusts IP addresses, so a username is only needed when the trunk uses digest authentication. |
| **Registration account** (`registration`) | MoreVoice registers to your provider like a desk phone, with a username and a password, and the provider sends calls to that registration. A cloud PBX extension is a registration account. |

Connections speak plain SIP over UDP with G.711 audio: A-law (`pcma`, the standard in Israel and Europe) and μ-law (`pcmu`, the standard in the US). Every connection runs through the same engine: AI agents, routing, recordings and limits work the same way. `backend: "freeswitch"` runs a connection through the optional FreeSWITCH front end instead of the built-in SIP stack; nothing else changes.

### Connect a SIP trunk

Give your carrier MoreVoice's SIP address (the dashboard shows it under **Settings › SIP / Phone**, in the **Phone system** card), then create the connection with the carrier's host and the addresses it sends calls from:

**cURL**

```sh
# Connect a SIP trunk: your carrier sends calls to MoreVoice, from these IP addresses only. Live keys only.
curl https://api.morevoice.ai/v1/connections \
  -H "Authorization: Bearer $MOREVOICE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Main trunk",
    "type": "trunk",
    "host": "sip.carrier.example.com",
    "inbound_acl": ["198.51.100.0/24"],
    "caller_ids": ["+97231234567", "+97231234568"],
    "default_caller_id": "+97231234567",
    "max_channels": 30,
    "inbound_reserve_pct": 20,
    "max_cps": 5,
    "overflow_policy": "unavailable"
  }'
```

**Node.js**

```ts title="create-trunk.ts"
import MoreVoice from "@morevoice/sdk";

const mv = new MoreVoice(); // reads MOREVOICE_API_KEY (a live key: connections are live-only)

// Connect a SIP trunk: your carrier sends calls to MoreVoice, from these IP addresses only.
const connection = await mv.connections.create({
	name: "Main trunk",
	type: "trunk",
	host: "sip.carrier.example.com",
	inbound_acl: ["198.51.100.0/24"],
	caller_ids: ["+97231234567", "+97231234568"],
	default_caller_id: "+97231234567",
	max_channels: 30,
	inbound_reserve_pct: 20, // 6 of the 30 channels stay free for inbound calls
	max_cps: 5,
	overflow_policy: "unavailable", // 503 when full: the carrier can fail over to its backup route
});
console.log(connection.id, connection.status.state);
```

**Python**

```python title="create_trunk.py"
import os
import uuid

import requests

API = "https://api.morevoice.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MOREVOICE_API_KEY']}"}  # a live key: connections are live-only

# Connect a SIP trunk: your carrier sends calls to MoreVoice, from these IP addresses only.
response = requests.post(
    f"{API}/connections",
    headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
    json={
        "name": "Main trunk",
        "type": "trunk",
        "host": "sip.carrier.example.com",
        "inbound_acl": ["198.51.100.0/24"],
        "caller_ids": ["+97231234567", "+97231234568"],
        "default_caller_id": "+97231234567",
        "max_channels": 30,
        "inbound_reserve_pct": 20,  # 6 of the 30 channels stay free for inbound calls
        "max_cps": 5,
        "overflow_policy": "unavailable",  # 503 when full: the carrier can fail over to its backup route
    },
    timeout=30,
)
response.raise_for_status()
connection = response.json()
print(connection["id"], connection["status"]["state"])
```

| Field | What it does |
| --- | --- |
| `host`, `port` | The carrier's SIP host and port (5060 by default). |
| `inbound_acl` | The addresses, single or CIDR ranges, that may send calls on this connection besides `host`. |
| `caller_ids` | The numbers the carrier lets you present on outbound calls. They also become [phone numbers](#phone-numbers) of your account. |
| `default_caller_id` | The caller ID of an outbound call that names none. |
| `max_channels` | Calls in progress at once on the connection; `0` means no limit. |
| `inbound_reserve_pct` | A share of the channels outbound calls can't take, so customers can always get through. |
| `max_cps` | New outbound calls per second. |
| `overflow_policy` | The answer when no channel is free: `busy` (`486 Busy`) or `unavailable` (`503`, which lets a carrier with a backup route fail over). |
| `answer_inbound` | `false` refuses every inbound call on the connection with `486 Busy`. |
| `inbound_assistant_id` | The assistant that answers when no [inbound route](https://docs.morevoice.ai/guides/inbound-calls/) matches a call. |

### Register to a provider

A registration account needs the provider's host, the account's username and its password. `register: true` keeps the registration alive, renewing it every `register_expires_seconds` (300 by default):

**cURL**

```sh
# A registration account: MoreVoice registers to your provider like a desk phone. The password is write-only.
curl https://api.morevoice.ai/v1/connections \
  -H "Authorization: Bearer $MOREVOICE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "name": "Office line",
    "type": "registration",
    "host": "pbx.provider.example.com",
    "username": "0312345670",
    "password": "'"$SIP_PASSWORD"'",
    "register": true,
    "max_channels": 4
  }'

# After you change the account at the provider, register again now and read the result.
curl -X POST https://api.morevoice.ai/v1/connections/$CONNECTION_ID/register \
  -H "Authorization: Bearer $MOREVOICE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

**Node.js**

```ts title="register.ts"
import MoreVoice from "@morevoice/sdk";

const mv = new MoreVoice(); // reads MOREVOICE_API_KEY (a live key)

// A registration account: MoreVoice registers to your provider like a desk phone. The password is write-only.
const line = await mv.connections.create({
	name: "Office line",
	type: "registration",
	host: "pbx.provider.example.com",
	username: "0312345670",
	password: process.env.SIP_PASSWORD!,
	register: true,
	max_channels: 4,
});

// After you change the account at the provider, register again now and read the result.
const registration = await mv.connections.register(line.id);
console.log(registration.status.state, registration.status.error ?? "");
```

**Python**

```python title="register.py"
import os
import uuid

import requests

API = "https://api.morevoice.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MOREVOICE_API_KEY']}"}  # a live key

# A registration account: MoreVoice registers to your provider like a desk phone. The password is write-only.
response = requests.post(
    f"{API}/connections",
    headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
    json={
        "name": "Office line",
        "type": "registration",
        "host": "pbx.provider.example.com",
        "username": "0312345670",
        "password": os.environ["SIP_PASSWORD"],
        "register": True,
        "max_channels": 4,
    },
    timeout=30,
)
response.raise_for_status()
line = response.json()

# After you change the account at the provider, register again now and read the result.
response = requests.post(f"{API}/connections/{line['id']}/register", headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())}, timeout=30)
response.raise_for_status()
status = response.json()["status"]
print(status["state"], status["error"] or "")
```

The password is write-only: no answer ever contains it, and `password_set` says whether one is stored. To change it, send a new `password`; to remove it, send `null`. `POST /v1/connections/{id}/register` registers again at once, for example after you changed the account at the provider, and answers with the state right after the attempt.

### Who may send calls

MoreVoice accepts a new inbound call (`INVITE`) only when it comes from the connection's `host` or from an address in its `inbound_acl`. Anything else is refused with `403 Forbidden`, so nobody can reach your assistants by sending calls to MoreVoice's address directly. When several connections could own a call (two extensions of the same PBX, for example), MoreVoice picks the one whose username or caller IDs match the dialled number.

### Connection status

Every connection carries its live `status`:

| `status.state` | Meaning |
| --- | --- |
| `registered` | A registration account is registered and ready. |
| `listening` | A trunk is ready to receive calls. |
| `registering` | A registration is in progress. |
| `incomplete` | Settings are missing (a host, a username). |
| `failed` | The last registration failed: `status.error` says why. |
| `disabled` | The connection is switched off (`enabled: false`). |

`active_calls` counts the calls on the connection right now. Subscribe to the [`connection.status_changed`](https://docs.morevoice.ai/webhooks/events/#connection.status_changed) event to hear when a registration goes up or down.

Changes to a connection apply to the next call; calls in progress are not affected. A connection that carries calls can't be deleted (`409`).

## Phone numbers

`GET /v1/phone_numbers` lists the numbers of your account, whatever their source:

| `source` | Where the number comes from |
| --- | --- |
| `connection` | A number of one of your SIP connections: its `caller_ids`, and the numbers your inbound routes match exactly. |
| `provisioned` | A number bought through MoreVoice. |
| `sandbox` | A test-mode number on the virtual carrier (test keys list only these). |

**cURL**

```sh
# The numbers on your account: their source, whether they answer inbound calls, and whether they can be a caller ID.
curl "https://api.morevoice.ai/v1/phone_numbers?limit=100" \
  -H "Authorization: Bearer $MOREVOICE_API_KEY"
```

**Node.js**

```ts title="list-numbers.ts"
import MoreVoice from "@morevoice/sdk";

const mv = new MoreVoice(); // reads MOREVOICE_API_KEY

// The numbers on your account: their source, whether they answer inbound calls, and whether they can be a caller ID.
for await (const n of mv.phoneNumbers.list({ limit: 100 })) {
	console.log(n.e164, n.source, n.inbound ? "inbound" : "", n.outbound_caller_id ? "caller ID" : "");
}
```

**Python**

```python title="list_numbers.py"
import os

import requests

API = "https://api.morevoice.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MOREVOICE_API_KEY']}"}

# The numbers on your account: their source, whether they answer inbound calls, and whether they can be a caller ID.
params = {"limit": 100}
while True:
    response = requests.get(f"{API}/phone_numbers", headers=HEADERS, params=params, timeout=30)
    response.raise_for_status()
    page = response.json()
    for n in page["data"]:
        print(n["e164"], n["source"], "inbound" if n["inbound"] else "", "caller ID" if n["outbound_caller_id"] else "")
    if not page["has_more"]:
        break
    params["starting_after"] = page["next_cursor"]
```

`inbound` says whether calls to the number are answered, and `inbound_route_id` names the route that answers them. `outbound_caller_id` says whether the number can be presented on outbound calls. To call from a number, pass its ID as `from_number_id` when you [create a call](https://docs.morevoice.ai/guides/outbound-calls/): it picks the connection and the caller ID together.

> **Coming soon: Buying and porting numbers**
>
> Numbers will be bought, ported and released through the API (`POST /v1/phone_numbers`), with the events `number.provisioned`, `number.released` and `number.port_completed`. Until then, the numbers of your account come from your own SIP connections.

## Set it up in the app

1. Open **Settings › SIP / Phone**. The **Phone system** card turns the SIP stack on and shows its public address. Give this address to your carrier for a trunk.

2. Click **New SIP trunk** or **New SIP account**, fill in the host and the credentials from your carrier, set **Max channels**, **Reserved for inbound**, **Max calls / second** and **Allowed inbound IPs**, and click **Save connection**.

3. Add [inbound routes](https://docs.morevoice.ai/guides/inbound-calls/) for the numbers on the line, and call one of them.
