Appearance
Three-way conference calls
Español: Conferencias a tres bandas.
Use a conference when the agent and consumer need to bring one more person into an active call. The original agent–consumer call stays in place while the third party joins through a separate leg.
Contract baseline: core
v0.95.0. This guide describes the API contract in that published release.
Before you add a third party
Conference access is off by default and is controlled per tenant:
conference_enabledopens conferences with an internal roster destination.conference_external_enabledadditionally permits a registered external number and requiresconference_enabled.
These are platform-operator settings. A tenant application can read the effective internal capability with GET /v1/me:
json
{
"capabilities": {
"conference": true
}
}If capabilities.conference is false, both conference routes return 404 not_found. This is fail-closed: the request body is not read, the call is not looked up and no rate budget is spent.
Authentication and control
The add and remove routes accept either a tenant API key (dd_) or a user session token (ddu_). The caller must still control the call. An agent who no longer controls it receives 403 forbidden. Widget tokens (ddw_) cannot call these routes and receive 403 forbidden.
Choose and configure the destination
The destination field accepts one of these values:
- an active roster agent's
sip_extension; - an active roster agent's UUID;
- when external conference is enabled, a current transfer-destination UUID or its registered strict-E.164 number.
The server trims destination; the result must be non-blank and no longer than 64 bytes. A number-shaped value must use strict E.164 (+ followed by 8–15 digits).
An external number must first be registered with POST /v1/transfer-destinations. That management route accepts a dd_ key or an account_admin user session; a member session receives 403 role_forbidden.
bash
curl -sS -X POST "$API/v1/transfer-destinations" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"number": "+13125550170",
"label": "Legal department",
"line_type": "landline"
}'number and label are required. label is trimmed, must not be blank and is limited to 120 characters. line_type is optional; its values are mobile, landline, voip and unknown, with unknown as the default. Registering a number does not bypass compliance: the external leg still passes the destination registry, egress, caller-ID, plan, capacity and pre-dial gates.
Add the third party
Call POST /v1/calls/{call_id}/conference/add with a JSON body:
bash
curl -sS -X POST "$API/v1/calls/$CALL_ID/conference/add" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"destination":"1001"}'The request has two fields:
| Field | Required | Meaning |
|---|---|---|
destination | yes | Internal extension or agent UUID; with external conference enabled, a registered destination UUID or strict-E.164 number. |
consumer_agreed | external only | Boolean attestation that the consumer agreed to the external third party hearing the call. It must be the literal true; a non-boolean value is 400 bad_request. |
For an external destination:
bash
curl -sS -X POST "$API/v1/calls/$CALL_ID/conference/add" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"destination": "+13125550170",
"consumer_agreed": true
}'The 200 response contains a call control result. It is returned only after the audit row is durable and the third-party leg is up.
Only one conference can be open on a call. A conference and an attended-transfer consultation are mutually exclusive, and a transfer cannot start while a conference is open.
Remove the third party
Call POST /v1/calls/{call_id}/conference/remove with no request body:
bash
curl -sS -X POST "$API/v1/calls/$CALL_ID/conference/remove" \
-H "Authorization: Bearer $TOKEN"Removal hangs up only the conference leg. The agent and consumer remain on their original call. If the switch already lost the third-party leg, removal still returns 200 because the requested state has been reached. If no conference is open, the route returns 409 no_conference.
Removal does not run the plan or compliance gates: it takes exposure away and is therefore allowed even when an add would be refused.
Recording behavior
When the original call is being recorded, the third party's voice is mixed into the consumer's existing recording. The conference does not create a separate track or a separate recording for that person, and the third party cannot be separated from the consumer afterwards.
The external-conference attestation records that the consumer agreed to the third party hearing the call. It does not add a spoken recording disclosure. Decide the required notice and consent before enabling external conference for a tenant.
CDR: one row per conference leg
Every third-party leg has its own append-only row in call_attempts. Read it with GET /v1/call_attempts, following next_cursor until it is null when more than one page is possible.
The leg row carries these values in compliance_snapshot:
leg: "conference";conference_kind: "internal"or"external";conference_of: the consumer call UUID that the third party joined.
Use conference_of to correlate the leg with the original call. The leg belongs to no debtor and no agent. An internal leg has no carrier and is not billed. An external leg records its carrier and is billed from its own answer, not from the API acknowledgement. Originals have corrects_id: null; later correction rows point to the original, and the latest correction holds the final disposition.
Errors and fail-closed behavior
Branch on error.code, not on the human-readable message.
| HTTP | Codes and meaning |
|---|---|
400 | bad_request: malformed JSON, invalid field type, blank or oversized destination. |
401 | unauthorized: missing, unknown or revoked bearer. |
402 | plan_inactive: the plan is inactive or its state cannot be read, so the added exposure is refused. |
403 | forbidden: suspended tenant, widget token or caller that does not control the call. |
404 | not_found: conference is disabled, or the call is missing or outside this tenant. |
409 | conference_in_progress, consultation_in_progress, or no_conference. |
422 | consumer_agreement_required, destination_not_found, destination_not_supported, destination_unavailable, caller_id_unavailable, conference_unanswered, or compliance_blocked. A registry refusal uses reason_code: destination_unregistered; a pre-dial compliance refusal includes gate: pre_dial and a decision_id when the engine ran. |
429 | rate_limited; for an external leg, also carrier_cps, tenant_channels or tenant_cps. |
502 | call_control_failed: the switch rejected the control command and the audit row settles as failed. |
503 | audit_unavailable, esl_unavailable; for an external leg, also no_route or capacity_unavailable. |
An add that cannot write its evidence, evaluate a required gate, reserve capacity or establish the third-party leg does not fall back to a less protected path. The existing agent–consumer call continues without a conference.