Voice conferences: create, manage, and end conference bridges
A conference bridge is a multi-party voice room: one named room that accepts several concurrent call legs and mixes their audio so everyone hears everyone. Use it for scheduled team calls, supervisor-led assistance desks, outbound dial-out bridges where the host invites several guests, or any flow where a 1:1 call is not enough. Every conference leg — the human participants you seed at creation and the ones you invite afterward — exits over the Devotel softswitch. You never configure a third-party outbound trunk to run a conference. Base path:/api/v1/voice
Authentication: Clerk session or API key with voice:read for listing and
voice:write for every mutation.
The console surface for this lifecycle is the dashboard’s
Voice → Conferences page (“create and manage voice conference bridges”),
where the room PIN, participant roster, per-leg status, and recording controls
are all visible without writing code. Everything below is the API surface that
page is built on.
The lifecycle at a glance
A conference moves through a fixed lifecycle:- Create the room with
POST /voice/conferences— optionally seeding dial-out participants in the same request. - Join and leave — each dialed participant lands on the room’s
participant roster with a per-leg status (
ringing,connected,no_answer,failed, …). - Manage while the room is live — invite more participants, update the recording mode, mute/hold/kick legs, or add an AI agent.
- End the room with
POST /voice/conferences/:id/end, or let it close automatically when the last leg hangs up or the expiry backstop hits.
1. What a conference bridge is
A conference is two resources working together:- the conference row — the room itself, with a name, a status
(
pending,in-progress,completed,failed,cancelled), a join PIN, an optional host PIN, a recording mode, a participant cap, and a maximum lifetime; and - the participant rows — one per dial-out leg, each with its own status, SIP response code, failure reason, join/leave timestamps, and (for GDPR) recording-consent state.
PIN protection by default
When you omit thepin field at creation, the platform auto-generates a
random 6-digit PIN, so every conference is PIN-protected by default. You can
supply your own 4–8 digit numeric pin, plus an optional host_pin reserved
for future host-only DTMF controls. The create response echoes the PIN so the
operator can distribute it, and POST /voice/conferences/:id/regenerate-pin
rotates the PIN mid-call without ending the room.
PIN visibility is restricted: only the conference creator and users with the
owner or admin role read the raw PIN on list and detail responses. Every other
caller in your tenant sees the masked sentinel ***.
2. Create a conference
POST /api/v1/voice/conferences creates the room and optionally seeds
dial-out participants in the same request. The create handler delegates to the
conference service, which persists the room, dials the seed legs in parallel,
and records each leg’s outcome.
cURL
The response is
201 Created with the conference row, including its conf_<32 hex> id and the active pin. Passing an Idempotency-Key header (or an
idempotency_key body field) makes create retries safe: within a five-minute
window, a duplicate key short-circuits to the original room instead of
spawning a second one.
Seed-leg failures are isolated, not fatal to the room: a leg that fails lands
on the roster as failed with its error code and SIP response code, and the
conference still exists for other legs. If every seed leg fails, the room is
kept for forensics at status failed — you receive no bridge, and the
troubleshooting page covers the error codes that explain why.
Invite participants one by one
POST /api/v1/voice/conferences/:id/participants adds a single participant to
a live room. The target is a discriminated pair:
cURL
kind: "pstn" with an E.164 number, or kind: "sip" with a sip: /
sips: URI. When the room carries a PIN, pass it in the pin field so the
invited leg can join.
3. End a conference
POST /api/v1/voice/conferences/:id/end terminates the room, dropping every
live leg:
cURL
end_reason (1–120 characters) is captured on the audit row. The
response is { "conference_id": "…", "status": "ended" }. Ending the same
conference twice is safe — the terminal-state guard keeps the second request a
no-op rather than an error.
Rooms also end on their own when the last participant leaves, and a four-hour
expiry backstop closes any room that never torn down. Use the explicit end
call when you want the room closed on your schedule.
4. Update a conference
PATCH /api/v1/voice/conferences/:id updates the mutable fields on an
existing conference. Today that is the recording mode:
cURL
record_mode accepts manual, auto_with_consent, or disabled. In
auto_with_consent mode the platform collects each participant’s join-time
recording consent, and POST /voice/conferences/:id/participants/:participantId/consent with
{ "state": "granted" } or { "state": "declined" } records the outcome per
leg. The patch response returns the updated conference row.
5. Read conferences and participants
GET /api/v1/voice/conferences lists your tenant’s conferences newest-first,
paginated by start time with an opaque cursor:
cURL
status— a comma-separated filter validated against the closed status enum; an unknown value returns 422. Unfiltered lists hide never-bridged dial-all-failed rooms (they remain available via an explicit?status=failedrequest).limit— 1–100, default 50.cursor— carry the response’snext_cursorback in to fetch the next page.
total_participants count so a list view does not need a
per-room follow-up.
GET /api/v1/voice/conferences/:id returns one conference with its
participant legs embedded under participants (handy for the detail page in a
single round-trip). For rooms where the roster grows, or when you only want
the legs, use the dedicated paginated endpoint
GET /api/v1/voice/conferences/:id/participants?limit=100&cursor=… — it
streams the participant list independently (limit up to 200).
Two rollups feed monitoring surfaces: GET /api/v1/voice/conferences/aggregate?window_days=30 returns the KPI strip
(total, active now, average duration, success rate, peak concurrent
participants, participants in window), and GET /api/v1/voice/conferences/failure-analytics?window_days=30 returns failure
rates with the top failure reasons and affected destinations. Both accept a
window_days capped at 365.
6. Lifecycle webhooks
Subscribe to these event types on your webhook endpoint to follow a room without polling:conference.ended is deduplicated across both closing paths — when the last
leg leaves and an explicit end land close together, your endpoint still
receives exactly one event. Map the events to symptoms in
Troubleshooting: conference lifecycle failures.
7. AI-agent participation
An AI agent can join a live conference as a participant — useful for post-meeting summaries, silent note-taking, or an agent that assists the room on request.cURL
joining, active, left, error). Agent joins
are gated to owner/admin/developer roles, like the other conference control
mutations.
Tenant-owned controls and ownership verification
Every participant-targeting mutation — kick, mute, unmute, hold, unhold, per- participant consent — verifies that the conference and participant pair you passed actually belongs to a conference owned by your tenant before the platform forwards the control action. A leaked or guessed conference or call identifier from another tenant cannot mute, kick, or join legs on your rooms, and the verification is scoped per tenant, not per organization-wide flag. Consents, PIN rotation, and end reasons are likewise per-tenant controls, keeping the room’s behavior inside your tenant’s configured compliance posture (GDPR recording consent, quiet-hours rules, and DNC checks all still apply to the dial-out legs).Limits and limits-less behavior
- A conference holds at most 32 participants; the create-time
maxParticipantscap is 32 and defaults to 8 when omitted. - Seed participants are bounded to 32 per create request; invite further legs one by one.
- List endpoints paginate with a cursor; single-conference detail embeds the first participants inline and the dedicated participants endpoint streams the rest.
- A room has a four-hour expiry backstop: a conference left running past
its expiry is closed regardless, and the
conference.endedwebhook carriesend_reason: "timeout"for that path. - The KPI rollups accept a
window_daysup to 365.
Related
- Troubleshooting: conference lifecycle failures — map a misbehaving room to the lifecycle step that broke.
- AI agent handback to human — the adjacent handoff pattern when an AI leg should hand back to a human participant.
- Recording library — where conference recordings land and how signed playback URLs behave.