Voice API
Build voice applications with Orbit’s Voice API. Make and receive calls, connect AI agents, configure SIP trunks, build IVR menus, host conference calls, and manage voicemail — all through a unified API. Base path:/api/v1/voice
Calls
Initiate a Call
POST /api/v1/voice/calls
Start an outbound call. Route it to an AI voice agent, an IVR flow, or a raw SIP destination.
string
required
Destination phone number in E.164 format
string
Caller ID — must be a number registered in your Orbit account. If omitted, the server falls back to your tenant’s default outbound number.
string
default:"outbound"
Call direction:
outbound or inbound.string
AI voice agent to handle the call. The agent must exist in your tenant and not be archived (omit for raw SIP or an Application-bound IVR).
boolean
default:"false"
Whether to record the call
boolean
default:"false"
Request carrier-side Answering Machine Detection for the outbound call.
string
Campaign ID for spend-cap tracking. Recorded on the call log for attribution.
string
IANA timezone (e.g.
America/New_York) used for the TCPA quiet-hours check on outbound calls.object
Arbitrary key-value pairs attached to the call
string
HTTPS URL for programmable call-control. When the call is answered, Orbit fetches this URL and expects a JSON verb sequence — either
{ "verbs": [...] } or a bare array — describing the flow (say, gather, dial, and so on). Must use the https:// scheme. Mutually exclusive with staticVerbs.string
default:"POST"
HTTP method used to fetch
answer_url: POST or GET.string
default:"safe-default"
What the call falls back to when the
answer_url fetch fails (timeout, non-2xx, or malformed body): safe-default, voicemail, or decline.array
Inline call-control verb sequence served without a remote fetch. Each entry is an object with a
verb field plus its verb-specific params (e.g. { "verb": "say", "text": "Hello" }). Up to 50 verbs. Mutually exclusive with answer_url.For per-call programmable control, use
answer_url or staticVerbs above — supply at most one of them. There are no webhook_url, ivr_id, or max_duration fields; bind IVR menus and TTS prompts to the Application or IVR Flow on the number instead. Any field not listed above is ignored by the server.List Calls
GET /api/v1/voice/calls
Retrieve call logs with cursor-based pagination and optional filters.
string
Cursor for pagination
integer
default:"25"
Results per page (max 200)
string
Filter by call status. Free-text (up to 40 characters) so carrier-reported
values pass through unchanged; an unrecognized value simply matches its own
rows (or none) rather than returning an error. Repeat the parameter to filter
by several statuses at once (e.g.
?status=completed&status=busy).Canonical lifecycle values in use: queued, initiated, ringing,
in-progress, active, answered, completed, busy, no-answer,
failed, canceled, cancelled, missed, transferred, hangup_failed.string
Filter by caller number
string
Filter by destination number
Get Call Details
GET /api/v1/voice/calls/{id}
Retrieve full details of a call including status, duration, recording URL, and agent interaction summary.
Hang Up a Call
POST /api/v1/voice/calls/{id}/hangup
Terminate an active call. AI agents receive a graceful shutdown signal before disconnection.
Accept / Decline an Inbound Call
POST /api/v1/voice/calls/{id}/accept
Accept an incoming call before it auto-rings the configured number-handler. Use when the softphone or programmatic handler wants to answer in lieu of the default routing.
POST /api/v1/voice/calls/{id}/decline
Reject an incoming call. The carrier receives 486 Busy Here.
Mute / Unmute a Call Leg
POST /api/v1/voice/calls/{id}/mute
POST /api/v1/voice/calls/{id}/unmute
Mute or unmute the operator side of a connected call. Mute is per-leg — the participant on the other side keeps speaking and their audio still records.
Hold / Unhold
POST /api/v1/voice/calls/{id}/hold
POST /api/v1/voice/calls/{id}/unhold
Place the remote party on hold (carrier-side music or silence depending on the trunk’s hold_music configuration). Unhold restores audio in both directions.
Send DTMF Tones
POST /api/v1/voice/calls/{id}/dtmf
string
required
String of DTMF digits to send (
0–9, *, #, A–D), up to 32 characters. Each digit plays for duration_ms followed by a gap_ms silence — by default 200ms tone / 50ms gap.integer
default:"200"
Per-digit tone duration in milliseconds. Range
50–1000.integer
default:"50"
Inter-digit gap in milliseconds. Range
50–500.Transfer (Blind)
POST /api/v1/voice/calls/{id}/transfer
Cold-transfer the call to a new destination. Hangs up the original leg the moment the new leg connects.
string
required
Destination phone number, agent ID (
agt_*), or extension (ext_*).Warm Transfer
POST /api/v1/voice/calls/{id}/warm-transfer
Start a warm transfer — the calling agent stays connected while a third leg is dialled. The original caller is placed on hold; once the third party answers, the agent has a private “whisper” window with them before completing the handoff.
string
E.164 destination for the warm-transfer consult leg (e.g.
+14155557890). Mutually exclusive with queueId — supply exactly one.string
Queue to transfer into instead of a direct number (attended-transfer-to-queue). Mutually exclusive with
destination — supply exactly one. Request is rejected when both or neither are present.boolean
default:"false"
When
true, speak a context sentence to the consult leg on answer before audio bridges.string
Custom whisper text to speak to the consult leg (max 220 characters), e.g. “Customer Jane Doe, escalation tier 2”. When omitted and
contextWhisper is true, a context sentence is generated automatically.string
Caller ID (E.164) to present on the consult leg. Must be a number your organization owns — a number you do not own is rejected. When omitted, the platform default caller ID is used.
boolean
default:"true"
When
true (the default), the agent gets a private consult window with the third party before the handoff can be completed. Set to false to bridge the caller in as soon as the consult leg answers.string
Optional reason for the transfer, recorded on the call’s transfer history for transfer-rate analytics. One of
wrong-queue, escalation, language-mismatch, expertise, or other.integer
Queue transfers only (used with
queueId). Routing priority from 0 to 99 for the agent match — lower wins. Ignored when transferring to a destination.string[]
Queue transfers only (used with
queueId). Skill tags (up to 20) the matched agent must have — only agents with every listed skill are eligible. Ignored when transferring to a destination.POST /api/v1/voice/calls/{id}/warm-transfer/complete
Bridge the original caller into the consult leg and disconnect the original agent. The two non-agent legs are now connected.
POST /api/v1/voice/calls/{id}/warm-transfer/cancel
Abort the warm transfer and return the original caller to the agent. The consult leg is hung up.
Recording
Recordings are configured per-call viarecord: true on POST /api/v1/voice/calls, or started/stopped mid-call via the endpoints below. Storage is tenant-isolated; URLs expire after 1 hour and require re-fetching via the call detail endpoint.
Start / Stop Mid-Call Recording
POST /api/v1/voice/calls/{id}/recording/start
Begin recording an in-progress call. Idempotent — calling on an already-recording call returns the existing recording id. This endpoint takes no body parameters; channel layout and silence trimming are determined by the call’s recording configuration.
POST /api/v1/voice/calls/{id}/recording/stop
Stop the in-progress recording and finalise the file. Subsequent reads of GET /api/v1/voice/calls/{id}/recording will return the fully-encoded URL.
Retrieve a Call Recording
GET /api/v1/voice/calls/{id}/recording
Returns the recording metadata + a 1-hour-expiring signed URL. If the call is still being recorded, status is in_progress and signed_url is null.
Conference Recording
POST /api/v1/voice/conferences/{id}/recording/start
POST /api/v1/voice/conferences/{id}/recording/stop
Same semantics as call recording but for conference rooms — captures the mixed audio of every participant.
SIP Trunks
Bring your own carrier by connecting SIP trunks to Orbit. Orbit acts as an SBC (Session Border Controller) with TLS and SRTP encryption.Create SIP Trunk
POST /api/v1/voice/sip-trunks
string
required
A descriptive name for the trunk (1-120 characters)
string
required
Hostname or IP address of the SIP trunk endpoint (1-253 characters)
number
default:"5060"
SIP port (1-65535). Defaults to 5060 if omitted.
string
default:"udp"
Transport protocol:
udp, tcp, or tls. Defaults to udp if omitted.object
Optional SIP authentication credentials
string[]
Preferred audio codecs in priority order (max 20 codecs, each ≤32 characters). When omitted, no codec preference is stored and the carrier’s negotiated default applies.
boolean
default:"true"
Whether the trunk is enabled for outbound calling. Defaults to
true if omitted.number
Maximum concurrent calls on this trunk (1-2000)
List SIP Trunks
GET /api/v1/voice/sip-trunks
Get SIP Trunk
GET /api/v1/voice/sip-trunks/{id}
Update SIP Trunk
PUT /api/v1/voice/sip-trunks/{id}
Delete SIP Trunk
DELETE /api/v1/voice/sip-trunks/{id}
IVR (Interactive Voice Response)
Build programmable voice menus that route callers through options, collect DTMF input, and transfer to agents or queues.Create IVR Flow
POST /api/v1/voice/ivr-flows
The IVR flow body is a graph definition, not a flat list of greeting/menu
fields. The greeting, per-option prompts, timeouts, and fallbacks are all
expressed as nodes and edges inside the required
definition object. The API
ignores any top-level field other than name, definition, and active.string
required
IVR flow name (1–200 characters).
object
required
The IVR flow graph, expressed as an XYFlow-style
{ nodes, edges } object (the same shape the dashboard IVR builder canvas
produces). It is structurally validated before persistence — an invalid graph
is rejected with 422 IVR_FLOW_GRAPH_INVALID and a list of node/edge errors.Validation rules: exactly one entry node (type ivrStart, or any node with
data.isEntry = true); at least one exit node (end, hangup, transfer,
voicemail, ringGroup, dialByName, or offerCallback); every edge must
reference existing nodes; every node must be reachable from the entry; and
every reachable node must have a path to an exit (a loop with no escape is
rejected to avoid burning carrier minutes). Limits: 1000 nodes, 2000 edges.boolean
default:"true"
Whether the flow is active.
List IVR Flows
GET /api/v1/voice/ivr-flows
Update IVR Flow
PUT /api/v1/voice/ivr-flows/{id}
Delete IVR Flow
DELETE /api/v1/voice/ivr-flows/{id}
Conferences
Host multi-party conference calls with moderation controls, recording, and real-time participant management.List Conferences
GET /api/v1/voice/conferences
Lists conferences for your tenant, paginated by started_at descending.
string
Comma-separated status filter —
pending, in-progress, completed, failed, cancelled.integer
default:"50"
Page size (max 100).
string
Opaque pagination cursor returned in the previous response’s
meta.cursor.Get Conference
GET /api/v1/voice/conferences/{id}
Returns a single conference with its participants embedded under participants. For rooms with more than 32 legs, paginate via GET /api/v1/voice/conferences/{id}/participants.
Get Aggregate Metrics
GET /api/v1/voice/conferences/aggregate
Returns server-side rollup KPIs over a rolling window (default 30 days). Powers the dashboard’s conference stat strip — avg duration, success rate, peak concurrent participants. Pass window_days to widen or narrow the rollup (max 365).
integer
default:"30"
Window in days the rollup spans (1-365).
Create Conference
POST /api/v1/voice/conferences
string
required
Conference name (max 200 chars).
string[]
required
Seed participants to dial when the conference is created. Each entry must be E.164. 0-32 entries (pass an empty array to create an empty conference).
string
E.164 caller-id used for each seed-participant dial. Must be a voice-capable number owned by your organization.
boolean
default:"false"
Record every participant leg on creation. Subject to per-jurisdiction recording consent gates.
integer
default:"8"
Cap on simultaneously-bridged legs (max 32).
Add Participant
POST /api/v1/voice/conferences/{id}/participants
string
required
E.164 phone number to dial into the conference (e.g.
+14155552671)string
Optional caller ID to present on the dialed leg. Defaults to the conference’s configured outbound number when omitted.
Remove Participant
DELETE /api/v1/voice/conferences/{id}/participants/{participantId}
End Conference
POST /api/v1/voice/conferences/{id}/end
Call Forwarding (Find-me / Follow-me)
Per-user call-forwarding rules — where a user’s inbound calls ring. Each user has at most one rule covering the handlingmode, an ordered list of destinations, a per-leg ring timeout, an optional active window, and an anonymous-call rejection toggle. Any authenticated user manages their own rule; org owners/admins can also manage rules on behalf of other users in the organization for onboarding and offboarding.
mode is one of:
PSTN forwarding destinations are configuration only. The outbound leg that dials a forwarded
pstn number is placed over the Devotel wholesale softswitch — the same termination path as every other outbound call.Get My Rule
GET /api/v1/voice/call-forwarding
Returns the authenticated user’s own rule. When the user has never configured forwarding, a synthetic none rule is returned (this endpoint never 404s).
Upsert My Rule
PUT /api/v1/voice/call-forwarding
Creates or replaces the authenticated user’s rule (idempotent upsert keyed on the user).
string
required
One of
none, forward_all, sim_ring, sequential, forward_after_timeout.object[]
Ordered forwarding targets (max 5). Each entry is
{ kind, value, label? }. kind is pstn (the value must be an E.164 number, for example +15551234567) or sip (the value must be a sip:-prefixed URI). A malformed destination returns 400. Defaults to an empty array.integer
Seconds to ring before the rule falls through to the next leg or voicemail (5–120). Defaults to 20.
string | null
ISO-8601 timestamp the rule starts applying.
null or omitted means always-on.string | null
ISO-8601 timestamp the rule stops applying.
null or omitted means always-on.string | null
Optional free-text note (max 280 characters).
boolean
When
true, inbound legs with a withheld/anonymous caller-id are declined before any fan-out. Defaults to false.Clear My Rule
DELETE /api/v1/voice/call-forwarding
Reverts the authenticated user’s rule back to none in one shot — clears destinations, active window, note, and anonymous-call rejection. Returns 204 No Content.
List Org Rules (admin)
GET /api/v1/voice/call-forwarding/users
Org owner/admin only. Returns every configured per-user rule in the organization, each annotated with the user’s name, email, and last-updated timestamp.
GET /api/v1/voice/call-forwarding/users/{userId}
PUT /api/v1/voice/call-forwarding/users/{userId}
DELETE /api/v1/voice/call-forwarding/users/{userId}
Org owner/admin only. Read, upsert (same body as the self-service PUT above), or clear the rule for another user in your organization. The target user must belong to your organization, otherwise the request returns 404.
Voicemail
Manage voicemail boxes for receiving and transcribing voice messages when calls go unanswered.Create Voicemail Box
POST /api/v1/voice/voicemail-boxes
Creates a department voicemail box scoped to your organization. A box is a shared mailbox (for example sales or support): voicemails captured against a box-bound number are visible to every member you list, and to no one else.
string
required
Slug-shaped identifier, unique within your organization. Lowercase
a-z, digits, hyphen or underscore; the first character must be a letter or digit. Max 64 characters (for example sales, support-eu).string
required
Human-readable display label shown in the dashboard (1–120 characters).
string[]
User ids subscribed to this box (max 50). Every id must belong to your organization, otherwise the request returns 422. Omit to create an empty roster you can populate later with
PUT.integer | null
Voicemail retention window in days (1–2555). Set to
null or omit to inherit your organization’s default retention.List Voicemail Messages
GET /api/v1/voice/voicemails
Retrieve voicemail messages across the org with transcriptions, with cursor pagination and filters by direction, status, and unread state.
GET /api/v1/voice/voicemails/{id}
Get a single voicemail message including transcription and recording URL.
PATCH /api/v1/voice/voicemails/{id}/read
Mark a voicemail as read.
DELETE /api/v1/voice/voicemails/{id}
Permanently delete a voicemail message.
Conference Operations (advanced)
BeyondAdd Participant / Remove Participant, the conference API exposes a complete moderation surface.
Mute / Unmute a Participant
POST /api/v1/voice/conferences/{id}/participants/{participantId}/mute
POST /api/v1/voice/conferences/{id}/participants/{participantId}/unmute
Mute / Unmute Everyone
POST /api/v1/voice/conferences/{id}/mute-all
POST /api/v1/voice/conferences/{id}/unmute-all
Useful for “all-hands” conferences where the moderator wants to control the floor. Already-muted participants keep their state.
Hold / Unhold a Participant
POST /api/v1/voice/conferences/{id}/participants/{participantId}/hold
POST /api/v1/voice/conferences/{id}/participants/{participantId}/unhold
Lock / Unlock Conference
POST /api/v1/voice/conferences/{id}/lock
POST /api/v1/voice/conferences/{id}/unlock
When locked, no new participants can join — existing participants stay connected. Use to seal a conference for a confidential discussion.
End / Delete a Conference
POST /api/v1/voice/conferences/{id}/end
End the active conference and disconnect every participant gracefully. The conference row is retained for analytics.
DELETE /api/v1/voice/conferences/{id}
Hard-delete the conference row (only allowed when status is ended).
Call Queues
Queue inbound calls by skill / priority and route them to the next available agent. Pairs with the AI agent layer for hybrid human + AI front-desk experiences.Create a Queue
POST /api/v1/voice/queues
string
required
Queue name (visible to supervisors in the wallboard).
string
default:"longest-idle"
Strategy used to pick an agent when several match a queued call. One of:
longest-idle (oldest idle agent wins — default), least-busy (lowest
occupancy), skill-weighted (highest weighted-proficiency score),
round-robin (true rotation by last-dispatched), fixed-order (linear hunt
by agent dispatch order), percent-allocation (weighted random by per-agent
allocation percentage), last-agent-preferred (route back to the agent who
last handled this caller, falling back to longest-idle).integer
default:"300"
Maximum hold time (seconds, 30–3600) before the call falls through to
overflowAction.integer
Per-queue maximum hold-time ceiling (seconds, 60–3600). When a caller waits
on hold longer than this, the queue raises a supervisor hold-time alert and
starts the overflow-escalation path. Omitted leaves the ceiling unset (no
hold-time escalation — the default).
string
default:"voicemail"
Action when max-wait expires. One of:
voicemail, callback, hangup,
overflow_queue. When set to overflow_queue, overflowQueueId is required.string
Target queue id (same tenant) calls overflow into. Required when
overflowAction is overflow_queue; must be omitted otherwise.string
Public URL of an MP3 played while waiting.
List / Update / Delete
GET /api/v1/voice/queues
PUT /api/v1/voice/queues/{id}
DELETE /api/v1/voice/queues/{id}
Enqueue a Call
POST /api/v1/voice/queues/{id}/enqueue
string
required
Call control ID of the live call to add to the queue.
string
required
Caller’s number in E.164 format.
integer
default:"10"
Routing priority from
0 to 99. Lower wins — 0 is the highest
priority and routes ahead of everyone else. Use for VIP customers and
escalations.string[]
Skill tags the answering agent must have. Only agents with every listed
skill are eligible to take the call. Defaults to no skill requirement.
Queue Stats
GET /api/v1/voice/queues/{id}/stats
Returns live counts: waiting, connected, abandoned_24h, avg_wait_seconds_24h, plus per-agent presence.
Set Agent Status
POST /api/v1/voice/agents/{id}/status
string
required
Agent presence:
available, busy, away, offline.string
Required when
status is away (for example lunch, break, training).
Lowercase ASCII letters, digits, _ or -, up to 64 characters.available agents are eligible for the next call.
Ring Groups
A ring group rings multiple destinations (SIP usernames, PSTN numbers, or other ring groups) on a single inbound number. Use for “ring all sales reps until someone answers” patterns, plus hunt-group walks and nested team-of-teams structures.Create / List / Get / Delete
POST /api/v1/voice/ring-groups
string
required
Unique ring group name within the organization (max 100 chars).
string
default:"simultaneous"
Ring strategy. Implemented today:
simultaneous, sequential,
round_robin, longest_idle, fewest_calls. See the Strategies
section below for how each one selects members.object[]
required
Array of
{ kind, value } entries. kind is one of
"sip_username" (references a row in tenant_sip_credentials),
"pstn" (an E.164-ish digits-only number, ^\+?[0-9]{7,20}$), or
"ring_group" (id of another ring group in the same org —
cycle-checked at save time). Min 1, max 100 members per group.integer
default:"30"
Per-step ring timeout in seconds (5-300). For
simultaneous it’s
the overall ring duration before the actionHook fires; for
sequential it’s the per-username ring duration.GET /api/v1/voice/ring-groups
GET /api/v1/voice/ring-groups/{id}
DELETE /api/v1/voice/ring-groups/{id}
Strategies
simultaneous— fork the INVITE to every leaf in parallel; first answer wins. PSTN leaves are dialed in parallel with SIP usernames.sequential— walk SIP usernames in array order, advancing on no-answer via/jambonz/lookup-next. PSTN leaves are appended after the SIP walk so “try Alice’s desk, then Bob’s desk, then ring my mobile” works without a parallel fork.round_robin— Redis-pinned cursor at{devotel}:ringgroup:rr:<groupId>rotates picks across members one at a time. One target per call so per-call billing aligns with the answerOnBridge contract.longest_idle— Redis hash at{devotel}:ringgroup:li:<groupId>records per-member last-selected timestamp. Picker chooses the longest-idle member; never-selected members beat any timestamped member; alphabetical tie-break gives deterministic convergence across replicas.fewest_calls— Redis hash at{devotel}:ringgroup:fc:<groupId>tracks per-member calls handled. Picker chooses the member with the fewest handled calls; never-selected members count as zero and beat any member with a positive count; alphabetical tie-break gives deterministic convergence across replicas. The count is incremented at dial-time when thedialverb is emitted to the picked member. One target per call so per-call billing aligns with the answerOnBridge contract.
PUT /api/v1/numbers/{phoneNumber}/routing with type: "ring_group", config: {}, and ring_group_id: "<group_id>" — the group identity lives on the route row, not in config.
Paging Groups
A paging group broadcasts a one-way (or two-way) announcement to a set of deskphones and softphone users at once — the “page the warehouse” / overhead-intercom pattern that replaces a legacy PBX paging system. Members are resolved at page-time into two delivery channels: registered SIP deskphones (rung directly via an internal user-to-user INVITE) and in-app softphone users.Paging legs are internal
user: targets on Orbit’s own SIP edge — they never traverse a carrier trunk or reach the PSTN.Create / List / Get / Update / Delete
POST /api/v1/voice/paging-groups
string
required
Unique paging group name within the organization (max 100 chars). A duplicate name returns
409.string
Optional human-readable description (max 500 chars).
string
default:"simplex"
Page mode. One of
simplex (one-way announcement) or duplex (two-way intercom). Until the live two-way conference bridge lands, a duplex page still rings every deskphone and plays the announcement (a functional one-way notification), so no member is dropped.object[]
default:"[]"
Array of
{ kind, value } entries. kind is one of "sip_username" (a registered softphone/deskphone in tenant_sip_credentials) or "user_id" (an organization user whose softphone receives an in-app page). Max 200 members per group.integer
default:"30"
Maximum announcement duration in seconds (5-300).
string
Optional URL of an audio file played as an attention chime before the announcement (max 2048 chars).
GET /api/v1/voice/paging-groups — cursor-paginated list (?cursor=&limit= up to 100). Each item carries memberCount.
GET /api/v1/voice/paging-groups/{id}
PATCH /api/v1/voice/paging-groups/{id} — partial update; any subset of the create fields.
DELETE /api/v1/voice/paging-groups/{id} — hard delete (204; 404 if unknown).
Trigger a Page
POST /api/v1/voice/paging-groups/{id}/page
string
Optional one-shot message read out via TTS instead of the pager’s live mic (1-500 chars).
string
Override the group’s configured mode for this page only (
simplex or duplex) — e.g. force simplex on a duplex group for an emergency announcement.Shared Lines
Shared Line Appearance (SLA) mirrors one “line” (a primary extension) onto several physical phones — the receptionist / exec-assistant pattern where an inbound call rings every member and any member can answer or grab a parked call. This endpoint family manages the line configuration and member roster; the inbound call-fork that makes a call ring multiple phones is handled by the voice call plane.Shared lines are inbound fork-and-park only — no outbound termination is wired here.
Create / List / Get / Update / Delete
POST /api/v1/voice/shared-lines
string
required
Unique shared line name within the organization (max 100 chars). A duplicate name or extension returns
409.string
Optional description (max 500 chars).
string
required
The primary extension mirrored onto the member phones (max 32 chars). Unique within the organization.
string
default:"active"
Line status. One of
active or inactive.GET /api/v1/voice/shared-lines — cursor-paginated list (?cursor=&limit= up to 100). Each item carries memberCount.
GET /api/v1/voice/shared-lines/{id} — returns the line plus its members[] ordered by sortOrder then createdAt.
PATCH /api/v1/voice/shared-lines/{id} — partial update.
DELETE /api/v1/voice/shared-lines/{id} — hard delete; member rows cascade.
Members
POST /api/v1/voice/shared-lines/{id}/members
string
default:"sip_extension"
One of
sip_extension (a registered phone in tenant_sip_credentials, existence-checked org-scoped at add — an unknown username returns 422) or user_id (an organization user whose softphone is resolved by the fork layer at call-time).string
required
The SIP username or user id, depending on
memberKind (max 128 chars). Adding the same value twice returns 409.boolean
default:"true"
Whether this member may grab (pick up) a call already ringing/parked on the line.
integer
Optional button position (0-999). When omitted, the next sequential position is assigned automatically.
409.
DELETE /api/v1/voice/shared-lines/{id}/members/{memberId} — remove a member (204; 404 if unknown).
Devices
Register physical desk phones for zero-touch provisioning. Each device pairs a MAC address with a vendor and (optionally) a SIP credential; the response returns aprovisioningUrl your IT team plugs into the vendor redirect server (Polycom ZTP / Yealink RPS / Cisco EDOS / Grandstream GDMS). On first boot the phone fetches its rendered config and registers against Orbit’s SIP edge.
Provisioning points the phone at Orbit’s SIP edge (
sip.orbit.devotel.io), which routes through to the Devotel softswitch — phones are never provisioned to register directly at an upstream carrier.Create / List / Get / Update / Delete
POST /api/v1/voice/devices
string
required
The phone’s MAC address (12-17 chars, with or without separators). Normalised server-side; a MAC already registered returns
409.string
required
Device vendor. One of
polycom, yealink, cisco, or grandstream.string
Optional model identifier (max 64 chars).
string
Optional id of a SIP credential in your organization to bind to the phone. Must belong to your organization (a foreign id returns
404). Until set, the phone has nothing to register with.string
Optional operator-facing label (max 64 chars).
string
Optional free-text notes (max 1000 chars).
string
ISO 8601 timestamp after which an un-booted device row auto-expires. Defaults to 30 days from creation; pass
null for no expiry (long-tail fleet rollouts).GET /api/v1/voice/devices — list every device in your organization.
GET /api/v1/voice/devices/{id}
PATCH /api/v1/voice/devices/{id} — update label, sipCredentialId, model, notes, disabled, or claimExpiresAt. A field present with value null clears the column.
DELETE /api/v1/voice/devices/{id} — soft delete.
Call Flip / Call Pull
Move a live call between devices without dropping it — the mobile-↔-desktop continuity pattern. The agent flips a call on one registered device (issuing a short-lived handoff token) and pulls it onto another device (their mobile soft-client, another desk phone) by claiming that token. This API manages the handoff ledger; the actual SIP re-INVITE that moves the leg is performed by the voice call plane.Per Orbit’s outbound-termination invariant, the moved (mobile-terminated) leg is re-INVITEd via the Devotel wholesale softswitch — never an upstream carrier’s call-control API.
Flip a Call
POST /api/v1/voice/call-flip
string
required
SID of the live call being flipped (1-128 chars).
string
Optional SIP username (from
tenant_sip_credentials.username) that the call is pinned to. When omitted, any registered device in the organization may pull the call; when set, only that device may pull (otherwise the pull returns 403).string
Optional reference to a
call_logs.id for join-time lookup.string
Optional E.164 number of the remote leg, shown in the pending-flips list.
string
Optional display name of the remote leg.
201 with the handoff, including the full token (returned only on this call), a 4-digit shortCode for DTMF pulls (*7 then the code on a legacy desk phone), and expiresAt. The token TTL is 120 seconds. Re-issuing a flip for the same callSid by the same user while one is active returns the existing token (idempotent).
Pull a Call
POST /api/v1/voice/call-flip/pull
string
required
The token returned by the flip endpoint (or the 4-digit short-code on the dial-code path).
string
required
The pulling device’s SIP username. When the flip pinned a
targetDeviceUsername, this must match.200 with the resolved callSid on success. A token that was already pulled or has expired returns 410; a device mismatch returns 403; an unknown token returns 404.
List / Cancel
GET /api/v1/voice/call-flip — list every live (un-pulled, un-expired) handoff in the organization. Tokens are never returned by this endpoint.
POST /api/v1/voice/call-flip/{id}/cancel — cancel an outstanding flip (the agent changed their mind). A subsequent pull then returns 410. By default only the user who issued the flip may cancel it; owners, admins, and supervisors may cancel any flip in their organization.
Hot-Desking
Let agents sign in to a shared physical desk phone so it re-skins as their own extension for the duration of a shift, then falls back to the shared inbound pool on sign-out — the retail / healthcare / hybrid-office “hardware-first” pattern (Cisco Extension Mobility, RingCentral Hot Desking, 8x8 Shared Phone Desks). There is one active session per device and one per user: signing in to a second device automatically signs you out of the first, and signing in to a device already bound to someone else displaces their session. Displaced sessions are returned in the sign-in response so the dashboard can show who was bumped.Sign In
POST /api/v1/voice/hot-desking/sign-in
string
required
SIP username of the shared desk phone — the same username you manage under SIP Credentials. Lowercase/uppercase letters, digits,
., _ or -; 3–128 characters (for example acme-shared-5F-2).string
Optional human-readable device label captured at sign-in time (max 120 chars) so the audit trail survives a later device rename.
string
Optional ISO-8601 scheduled auto-sign-out. Must be in the future and within 24 hours, otherwise the request returns
400.201 with { session, displaced } on a fresh bind, or 200 with the existing session when the same user re-signs in to the same device (idempotent no-op). A concurrent sign-in race returns 409.
Sign Out
POST /api/v1/voice/hot-desking/sign-out
Releases the calling user’s active session and returns the device to the shared inbound pool. Returns 200 with the released session. A second sign-out returns 410 (with the prior release reason and timestamp) when you have a prior session, or 404 when you were never signed in.
List Active Sessions
GET /api/v1/voice/hot-desking — list the organization’s active (not signed-out) hot-desk sessions, newest first, capped at 200. Powers the dashboard’s hot-desking board.
My Session
GET /api/v1/voice/hot-desking/me — returns the calling user’s active session, or null under data when not signed in to any device.
Hoteling
Hot-desking lets a user claim a shared desk phone now; hoteling adds the advance-booking layer on top — an agent or knowledge-worker reserves a specific desk/extension for a future date/time window, the system prevents double-booking, and a no-show or elapsed reservation is auto-released so a desk is never dead-locked by an absent booker (the Cisco / RingCentral / 8x8 “book a desk” pattern). These endpoints back the reservation calendar shown under Voice → Hot-Desking. A booking is rejected with409 HOTELING_DOUBLE_BOOKED when its [startsAt, endsAt) window overlaps an existing active reservation for the same device; the conflicting reservation is returned under error.details.conflict.
Book a Reservation
POST /api/v1/voice/hoteling/reservations
string
required
SIP username of the shared desk phone being booked — the same username you manage under SIP Credentials. 3–128 characters of letters, digits,
., _ or - (for example acme-shared-5F-2).string
required
ISO-8601 inclusive start of the booked window (for example
2026-07-20T09:00:00Z).string
required
ISO-8601 exclusive end of the booked window. Must be after
startsAt, in the future, and at most 24 hours after startsAt; startsAt itself may be at most 90 days ahead.string
Optional human-readable device label captured at booking time (max 120 chars) so the audit trail survives a later device rename.
string
Optional free-text note shown on the booking calendar (max 500 chars).
201 with the created reservation, or 409 HOTELING_DOUBLE_BOOKED when the window overlaps an existing reservation for the same desk.
List Reservations
GET /api/v1/voice/hoteling/reservations
string
default:"me"
me returns the caller’s reservations; team returns the whole organization’s.string
Optional ISO-8601 lower bound — only reservations ending after this instant are returned.
string
Optional ISO-8601 upper bound — only reservations starting before this instant are returned.
string
Optional filter to a single shared desk.
Cancel a Reservation
DELETE /api/v1/voice/hoteling/reservations/{id}
Cancels an active reservation. The booker can cancel their own; owner/admin can cancel any. Returns 410 when the reservation is already cancelled, fulfilled, or expired, and 404 when it does not exist for your organization.
Release Elapsed Reservations
POST /api/v1/voice/hoteling/reservations/sweep
owner/admin only. Flips any reservation whose window has fully elapsed to expired, freeing the desk. This also runs automatically on every reservation read/book — call it explicitly only to force an immediate sweep. Returns { "released": <count> }.
Extensions
SIP extensions for soft-phone registration. Each extension binds one user to a SIP credential pair (managed via the SIP Credentials endpoints) and can receive direct calls or appear in ring groups. Extensions are managed end-to-end through the dashboard at Voice → Extensions. The underlying call-flow surface (registering, rotating credentials, observing register state) is exposed via the SIP Credentials API at/api/v1/sip-credentials/* (see SIP Credentials).
Calendars
Holiday calendars are per-organization named lists of closure dates. Attach one to an inbound route (holiday_calendar_id) and the route treats those dates as closed regardless of weekday business hours — calls route to your closed-hours handling (voicemail or a fallback). Each calendar carries an IANA timezone, used to decide which calendar day the current time falls on.
Create a Calendar
POST /api/v1/voice/calendars
Requires the owner or admin role.
string
required
Calendar name. Must be unique within your organization — a duplicate name returns
409.string
IANA timezone, e.g.
Europe/Istanbul. Defaults to UTC.object[]
Closure dates. Each entry is
{ "date": "YYYY-MM-DD", "label": "..." } where label is optional. Dates must be today or later (in the calendar’s timezone); up to 366 entries.List / Get / Delete
GET /api/v1/voice/calendars
GET /api/v1/voice/calendars/{id}
DELETE /api/v1/voice/calendars/{id}
The list response includes a usage_count on each calendar — how many of your inbound routes reference it. Deleting a calendar is permanent; any inbound route pointing at it has its holiday_calendar_id reset to null (the route keeps working, with no holiday closures). Delete returns 204.
Update a Calendar
PATCH /api/v1/voice/calendars/{id}
Requires the owner or admin role. Send any subset of name, timezone, or dates; omitted fields are left unchanged. When you send dates, it fully replaces the existing list rather than merging.
Monitoring
Live supervisor surface — listen-in, whisper to the agent, or barge into a call for real-time coaching. All operations require thevoice:monitor scope.
Start Listening (silent monitoring)
POST /api/v1/voice/calls/{id}/listen
Open a one-way audio bridge to the supervisor — neither call leg hears the supervisor.
POST /api/v1/voice/calls/{id}/unlisten
Disconnect the supervisor leg.
Whisper to the Agent
POST /api/v1/voice/calls/{id}/whisper
Audio plays only to the agent leg. The customer cannot hear. Use to coach the agent mid-call.
Barge In
POST /api/v1/voice/calls/{id}/barge
Add the supervisor as a third audible participant. Both call legs hear the supervisor.
Live Monitoring Snapshot
GET /api/v1/voice/monitoring
Returns a denormalised snapshot of every active call, queue, and agent presence — drives the wallboard / Voice → Monitoring page.
Real-time Transcripts
GET /api/v1/voice/calls/{callId}/transcript/stream
Server-Sent Events (SSE) stream of live STT transcript chunks for an in-progress call. Each event carries a JSON body of:
GET /api/v1/voice/calls/{id}/transcript
Final, post-call transcript (one shot, no streaming). Available once the recording has been processed (~30s after hangup).
POST /api/v1/voice/transcribe
Submit an arbitrary audio URL for asynchronous transcription. Returns a transcript_id to poll for the result.
AI Call Intelligence
Post-call analysis — sentiment trajectory, key moments, summary, action items, customer effort score. Powered by the agent runtime’s call intelligence model.Per-Call Intelligence
GET /api/v1/voice/calls/{id}/intelligence
Run Sentiment On-Demand
POST /api/v1/voice/calls/{id}/sentiment
Forces a re-run of the sentiment analyzer. Used when a call’s transcript has been edited or when the org’s sentiment model has changed.
List Intelligence Records
GET /api/v1/voice/intelligence/calls
List intelligence records for the org with filters by sentiment, agent, date range, and customer-effort threshold.
Trends
GET /api/v1/voice/intelligence/trends
Aggregated time-series of sentiment + effort score, bucketed by day / week. Drives the Voice → Intelligence → Trends dashboard.
string
ISO-8601 start of the trend window. Defaults to 30 days ago.
string
ISO-8601 end of the trend window. Defaults to now.
string
default:"day"
Aggregation bucket:
hour, day, week.Voice Quality (VAQI)
Voice Aggregated Quality Index — a 0–100 score derived from MOS, jitter, packet-loss, and one-way audio detection. Surface for SIP-trunk health and per-call diagnostics.Aggregates
GET /api/v1/voice/quality/aggregates
Per-period rollup of every active SIP trunk: average MOS, p95 jitter, packet loss percentage, VAQI score. Used by the Voice → Quality dashboard.
Per-Carrier Quality
GET /api/v1/voice/quality/carriers
Same shape as /aggregates but bucketed by upstream carrier. Use to compare provider performance across the same time window.
Worst Calls
GET /api/v1/voice/quality/worst-calls
Top-N calls in the period by lowest VAQI / highest packet loss / highest jitter. Drives the “Investigate” CTA in the dashboard.
VAQI Detail
GET /api/v1/voice/vaqi
Full per-call quality matrix (MOS, jitter, packet loss, RTT, audio gaps) with optional call_id filter for a single-call drill-down.
Softphone Token
POST /api/v1/voice/softphone/token
Mints a short-lived JWT (default 60s expiry) for browser-based softphone clients to register against the Orbit Media / Jambonz cluster without exposing a long-lived API key. Orbit Media is forked from LiveKit OSS under Apache-2 — see attribution.
POST /api/v1/voice/softphone/dial
Server-initiated outbound dial from a softphone session — the softphone client passes the to and Orbit places the call from the bound extension’s caller-ID.
Voice Clones
Custom-trained TTS voices for AI agents. Voices are tenant-scoped. A clone is sourced from an existing recorded call on your account — you do not upload an audio sample. The source call must have a granted, active recording-consent receipt, and you must attest to the voice owner’s permission when you create the clone.Create
POST /api/v1/voice/clones
Request body:
You must supply at least one of
voice_owner_consent_evidence_url or voice_owner_consent_text. A request that clones a call without a granted recording-consent receipt, or without a consent attestation, is rejected with 400.
The response returns the clone id, its status (processing, ready, or failed), the source_call_id, and created_at. Cloning runs asynchronously; poll GET /api/v1/voice/clones until the status is ready.
List / Delete
GET /api/v1/voice/clones
DELETE /api/v1/voice/clones/{id}
Voice IDs returned by GET /api/v1/voice/clones can be referenced from agent configuration via tts_voice_id.
Coaching Notes
Supervisors attach a structured, acknowledgeable coaching note to a specific call, targeting the agent who handled it. Notes appear on the agent’s coaching timeline; the agent acknowledges each one. Writing a note requires an owner, admin, or supervisor role.Create a Coaching Note
POST /api/v1/voice/calls/{id}/coaching-notes
string
required
The call the note is anchored to.
string
required
The agent the note coaches. Must be the agent who handled this call — a mismatch is rejected with
422 AGENT_MISMATCH.string
required
The coaching text (1–4000 characters).
string
default:"general"
Note category:
positive (commendation), improvement (coachable moment), warning (policy or performance flag), or general (informational).string[]
Optional QA-rubric references this note maps to (up to 10).
string
Optional follow-up action for the agent (up to 500 characters).
List Coaching Notes on a Call
GET /api/v1/voice/calls/{id}/coaching-notes
Owners, admins, and supervisors see every note on the call. An agent sees only the notes addressed to them.
Acknowledge a Coaching Note
PUT /api/v1/voice/calls/{id}/coaching-notes/{noteId}/acknowledge
Only the agent the note is addressed to may acknowledge it. Acknowledgement is idempotent — a repeat call returns the original acknowledgement timestamp.
Coaching Plans
A coaching plan wraps a longitudinal, multi-session improvement program for one agent — a goal, an optional due date, and a lifecycle that closes with an outcome. Writes require an owner, admin, or supervisor role; an agent can read only their own plans.Create a Coaching Plan
POST /api/v1/voice/coaching-plans
string
required
The agent the plan addresses.
string
required
The goal statement (1–2000 characters), e.g. “Reduce average handle time by 20% over four weeks.”
string
Optional target completion timestamp (ISO 8601). Omit for an open-ended plan the supervisor closes manually.
List Coaching Plans
GET /api/v1/voice/coaching-plans
string
Filter to one agent’s plans.
string
Filter to plans authored by one supervisor.
string
Filter by lifecycle status:
active, completed, or cancelled.integer
default:"50"
Results to return (1–200). Active plans sort first, then by due date.
Get a Coaching Plan
GET /api/v1/voice/coaching-plans/{id}
Update a Coaching Plan
PUT /api/v1/voice/coaching-plans/{id}
Only active plans can be updated — updating a closed plan returns 409 PLAN_CLOSED. Supply at least one of agent_user_id, goal, or due_at.
Close a Coaching Plan
POST /api/v1/voice/coaching-plans/{id}/close
string
default:"completed"
Terminal status:
completed or cancelled.string
required
Supervisor judgement of the result:
met, partial, or missed.string
Optional narrative on what changed (up to 2000 characters).
Coaching-Plan Effectiveness
GET /api/v1/voice/coaching-plans/{id}/effectiveness
Returns a data-driven before/after report: the agent’s Average Handle Time, First-Contact Resolution, and CSAT measured over a window before the plan compared against an equal window after it, with each metric classified as improved, regressed, flat, or insufficient_data. Active plans report in-flight progress from the plan’s start; closed plans measure from the close date.
integer
default:"14"
Length in days of both the before and after comparison windows (1–90).
integer
default:"24"
Look-ahead used by the First-Contact-Resolution calculation (1–168).
Real-Time Agent Assist
Stream real-time guidance to an agent while a call is live. An LLM reads the running transcript and emits cards — a suggested reply, relevant knowledge-base articles, and a next-best action — over Server-Sent Events.Agent-Assist Stream
GET /api/v1/voice/calls/{callId}/agent-assist/stream
The response is an SSE stream (Content-Type: text/event-stream). It opens with an event: connected handshake frame carrying the current call status, followed by an event: status frame. Each new suggestion arrives as an event: assist frame whose data is the card JSON. : ping comment lines keep the connection alive, and an event: end frame is sent when the call completes.
Real-Time Supervisor Assist
The supervisor-facing companion to agent assist. Instead of a suggested reply, it surfaces an oversight action card —intervene, escalate, offer, or monitor — to a supervisor watching a live call, so they can decide in the moment whether to whisper-coach, barge in, relay a resource, or keep watching. Requires an owner, admin, or supervisor role; supervisors see only calls on queues they supervise.
Supervisor-Assist Stream
GET /api/v1/voice/supervisor/calls/{callId}/assist/stream
Same SSE shape as agent assist: an event: connected handshake, an event: status frame, event: assist action cards as they are generated, : ping keep-alives, and a closing event: end frame.
Real-Time Compliance Monitor
Score a live call against a compliance ruleset as it happens. An LLM evaluates the running transcript for mandatory disclosures (recording-consent notice, mini-Miranda, FDCPA language), profanity, and script adherence, and streams per-rule findings to a monitoring panel. Critical and warning breaches also raise avoice.compliance.violation event so a supervisor is alerted even without the panel open.
Compliance-Monitor Stream
GET /api/v1/voice/calls/{callId}/compliance/stream
An SSE stream that opens with an event: connected handshake and an event: status frame, then emits an event: compliance frame each time the monitor scores a rule. : ping comments keep the connection alive; an event: end frame closes it when the call completes.
Each event: compliance frame carries a findings array — one entry per rule, each with a kind (disclosure, profanity, or script_adherence), a status (satisfied, pending, or violated), a severity (info, warning, or critical), and a short detail (plus an optional transcript excerpt). The frame also carries an overall_adherence score in [0, 1] and an escalated array listing the rule_ids that raised a voice.compliance.violation supervisor alert on that tick.
Dial Plan
A dial plan applies PBX-style digit-translation rules to outbound destinations before the call is placed — strip an access prefix, prepend a country code, or rewrite a short code to a full number. The plan applies across your whole organization and requires an owner or admin role.Get the Dial Plan
GET /api/v1/voice/dial-plan
Returns the current plan. When none is configured, returns { "enabled": false, "rules": [] } so you can render an empty state without handling a 404.
Replace the Dial Plan
PUT /api/v1/voice/dial-plan
Replaces the plan in full. The response echoes the normalized plan — no-op rules are dropped and defaults are applied — so it reflects exactly what the translation engine will run.
boolean
required
Whether the plan is active. When
false, destinations are dialed unchanged.object[]
required
Ordered translation rules (up to 100). The first matching rule is applied.
Each rule must do something — set
replace_with, or set strip_digits/prepend. A rule that would leave the number unchanged is rejected with 400.Dialing Restrictions
Dialing restrictions are a class-of-service policy: they cap which categories of number each user is allowed to dial outbound — toll-free, domestic, international, or premium-rate. The policy applies across your organization and requires an owner or admin role.Get Dialing Restrictions
GET /api/v1/voice/dialing-restrictions
Returns the current policy, or defaults (enabled: false) when none is set.
Replace Dialing Restrictions
PUT /api/v1/voice/dialing-restrictions
boolean
required
Whether the policy is enforced. When
false, no class-of-service check runs.string
ISO 3166 alpha-2 code (e.g.
US, GB) used to classify destinations as domestic vs international.Extra premium-rate prefixes to treat as the
premium class, in + E.164 form (up to 100).string[]
Classes any user may dial unless overridden. Values:
tollfree, domestic, international, premium.object
Per-user overrides keyed by user id. Each value is
{ "allowed_classes": [...] } listing the classes that user may dial.Voicemail Greeting
Each user manages their own personal voicemail greeting — upload an audio file, synthesize one from text, fetch the current greeting, or remove it to fall back to your tenant default. Any authenticated user can manage their own greeting; no admin role is required.Get the Current Greeting
GET /api/v1/voice/voicemail/greeting
Returns voicemail_greeting_url and voicemail_greeting_uploaded_at (both null when no personal greeting is set).
Upload a Greeting
POST /api/v1/voice/voicemail/greeting
Send multipart/form-data with a single audio file. MP3 and WAV are accepted, up to 2 MB. Non-audio uploads return 415; a file over the limit returns 413.
Synthesize a Greeting from Text
POST /api/v1/voice/voicemail/greeting/tts
string
required
The greeting script to speak (1–500 characters).
string
Optional voice to synthesize with.
string
Optional BCP-47 language code such as
en or en-US.Delete the Greeting
DELETE /api/v1/voice/voicemail/greeting
Removes your personal greeting and reverts to the tenant default.
Post-Call Surveys (CSAT & NPS)
Configure IVR post-call surveys and read their results. After a qualifying call ends, the caller is offered a single-digit survey — a 1–5 CSAT rating or a 0–10 NPS rating. The endpoints below manage the survey configuration and return the aggregated analytics; the survey itself is delivered by the voice platform.CSAT Analytics
GET /api/v1/voice/csat/surveys/{id}/analytics
Returns totals, average score (1–5), satisfaction percentage (share scoring 4 or 5), a per-score breakdown, and a daily trend.
string
required
The survey id.
string
default:"30d"
Reporting window:
7d, 30d, 90d, or all.string
Optional filter to one agent’s calls.
CSAT Configuration
GET /api/v1/voice/csat/surveys/{id}/config
PATCH /api/v1/voice/csat/surveys/{id}/config
Read or update the survey’s delivery settings. PATCH accepts any of the fields below (at least one is required).
boolean
Whether the survey is offered after calls.
string
How the survey is delivered:
ivr_post_call (spoken at the end of the call) or message.object
IVR prompt and gating settings.
NPS Analytics
GET /api/v1/voice/nps/surveys/{id}/analytics
Returns totals, the NPS score (promoters minus detractors, −100 to +100), the promoter/passive/detractor breakdown, and a daily trend. Accepts the same window and agent_user_id query parameters as CSAT analytics.
NPS Configuration
GET /api/v1/voice/nps/surveys/{id}/config
PATCH /api/v1/voice/nps/surveys/{id}/config
Read or update the NPS survey settings. The body matches the CSAT config above (enabled, delivery_mode, ivr_config), with an NPS 0–10 prompt.