voepy

Conferences

Host multi-party voice rooms: bridge many call legs into one mixed audio space, dial new people in, mute and hold individuals, broadcast audio or text-to-speech to everyone, record the whole room, and let a supervisor whisper, monitor, or barge in for call-center coaching.

All paths under /v1/. Authorization: Bearer <api-key> required. JSON in, JSON out, snake_case. Send an Idempotency-Key on every POST that creates or dials.

Concepts

A conference is a server-side mixing room. You don't dial a conference number; you create the room on top of an existing answered call (the host leg, "leg A"), then attach more call legs to it. Every attached leg is a participant (also called a member).

  • The host call is an answered Call you already control. Creating the conference puts that leg into the room as the host participant.
  • A participant is one call leg in the room. You add participants two ways: dial a brand-new outbound leg straight into the room (POST …/participants/), or attach a call leg you already have answered (POST …/actions/join/).
  • Each participant carries live state mirrored from the carrier: is_muted, is_on_hold, is_speaking, plus joined_at / left_at.

Lifecycle

init → in_progress → completed
statusMeaning
initRoom row created; the create call has been issued but the carrier hasn't confirmed the room yet.
in_progressRoom is live (confirmed by the conference.started webhook).
completedRoom ended — by actions/end, by the host leaving when end_on_exit is set, or when it hits duration_minutes. Terminal.

Almost every write checks the room is alive first. Acting on a completed room returns 409 conference_already_ended.

The status transitions are driven by webhooks, not by your API call returning — create returns a room in init, and it flips to in_progress once the carrier fires conference.started. See Webhooks.

The Conference resource

{
  "id": "voepy_conf_01hz…",
  "tenant_id": "0c8f…",
  "name": "support-bridge-42",
  "status": "in_progress",
  "host_call_id": "voepy_call_01hz…",
  "beep_enabled": "never",
  "start_on_create": true,
  "end_on_exit": true,
  "max_participants": 10,
  "duration_minutes": 720,
  "started_at": "2026-05-12T14:32:18.000Z",
  "ended_at": null,
  "created_at": "2026-05-12T14:32:17.500Z",
  "participants": [ /* ConferenceParticipant objects */ ]
}
FieldTypeNotes
idvoepy_conf_…Opaque room id.
namestringYour label. Must be unique among your non-completed rooms — once a room ends you can reuse the name.
statusenuminit, in_progress, completed.
host_call_idvoepy_call_…The leg the room was built on. null if that call has since been purged.
beep_enabledenumnever, on_enter, on_exit, always. When members hear an entry/exit beep.
start_on_createboolWhether the room becomes active immediately on create vs. waiting for the second participant.
end_on_exitboolWhether the room tears down when the host leg leaves.
max_participantsint2–250. Adding past this returns 409 conference_full.
duration_minutesint1–2880 (max 48h). Hard cap on room lifetime.
started_at / ended_attimestampnull until the room starts / ends.
participantsarrayEmbedded participant list (see below).

The ConferenceParticipant (member) resource

{
  "id": "voepy_member_01hz…",
  "conference_id": "voepy_conf_01hz…",
  "call_id": "voepy_call_01hz…",
  "role": "participant",
  "joined_at": "2026-05-12T14:32:25.000Z",
  "left_at": null,
  "duration_seconds": 0,
  "is_muted": false,
  "is_on_hold": false,
  "is_speaking": true
}
FieldTypeNotes
idvoepy_member_…Member id — use this for GET / PATCH on a single participant.
conference_idvoepy_conf_…The room.
call_idvoepy_call_…The underlying call leg. Use this id to remove a participant.
roleenumhost or participant.
joined_attimestampnull while the dialled leg is still ringing; stamped on conference.member_joined.
left_attimestampnull while attached; stamped on conference.member_left.
duration_secondsintTime in the room.
is_muted / is_on_hold / is_speakingboolLive state, mirrored from carrier webhooks.

Two different ids identify a member: voepy_member_… (GET / PATCH a participant) and the participant's voepy_call_… (DELETE / remove). This mirrors the underlying call-control model — removal targets the call leg, per-member settings target the member row.

Create a conference

You need an answered (or bridged) call to host the room.

POST /v1/conferences/
Content-Type: application/json
Idempotency-Key: <uuid>

{
  "name": "support-bridge-42",
  "host_call_id": "voepy_call_01hz…",
  "beep_enabled": "on_enter",
  "start_on_create": true,
  "end_on_exit": true,
  "max_participants": 25,
  "duration_minutes": 120
}
FieldTypeDefaultNotes
namestring ≤120requiredUnique among your active rooms.
host_call_idvoepy_call_…requiredMust be one of your calls and currently answered or bridged.
beep_enabledenumnevernever, on_enter, on_exit, always.
start_on_createbooltrueStart the room immediately.
end_on_exitbooltrueTear down when the host leg leaves.
max_participantsint 2–25010Room capacity.
duration_minutesint 1–2880720Max room lifetime.

Response: 201 with the Conference resource at status: "init". The host call becomes the host participant.

Failure modes:

  • 400 invalid_requesthost_call_id isn't a well-formed voepy_call_….
  • 404 not_found — that call doesn't belong to you.
  • 409 call_not_in_required_state — the host call isn't answered or bridged.

List conferences

GET /v1/conferences/
  ?status=in_progress
  &name=support
  &page=1
  &page_size=50
Query paramNotes
statusExact match: init, in_progress, completed.
nameCase-insensitive substring match.
page1-indexed, default 1.
page_sizeDefault 20, max 200.

Response is the standard page envelope:

{
  "count": 37,
  "next": "https://api.voepy.com/v1/conferences/?page=2",
  "previous": null,
  "results": [ /* Conference objects, newest first */ ]
}

Retrieve a conference

GET /v1/conferences/{id}/

Returns the full Conference resource including the embedded participants array. 404 not_found if the id isn't yours.

Update settings

POST /v1/conferences/{id}/actions/update/
{
  "duration_minutes": 240,
  "beep_enabled": "always",
  "end_on_exit": false,
  "max_participants": 50
}

All four fields are optional; send only what you want to change.

FieldTypeNotes
duration_minutesint 1–2880New room lifetime cap.
beep_enabledenumnever, on_enter, on_exit, always.
end_on_exitboolWhether the host leaving ends the room.
max_participantsint 2–250New capacity.

These are wrapper-side preferences applied to subsequent participant actions and reflected on the GET response. The updated Conference resource is returned. 409 conference_already_ended on a finished room.

End a conference

POST /v1/conferences/{id}/actions/end/
{}

Hangs up every leg and moves the room to completed. Idempotent in spirit, but a second call against an already-ended room returns 409 conference_already_ended. The optional region field (see Regions) is accepted.

Manage participants

Add a participant by dialing out

Dials a fresh outbound leg and attaches it to the room the moment it answers.

POST /v1/conferences/{id}/participants/
Content-Type: application/json
Idempotency-Key: <uuid>

{
  "from": "+15125550100",
  "to":   "+15558675309",
  "route_id": "voepy_route_01hz…",
  "timeout_secs": 30,
  "customer_reference": "ticket-991"
}
FieldTypeDefaultNotes
fromstring (E.164)requiredA DID you own (or set caller_id_forwarding).
tostring (E.164)requiredWho to dial in.
route_idvoepy_route_…tenant defaultWhich connection to route on.
timeout_secsint 1–600carrier defaultHow long to ring.
customer_referencestring ≤128Opaque to us; echoed on this leg's webhooks.
caller_id_forwardingboolfalseDial from a number you don't own. Same rules as Calls — requires the per-tenant admin flag.

Response: 201 with the new ConferenceParticipant. Its joined_at is null until the leg answers and the carrier fires conference.member_joined. The dial leg is a normal Call you can inspect at GET /v1/calls/{id}.

Failure modes: 409 conference_full (room at max_participants), 409 conference_already_ended, 422 from_number_not_owned, 422 caller_id_forwarding_not_allowed.

Add an existing call leg

If you already have an answered call (e.g. an inbound caller you want to pull into the bridge), attach it rather than dialing a new leg.

POST /v1/conferences/{id}/actions/join/
{
  "call_id": "voepy_call_01hz…",
  "mute": false,
  "hold": false
}
FieldTypeNotes
call_idvoepy_call_…required. Must be yours and currently answered or bridged.
muteboolJoin muted.
holdboolJoin on hold.

Returns the updated Conference. 409 call_not_in_required_state if the call isn't answered/bridged; 409 conference_full; 404 not_found if the call isn't yours.

List participants

GET /v1/conferences/{id}/participants/list/
  ?muted=true
  &on_hold=false
  &include_left=false
  &page=1
  &page_size=50
Query paramNotes
mutedtrue / false.
on_holdtrue / false.
include_lefttrue to include departed members; default false (active only).
page / page_sizeStandard paging; default page_size 20, max 200.

Filters compose — ?muted=true&on_hold=false returns members muted but not on hold. Returns the standard page envelope of ConferenceParticipant objects, speakers first.

This local list is the authoritative snapshot: mid-call state (is_muted, is_on_hold, is_speaking, joined_at, left_at) is kept live from carrier webhooks. There is no whispering filter here — supervisor role isn't persisted locally; passing ?whispering=… returns 400 invalid_request pointing you at the upstream snapshot.

Upstream snapshot (incident tooling). GET /v1/conferences/{id}/participants/upstream/ bypasses the database and queries the carrier live, annotating each upstream row with the matching voepy_member_… id (or null + "drift": true for rows we don't recognize). It supports muted, on_hold, whispering plus page / page_size (1–100), and is page-based with a meta block rather than the standard envelope. Every call is a carrier round-trip — use it during incidents only; the local list is what you want in normal operation.

Get a single participant

GET /v1/conferences/{id}/participants/{member_id}/

{member_id} is a voepy_member_… id. Returns the ConferenceParticipant. 404 member_not_found if that member isn't on the room.

Update a participant

PATCH /v1/conferences/{id}/participants/{member_id}/
{
  "end_on_exit": true,
  "soft_end_on_exit": false,
  "beep_enabled": "on_exit"
}
FieldTypeNotes
end_on_exitboolThis member leaving ends the whole room.
soft_end_on_exitboolMember leaving ends the room only once no un-held members remain.
beep_enabledenumPer-member beep: never, on_enter, on_exit, always.

All optional. Returns the updated member. 404 member_not_found if the member has no upstream leg yet (still ringing) or isn't on the room.

Remove a participant

Removal targets the participant's call id, not the member id.

DELETE /v1/conferences/{id}/participants/{member_call_id}/

{member_call_id} is the participant's voepy_call_…. The leg is removed from the room (the call itself isn't necessarily hung up). Returns the ConferenceParticipant. 404 member_not_found if that call isn't an active member.

Per-participant mute and hold

Mute, unmute, hold, and unhold target one or more participants by their leg ids. Each takes a non-empty leg_ids array (1–64 voepy_leg_… ids). All return the updated Conference.

POST /v1/conferences/{id}/actions/mute/
{ "leg_ids": ["voepy_leg_01hz…", "voepy_leg_02hz…"] }
POST /v1/conferences/{id}/actions/unmute/
{ "leg_ids": ["voepy_leg_01hz…"] }
POST /v1/conferences/{id}/actions/hold/
{
  "leg_ids": ["voepy_leg_01hz…"],
  "audio_url": "https://cdn.example.com/hold-music.mp3"
}
POST /v1/conferences/{id}/actions/unhold/
{ "leg_ids": ["voepy_leg_01hz…"] }
VerbExtra fields
mute / unmuteleg_ids only.
holdleg_ids, optional audio_url (hold music played to the held legs).
unholdleg_ids only.

A leg that isn't an active member of the room returns 404 member_not_found. The resulting is_muted / is_on_hold state flows back via webhooks and is reflected on the next participant list.

In-conference media

Play audio

Stream an audio file into the room (or to specific legs).

POST /v1/conferences/{id}/actions/play/
{
  "url": "https://cdn.example.com/announcement.mp3",
  "loop": 1,
  "leg_ids": ["voepy_leg_01hz…"]
}
FieldTypeNotes
urlURLrequired. The audio file to play.
loopint 1–100Repeat count.
leg_idsarray of voepy_leg_…Omit to play to the whole room; provide to target specific members.

Speak (text-to-speech)

POST /v1/conferences/{id}/actions/speak/
{
  "payload": "The meeting will end in five minutes.",
  "voice": "Polly.Joanna",
  "language": "en-US",
  "payload_type": "text"
}
FieldTypeNotes
payloadstring ≤2000required. Text (or SSML) to speak.
voicestringrequired. TTS voice id, e.g. Polly.Joanna.
languagestringBCP-47 tag, e.g. en-US.
payload_typeenumtext (default) or ssml.
leg_idsarray of voepy_leg_…Omit for the whole room; provide to target members.

Stop audio / speak

POST /v1/conferences/{id}/actions/stop/
{ "leg_ids": ["voepy_leg_01hz…"] }

Stops in-progress playback and speech. Omit leg_ids to stop for the whole room; provide it to stop only on specific legs. Returns the Conference.

Recording

Conference recording captures the mixed room audio. Start, stop, pause, and resume independently of any per-leg call recording.

POST /v1/conferences/{id}/actions/record_start/
{
  "format": "mp3",
  "channels": "single",
  "play_beep": true
}
FieldTypeDefaultNotes
formatenumrequiredmp3 or wav.
channelsenumsinglesingle (mixed) or dual.
play_beepboolAudible record-start beep.
POST /v1/conferences/{id}/actions/record_stop/    { }
POST /v1/conferences/{id}/actions/record_pause/   { }
POST /v1/conferences/{id}/actions/record_resume/  { }

All four return the Conference. When the recording is ready you'll get a recording.completed webhook; the resulting Recording carries the conference, and its call_id resolves to the host call. Fetch and manage the file through the recordings API — see Recordings.

Supervise (whisper / monitor / barge)

Attach a supervisor to the room and set their relationship to the rest of the participants — the standard call-center coaching primitives.

POST /v1/conferences/{id}/actions/supervise/
{
  "leg_id": "voepy_leg_supervisor…",
  "role": "whisper",
  "whisper_leg_ids": ["voepy_leg_agent…"]
}
FieldTypeNotes
leg_idvoepy_leg_…required. The supervisor's leg. Must be an active member.
roleenumrequired. monitor, whisper, barge, or none.
whisper_leg_idsarray of voepy_leg_…Required when role is whisper — the legs the supervisor can be heard by. Ignored for other roles.
RoleWhat the supervisor can do
monitorListen only. No one in the room hears the supervisor.
whisperSpeak privately to the legs in whisper_leg_ids (coaching) while hearing the whole room; other members don't hear the supervisor.
bargeFull two-way — the supervisor hears and is heard by everyone.
noneClear the supervisor role; revert to a normal participant.

Returns the Conference. Omitting whisper_leg_ids with role: "whisper" is a 400 invalid_request.

Regions

Hold, mute, play, speak, record, join, supervise, and end all accept an optional region field. Valid values: Australia, Europe, Middle East, US. Leave it off unless you have a specific routing reason to pin a region.

Events {#events}

Conference activity is delivered through your webhook subscriptions using wrapper-public event names:

EventFires whenKey payload fields
conference.startedRoom goes liveconference_id, name, host_call_id
conference.endedRoom completesconference_id, ended_at, duration_seconds
conference.member_joinedA leg attachesconference_id, member_id, call_id, role, joined_at
conference.member_leftA leg detachesconference_id, member_id, call_id, role, left_at
conference.member_mutedA member is mutedconference_id, member_id, call_id
conference.member_unmutedA member is unmutedconference_id, member_id, call_id
conference.member_speaking_startedA member starts talkingconference_id, member_id, call_id
conference.member_speaking_endedA member stops talkingconference_id, member_id, call_id
recording.completedA conference recording is readyrecording fields (see Recordings)

Subscribe to these (or ["*"]) the same way as call events. See Webhooks for subscription setup, signature verification, and retry behavior.

Errors specific to conferences

HTTPCodeWhen
400invalid_requestMalformed body, bad id format, or an unsupported filter (e.g. whispering on the local list).
404not_foundConference or host/join call doesn't belong to you.
404member_not_foundThe targeted member, leg, or call isn't an active participant.
409conference_fullRoom is at max_participants.
409conference_already_endedAction attempted on a completed room.
409call_not_in_required_stateHost/join call isn't answered or bridged.
422from_number_not_ownedDial-in from isn't a DID you own and forwarding isn't enabled.
422caller_id_forwarding_not_allowedcaller_id_forwarding: true without the admin opt-in.
502upstream_errorCarrier-side failure. Often retriable; check the message.

Every error uses the standard envelope:

{ "error": { "code": "conference_full", "message": "…" } }

Full reference: Error reference.

Next

  • Calls — place and answer the legs you bridge into a room.
  • Recordings — fetch and manage conference recordings.
  • Webhooks — subscribe to the conference.* events that drive your room logic.
  • Error reference — every code the API can return.