Skip to main content

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 (09, *, #, AD), 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 501000.
integer
default:"50"
Inter-digit gap in milliseconds. Range 50500.
Use to navigate carrier IVRs from a programmatic outbound call (PIN entry, account confirmations, etc.).

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 via record: 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 handling mode, 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)

Beyond Add 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.
Drives queue routing — 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 the dial verb is emitted to the picked member. One target per call so per-call billing aligns with the answerOnBridge contract.
Wiring a ring group to an inbound number: save the route via 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.
The response reports both delivery channels so the UI can render “Paged N softphone users + M deskphones (K legs failed)“:

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.
A line is capped at 50 members; adding past the cap returns 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 a provisioningUrl 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.
Returns 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.
Returns 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.
Returns 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 with 409 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).
Returns 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.
Returns the matching reservations ordered by window start, capped at 200. Elapsed windows are auto-released before the list is read.

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 the voice: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:
Use for live captions, real-time agent assist, or downstream sentiment / intent classification. The stream closes when the call ends. 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.
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 a voice.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.
string[]
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.

Call Statuses