Skip to main content

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:
  1. Create the room with POST /voice/conferences — optionally seeding dial-out participants in the same request.
  2. Join and leave — each dialed participant lands on the room’s participant roster with a per-leg status (ringing, connected, no_answer, failed, …).
  3. Manage while the room is live — invite more participants, update the recording mode, mute/hold/kick legs, or add an AI agent.
  4. 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.
Each step emits audit rows and lifecycle webhooks, so you can follow the room end-to-end. For diagnosing rooms that misbehave, see Troubleshooting: conference lifecycle failures.

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.
The creator’s own inbound leg (for example, an operator dialing in from a PSTN handset) attaches directly to the room and is not counted as a participant row — participant rows represent the legs the platform dialed out to reach.

PIN protection by default

When you omit the pin 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
Use 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
The optional 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=failed request).
  • limit — 1–100, default 50.
  • cursor — carry the response’s next_cursor back in to fetch the next page.
Each row embeds a 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
The join request returns the agent’s row id and call SID; the list endpoint shows each agent’s status (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 maxParticipants cap 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.ended webhook carries end_reason: "timeout" for that path.
  • The KPI rollups accept a window_days up to 365.
No participant cap applies to historical conference reads — a room’s roster is fully retrievable after it ends, so post-hoc dispute or compliance review can revisit who joined, who left, and which legs failed even after the four-hour backstop closed the live room.