curl --request POST \
--url https://{host}/api/v1/calls/{uuid}/conference \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"destination": "<string>",
"kind": "<string>",
"with": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
destination: '<string>',
kind: '<string>',
with: '3c90c3cc-0d44-4b50-8888-8dd25736052a'
})
};
fetch('https://{host}/api/v1/calls/{uuid}/conference', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://{host}/api/v1/calls/{uuid}/conference"
payload = {
"destination": "<string>",
"kind": "<string>",
"with": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"room": "<string>"
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}Merge two of your calls, or dial a third party into this one
Two shapes, chosen by the body.
Merge (with: another call UUID): both parties and the caller’s own leg from this call are moved into one mod_conference room, so all three hear each other. The other call’s agent leg is left bridged to nobody and ends — on a two-line softphone that reads as ‘line 2 hung up, line 1 is now the conference’. Authorised twice, once per call, so an agent can merge only calls they are on; a call they are not on is 404, exactly like a call in another tenant.
Add (destination, optional kind, default extension): the switch dials that person into this call’s room. The destination goes through the same ResolveDestination as transfer, so the allow-list holds and external numbers are refused — a conference adds people here, not outside numbers.
The room is named after the call, so two conferences cannot land in one another’s — which would be a live audio bridge between unrelated customers.
curl --request POST \
--url https://{host}/api/v1/calls/{uuid}/conference \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"destination": "<string>",
"kind": "<string>",
"with": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
destination: '<string>',
kind: '<string>',
with: '3c90c3cc-0d44-4b50-8888-8dd25736052a'
})
};
fetch('https://{host}/api/v1/calls/{uuid}/conference', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://{host}/api/v1/calls/{uuid}/conference"
payload = {
"destination": "<string>",
"kind": "<string>",
"with": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"room": "<string>"
}{
"error": {
"code": "invalid_request",
"message": "<string>"
}
}Authorizations
Every request sends Authorization: Bearer <token>. The token is either a panel session (a JWT from /auth/login, 12 hours) or an API key ft_<id>_<secret>. An API key is accepted only from an address on its IP allowlist (403 ip_not_allowed otherwise; 403 ip_allowlist_required for an old key that has none), is limited to its rate per minute (429 rate_limited with Retry-After; X-RateLimit-Limit/Remaining/Reset on every response), and at most 60 call placements a minute.
Path Parameters
Body
Response
Conferenced