Trunks
Create, list, view, update and delete the trunks on your Superfone account.
Requires an API key, or the session of the account owner or a manager. See Authentication.
The TypeScript examples use the types in TypeScript types. Replace pbx.example.com with your SIP server and 101 with your trunk ID.
Create a trunk
Create a trunk on your account. The trunk is active straight away and takes calls once you have attached at least one number.
HTTP request
POST /sf-trunks
Request body
| Field | Type | Required | Default | Description | Validation |
|---|---|---|---|---|---|
customer_sip_host | string | Yes | - | Public hostname or IPv4 address of your SIP server. We deliver inbound calls here | See Allowed SIP hosts |
customer_sip_port | integer | No | 5060 | Port of your SIP server | 1 to 65535 |
customer_sip_transport | string | No | udp | Transport for calls to your SIP server. Accepts udp, tcp or tls. udp and tcp are supported today, tls is not. See Transport | udp, tcp or tls |
customer_sip_fallback_host | string | null | No | null | Public hostname or IPv4 address of a second SIP server. We try it when your primary server is down. See Fallback destination | See Allowed SIP hosts |
customer_sip_fallback_port | integer | null | No | 5060 when a fallback host is set | Port of your fallback server | 1 to 65535. Needs a fallback host |
customer_sip_fallback_transport | string | null | No | null | Transport for calls to your fallback server. null uses the same transport as your primary server | udp, tcp, tls or null. Needs a fallback host |
call_recording | boolean | null | No | null | true or false sets call recording for the trunk. null follows the call recording setting of each number | true, false or null |
Fields not in this table are ignored. That includes status, which only Superfone can set, and the trunk limits, which you set with Update a trunk.
Allowed SIP hosts
customer_sip_host and customer_sip_fallback_host must be a public IPv4 address or a fully qualified hostname such as pbx.example.com. A hostname can use letters, digits, hyphens and dots. We remove spaces from the start and end of the value.
We refuse:
- IPv6 addresses,
- private addresses:
10.0.0.0/8,172.16.0.0/12and192.168.0.0/16, - loopback (
127.0.0.0/8), link-local (169.254.0.0/16) and shared CGNAT (100.64.0.0/10) addresses, 0.0.0.0/8, and every address from224.0.0.0up,- Superfone's own addresses, and any name under
sft.superfone.co.in.
The same rules apply when you update either host.
When you give a hostname, we look it up for each call and respect its DNS TTL. A change to your DNS record reaches us once the old record expires.
Fallback destination
A fallback is optional. It is a second SIP server that takes calls when your primary server is down.
We always deliver a call to your primary server first. We try the fallback once, and only when the primary is down:
- your primary does not respond within about 5 seconds, or
- your primary replies with a
5xxerror.
We do not try the fallback when your primary rang and nobody answered, or when it rejected the call, for example busy, declined, or any other 4xx or 6xx reply. Unanswered calls ring for up to 60 seconds in total.
To set a fallback, send customer_sip_fallback_host when you create or update the trunk. The port defaults to 5060. The transport defaults to the same transport as your primary server.
- A fallback port or transport without a fallback host returns
400. - A fallback with the same host, port and transport as the primary returns
400. - To remove the fallback, update the trunk with
customer_sip_fallback_hostset tonull. This also clears the fallback port and transport.
curl -X PATCH https://prod-api.superfone.co.in/superfone/sf-trunks/101 \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"customer_sip_fallback_host": "pbx-backup.example.com",
"customer_sip_fallback_port": 5060
}'
Code examples
- cURL
- JavaScript
- TypeScript
- Python
curl -X POST https://prod-api.superfone.co.in/superfone/sf-trunks \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"customer_sip_host": "pbx.example.com",
"customer_sip_port": 5060,
"customer_sip_transport": "udp"
}'
const response = await fetch('https://prod-api.superfone.co.in/superfone/sf-trunks', {
method: 'POST',
headers: {
'X-API-Key': 'your_api_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
customer_sip_host: 'pbx.example.com',
customer_sip_port: 5060,
customer_sip_transport: 'udp'
})
});
const result = await response.json();
console.log(result.data.sf_trunk);
interface CreateSfTrunkRequest {
customer_sip_host: string;
customer_sip_port?: number;
customer_sip_transport?: SfTrunkTransport;
customer_sip_fallback_host?: string | null;
customer_sip_fallback_port?: number | null;
customer_sip_fallback_transport?: SfTrunkTransport | null;
call_recording?: boolean | null;
}
const response = await fetch('https://prod-api.superfone.co.in/superfone/sf-trunks', {
method: 'POST',
headers: {
'X-API-Key': 'your_api_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
customer_sip_host: 'pbx.example.com',
customer_sip_port: 5060,
customer_sip_transport: 'udp'
} as CreateSfTrunkRequest)
});
const result: ApiResponse<{ sf_trunk: SfTrunk }> = await response.json();
console.log(result.data.sf_trunk.fqdn);
import requests
url = 'https://prod-api.superfone.co.in/superfone/sf-trunks'
headers = {
'X-API-Key': 'your_api_key_here',
'Content-Type': 'application/json'
}
payload = {
'customer_sip_host': 'pbx.example.com',
'customer_sip_port': 5060,
'customer_sip_transport': 'udp'
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
print(result['data']['sf_trunk'])
Success response
Status Code: 200 OK
{
"data": {
"sf_trunk": {
"id": 101,
"label": "a1b2c3d4",
"org_id": 12345,
"customer_sip_host": "pbx.example.com",
"customer_sip_port": 5060,
"customer_sip_transport": "udp",
"customer_sip_fallback_host": null,
"customer_sip_fallback_port": null,
"customer_sip_fallback_transport": null,
"cps_limit": null,
"cps_burst": null,
"concurrent_limit": null,
"status": "active",
"call_recording": null,
"created_at": "2026-02-02T10:30:00.000Z",
"updated_at": "2026-02-02T10:30:00.000Z",
"fqdn": "a1b2c3d4.sft.superfone.co.in"
}
},
"message": "success"
}
data.sf_trunk is a trunk object. Keep the label and fqdn. Your PBX sends outbound calls to the FQDN.
A new trunk has no limits of its own. Its calls draw on your organisation's limits. See Trunk limits.
Error responses
| Status Code | Message | When It Occurs |
|---|---|---|
400 | Required | customer_sip_host is missing |
400 | customer_sip_host must be a public hostname or IPv4 address | The host is not allowed |
400 | Number must be less than or equal to 65535 | The port is above 65535. A port below 1 gets Number must be greater than or equal to 1 |
400 | Invalid enum value. Expected 'udp' | 'tcp' | 'tls', received '<value>' | The transport is not one of the three values |
400 | customer_sip_fallback_host must be a public hostname or IPv4 address | The fallback host is not allowed |
400 | customer_sip_fallback_port and customer_sip_fallback_transport require customer_sip_fallback_host | A fallback port or transport is sent without a fallback host |
400 | The fallback SIP destination must differ from the primary (same host, port and transport) | The fallback has the same host, port and transport as the primary |
401 | UnAuthorized, Please Provide Valid API Key | The API key is not valid |
401 | UnAuthorized, Please Login | No valid session |
403 | Only owners and managers can perform this action | You are signed in with a session, but not as the owner or a manager of the account |
500 | Failed to create SF trunk | Something failed on our side |
Example error response
{
"message": "customer_sip_host must be a public hostname or IPv4 address"
}
List trunks
List every trunk on your account, newest first. If you have none, sf_trunks is an empty array.
HTTP request
GET /sf-trunks
Code examples
- cURL
- JavaScript
- TypeScript
- Python
curl https://prod-api.superfone.co.in/superfone/sf-trunks \
-H "X-API-Key: your_api_key_here"
const response = await fetch('https://prod-api.superfone.co.in/superfone/sf-trunks', {
headers: {
'X-API-Key': 'your_api_key_here'
}
});
const result = await response.json();
console.log(result.data.sf_trunks);
const response = await fetch('https://prod-api.superfone.co.in/superfone/sf-trunks', {
headers: {
'X-API-Key': 'your_api_key_here'
}
});
const result: ApiResponse<{ sf_trunks: SfTrunk[] }> = await response.json();
result.data.sf_trunks.forEach(trunk => console.log(trunk.fqdn, trunk.status));
import requests
url = 'https://prod-api.superfone.co.in/superfone/sf-trunks'
headers = {
'X-API-Key': 'your_api_key_here'
}
response = requests.get(url, headers=headers)
result = response.json()
print(result['data']['sf_trunks'])
Success response
Status Code: 200 OK
{
"data": {
"sf_trunks": [
{
"id": 101,
"label": "a1b2c3d4",
"org_id": 12345,
"customer_sip_host": "pbx.example.com",
"customer_sip_port": 5060,
"customer_sip_transport": "udp",
"customer_sip_fallback_host": null,
"customer_sip_fallback_port": null,
"customer_sip_fallback_transport": null,
"cps_limit": 10,
"cps_burst": 20,
"concurrent_limit": 30,
"status": "active",
"call_recording": null,
"created_at": "2026-02-02T10:30:00.000Z",
"updated_at": "2026-02-02T11:05:00.000Z",
"fqdn": "a1b2c3d4.sft.superfone.co.in"
}
]
},
"message": "success"
}
Each item in data.sf_trunks is a trunk object. The list does not include numbers. Get a trunk to see its numbers.
Error responses
| Status Code | Message | When It Occurs |
|---|---|---|
401 | UnAuthorized, Please Provide Valid API Key | The API key is not valid |
401 | UnAuthorized, Please Login | No valid session |
403 | Only owners and managers can perform this action | You are signed in with a session, but not as the owner or a manager of the account |
Get a trunk
Get one trunk and the numbers attached to it.
HTTP request
GET /sf-trunks/:id
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The trunk ID |
Code examples
- cURL
- JavaScript
- TypeScript
- Python
curl https://prod-api.superfone.co.in/superfone/sf-trunks/101 \
-H "X-API-Key: your_api_key_here"
const trunkId = 101;
const response = await fetch(`https://prod-api.superfone.co.in/superfone/sf-trunks/${trunkId}`, {
headers: {
'X-API-Key': 'your_api_key_here'
}
});
const result = await response.json();
console.log(result.data.sf_trunk, result.data.numbers);
interface SfTrunkWithNumbers {
sf_trunk: SfTrunk;
numbers: string[];
}
const trunkId = 101;
const response = await fetch(`https://prod-api.superfone.co.in/superfone/sf-trunks/${trunkId}`, {
headers: {
'X-API-Key': 'your_api_key_here'
}
});
const result: ApiResponse<SfTrunkWithNumbers> = await response.json();
console.log(result.data.sf_trunk.status, result.data.numbers);
import requests
trunk_id = 101
url = f'https://prod-api.superfone.co.in/superfone/sf-trunks/{trunk_id}'
headers = {
'X-API-Key': 'your_api_key_here'
}
response = requests.get(url, headers=headers)
result = response.json()
print(result['data']['sf_trunk'], result['data']['numbers'])
Success response
Status Code: 200 OK
{
"data": {
"sf_trunk": {
"id": 101,
"label": "a1b2c3d4",
"org_id": 12345,
"customer_sip_host": "pbx.example.com",
"customer_sip_port": 5060,
"customer_sip_transport": "udp",
"customer_sip_fallback_host": null,
"customer_sip_fallback_port": null,
"customer_sip_fallback_transport": null,
"cps_limit": 10,
"cps_burst": 20,
"concurrent_limit": 30,
"status": "active",
"call_recording": null,
"created_at": "2026-02-02T10:30:00.000Z",
"updated_at": "2026-02-02T11:05:00.000Z",
"fqdn": "a1b2c3d4.sft.superfone.co.in"
},
"numbers": ["+91XXXXXXXXXX", "+91YYYYYYYYYY"]
},
"message": "success"
}
Response fields
| Field | Type | Description |
|---|---|---|
data.sf_trunk | object | The trunk object |
data.numbers | string[] | Numbers attached to the trunk, in E.164 format, in the order they were attached. Empty if none are attached |
Error responses
| Status Code | Message | When It Occurs |
|---|---|---|
400 | Invalid SF trunk id | id is not a positive whole number |
401 | UnAuthorized, Please Provide Valid API Key | The API key is not valid |
401 | UnAuthorized, Please Login | No valid session |
403 | Only owners and managers can perform this action | You are signed in with a session, but not as the owner or a manager of the account |
404 | SF trunk not found | No trunk with this ID on your account |
Update a trunk
Change where we deliver inbound calls, the fallback destination, the trunk's call recording setting, or its limits. Send only the fields you want to change. Fields you leave out keep their current value.
HTTP request
PATCH /sf-trunks/:id
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The trunk ID |
Request body
| Field | Type | Required | Description | Validation |
|---|---|---|---|---|
customer_sip_host | string | No | Public hostname or IPv4 address of your SIP server | See Allowed SIP hosts |
customer_sip_port | integer | No | Port of your SIP server | 1 to 65535 |
customer_sip_transport | string | No | Transport for calls to your SIP server. Accepts udp, tcp or tls. udp and tcp are supported today, tls is not. See Transport | udp, tcp or tls |
customer_sip_fallback_host | string | null | No | Public hostname or IPv4 address of your fallback server. Send null to remove the fallback. See Fallback destination | See Allowed SIP hosts |
customer_sip_fallback_port | integer | null | No | Port of your fallback server. 5060 when a fallback host is set | 1 to 65535. Needs a fallback host |
customer_sip_fallback_transport | string | null | No | Transport for calls to your fallback server. null uses the same transport as your primary server | udp, tcp, tls or null. Needs a fallback host |
call_recording | boolean | null | No | true or false sets call recording for the trunk. Send null to go back to following each number's setting | true, false or null |
concurrent_limit | integer | null | No | The trunk's own limit on calls in progress at once. Send null to remove it | Positive integer or null. Cannot exceed your organisation's limit |
cps_limit | integer | null | No | The trunk's own limit on new calls per second. Send null to remove it | Positive integer or null. Cannot exceed your organisation's limit |
cps_burst | integer | null | No | The trunk's own peak CPS. Send null to remove it | Positive integer or null. At least cps_limit when both are set. Cannot exceed your organisation's limit |
The recording setting is per trunk, not per number. It covers every number attached to the trunk, in both directions.
You cannot change the status. If you send status, we ignore it and keep the current value. Email hello@superfone.in to change it.
Trunk limits
Trunk limits you can set ship with the org-limits release.
concurrent_limit, cps_limit and cps_burst cap this trunk below your organisation's limits. Each is null by default, which means only the organisation limits apply. Inbound and outbound calls count together.
A trunk limit cannot be higher than your organisation's. Read your organisation's limits with Get Call Limits. To raise them, email hello@superfone.in. See Limits and billing.
Code examples
- cURL
- JavaScript
- TypeScript
- Python
curl -X PATCH https://prod-api.superfone.co.in/superfone/sf-trunks/101 \
-H "X-API-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"customer_sip_host": "pbx2.example.com",
"customer_sip_port": 5080
}'
const trunkId = 101;
const response = await fetch(`https://prod-api.superfone.co.in/superfone/sf-trunks/${trunkId}`, {
method: 'PATCH',
headers: {
'X-API-Key': 'your_api_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
customer_sip_host: 'pbx2.example.com',
customer_sip_port: 5080
})
});
const result = await response.json();
console.log(result.data.sf_trunk);
interface UpdateSfTrunkRequest {
customer_sip_host?: string;
customer_sip_port?: number;
customer_sip_transport?: SfTrunkTransport;
customer_sip_fallback_host?: string | null;
customer_sip_fallback_port?: number | null;
customer_sip_fallback_transport?: SfTrunkTransport | null;
call_recording?: boolean | null;
concurrent_limit?: number | null;
cps_limit?: number | null;
cps_burst?: number | null;
}
const trunkId = 101;
const response = await fetch(`https://prod-api.superfone.co.in/superfone/sf-trunks/${trunkId}`, {
method: 'PATCH',
headers: {
'X-API-Key': 'your_api_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
customer_sip_host: 'pbx2.example.com',
customer_sip_port: 5080
} as UpdateSfTrunkRequest)
});
const result: ApiResponse<{ sf_trunk: SfTrunk }> = await response.json();
console.log(result.data.sf_trunk);
import requests
trunk_id = 101
url = f'https://prod-api.superfone.co.in/superfone/sf-trunks/{trunk_id}'
headers = {
'X-API-Key': 'your_api_key_here',
'Content-Type': 'application/json'
}
payload = {
'customer_sip_host': 'pbx2.example.com',
'customer_sip_port': 5080
}
response = requests.patch(url, headers=headers, json=payload)
result = response.json()
print(result['data']['sf_trunk'])
Success response
Status Code: 200 OK
{
"data": {
"sf_trunk": {
"id": 101,
"label": "a1b2c3d4",
"org_id": 12345,
"customer_sip_host": "pbx2.example.com",
"customer_sip_port": 5080,
"customer_sip_transport": "udp",
"customer_sip_fallback_host": null,
"customer_sip_fallback_port": null,
"customer_sip_fallback_transport": null,
"cps_limit": 10,
"cps_burst": 20,
"concurrent_limit": 30,
"status": "active",
"call_recording": null,
"created_at": "2026-02-02T10:30:00.000Z",
"updated_at": "2026-02-03T09:15:00.000Z",
"fqdn": "a1b2c3d4.sft.superfone.co.in"
}
},
"message": "success"
}
data.sf_trunk is the trunk object after the change. The label and fqdn never change, so your PBX keeps sending outbound calls to the same address.
Error responses
| Status Code | Message | When It Occurs |
|---|---|---|
400 | Invalid SF trunk id | id is not a positive whole number |
400 | customer_sip_host must be a public hostname or IPv4 address | The host is not allowed |
400 | customer_sip_fallback_host must be a public hostname or IPv4 address | The fallback host is not allowed |
400 | customer_sip_fallback_port and customer_sip_fallback_transport require customer_sip_fallback_host | A fallback port or transport is sent but the trunk has no fallback host |
400 | The fallback SIP destination must differ from the primary (same host, port and transport) | The fallback has the same host, port and transport as the primary |
400 | <field> cannot exceed your organisation limit of <n> | concurrent_limit, cps_limit or cps_burst is higher than your organisation's limit. <n> is the organisation's limit |
400 | A validation message, for example Expected number, received string | Another field has the wrong type or is out of range, or cps_burst is below cps_limit |
401 | UnAuthorized, Please Provide Valid API Key | The API key is not valid |
401 | UnAuthorized, Please Login | No valid session |
403 | Only owners and managers can perform this action | You are signed in with a session, but not as the owner or a manager of the account |
404 | SF trunk not found | No trunk with this ID on your account |
Delete a trunk
Delete a trunk and detach all its numbers.
You cannot undo a delete. The trunk's numbers go back to working like any other Superfone number, and you can attach them to another trunk. A trunk you create later gets a new label and FQDN, so you will need to update your PBX.
HTTP request
DELETE /sf-trunks/:id
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The trunk ID |
Code examples
- cURL
- JavaScript
- TypeScript
- Python
curl -X DELETE https://prod-api.superfone.co.in/superfone/sf-trunks/101 \
-H "X-API-Key: your_api_key_here"
const trunkId = 101;
const response = await fetch(`https://prod-api.superfone.co.in/superfone/sf-trunks/${trunkId}`, {
method: 'DELETE',
headers: {
'X-API-Key': 'your_api_key_here'
}
});
const result = await response.json();
console.log(result.data.deleted);
const trunkId = 101;
const response = await fetch(`https://prod-api.superfone.co.in/superfone/sf-trunks/${trunkId}`, {
method: 'DELETE',
headers: {
'X-API-Key': 'your_api_key_here'
}
});
const result: ApiResponse<{ deleted: boolean }> = await response.json();
console.log(result.data.deleted);
import requests
trunk_id = 101
url = f'https://prod-api.superfone.co.in/superfone/sf-trunks/{trunk_id}'
headers = {
'X-API-Key': 'your_api_key_here'
}
response = requests.delete(url, headers=headers)
result = response.json()
print(result['data']['deleted'])
Success response
Status Code: 200 OK
{
"data": {
"deleted": true
},
"message": "success"
}
| Field | Type | Description |
|---|---|---|
data.deleted | boolean | Always true on success |
After the delete, GET /sf-trunks/101 returns 404 with SF trunk not found.
Error responses
| Status Code | Message | When It Occurs |
|---|---|---|
400 | Invalid SF trunk id | id is not a positive whole number |
401 | UnAuthorized, Please Provide Valid API Key | The API key is not valid |
401 | UnAuthorized, Please Login | No valid session |
403 | Only owners and managers can perform this action | You are signed in with a session, but not as the owner or a manager of the account |
404 | SF trunk not found | No trunk with this ID on your account, or it was already deleted |
Next Steps
- Numbers: attach numbers to your trunk
- Outbound calls: point your PBX at the trunk FQDN
- API overview: authentication, errors and the trunk object