Phone numbers and SIP
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 decide who answers each number.
Connections
Section titled “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
Section titled “Connect a SIP trunk”Give your carrier MoreVoice’s SIP address (the dashboard shows it under SettingsSIP / Phone, in the Phone system card), then create the connection with the carrier’s host and the addresses it sends calls from:
# 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" }'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);import osimport 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 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 matches a call. |
Register to a provider
Section titled “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):
# 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)"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 ?? "");import osimport 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
Section titled “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
Section titled “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 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
Section titled “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). |
# 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"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" : "");}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: it picks the connection and the caller ID together.
Set it up in the app
Section titled “Set it up in the app”-
Open SettingsSIP / Phone. The Phone system card turns the SIP stack on and shows its public address. Give this address to your carrier for a trunk.
-
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.
-
Add inbound routes for the numbers on the line, and call one of them.