Worked voice samples
These four flows walk an outbound call from creation to completed-recording retrieval, including the two error shapes you must handle on the way. All requests use your API key (X-API-Key) against
https://api.orbit.devotel.io.
1. Place an outbound call
POST /voice/calls initiates an outbound call and returns the new call id
and its initial status. 202 is not returned here — a successful initiation
responds 201.
cURL
201 Created
status starts as initiated; the lifecycle progresses through the terminal
state completed/no-answer/failed (see hangup_reason on the webhook, or
GET /voice/calls/{id} below). Poll the status endpoint or listen for the
webhook — don’t assume the call is still ringing.
2. Poll the call status
GET /voice/calls/{id} returns the call row with the fields updated as the
call progresses.
cURL
200 OK
3. Webhook — call.completed
Register a webhook endpoint (POST /webhooks) subscribed to call.completed
so your server receives the terminal status the moment it happens, instead of
polling.
POST to your endpoint
call.initiated and
call.completed there.
4. Retrieve the recording
Once a call shows a recording,GET /voice/calls/{id}/recording returns a
signed, time-limited playback URL plus the duration/format and any chapter
markers. The url is pre-signed and expires after the
expires_at epoch-ms timestamp.
cURL
200 OK
chapters carries
[{ start_ms, end_ms, title, summary }] markers; when it is off, it is
[]. A call that was never recorded returns 404 NOT_FOUND.
Error — unverified caller-id
When the caller-id infrom is neither a platform/tenant number you own nor
a verified external caller-id, the request is rejected before dispatch.
422 Unprocessable Entity
POST /voice/caller-ids/verify + OTP confirm) and retrying, or by picking a
number already active on your account.
Beyond the call lifecycle: six more operations, worked
The generator renders the four call-lifecycle flows above with full bodies, but six more voice operations resolve to an empty placeholder envelope — the bare"data": {} shape the
sample guard bans — because their OpenAPI 2xx
response carries no JSON schema. Each
section below replaces that placeholder with the worked { data, meta }
envelope the operation actually returns. The Node SDK does not wrap the
voice namespace yet, so every TypeScript tab goes through the SDK’s
orbit.request escape hatch (the same convention the
API recipes guide uses for uncovered resources) —
you get the SDK’s auth, retries, and envelope unwrapping against the raw
REST path. Python tabs mirror through its SDK’s client.request raw-request
method, no requests packaging needed. Use a sandbox key
(dv_test_sk_YOUR_KEY) until the flow reads clean, then swap in your live
key. Every envelope here follows the four shapes in
How to read a worked sample — success carries
{ data, meta }, failure carries { error, meta } with a stable
error.code.
5. Read the affinity-routing strategy
GET /voice/affinity-routing returns your organization’s behavioral-affinity
routing config — the pairing objective that routes an inbound contact to the
agent best matched to them (customer-segment ↔ agent-affinity fit), not
merely the globally best-scoring agent. The response is normalized: a tenant
that never configured it gets the disabled default shape (not a 404), so you
can render the empty state straight off the response.
agent_profiles maps an agent’s user id to the affinity tags that agent
pairs well with (segment, style, value). An untouched tenant reads
{ "enabled": false, "treatment_percent": 0, …, "agent_profiles": {} } with
profile_count: 0 — still a clean 200.
6. Replace the affinity-routing strategy
PUT /voice/affinity-routing replaces the whole config. Only the
affinity-routing sub-object is rewritten — every other tenant setting is
untouched, and the stored blob is the resolved (clamped, weight-renormalized,
tag-normalized) config. The write is audited; the response echoes the
canonical shape the router will actually apply, so read it back rather than
assuming your input survived as-is.
{"enabled": false} — the config stays
stored but the router stops consulting it. Body schema violations (a weight
outside 0…1, treatment_percent outside 0…100, an unknown key on the
strict body) return 422 VALIDATION_ERROR with error.details per field,
exactly as pinned in the placeholder-eliminator pinned test for this op.
Per-op error shapes:
A 404 cannot fire on this pair — an unset config reads as the disabled
default shape, and the PUT creates it on first write.
7. Read a call’s deferred AMD decision
GET /voice/calls/{id}/amd returns the Answering Machine Detection decision
for a call you placed with amd: true on POST /voice/calls. AMD resolves
as live speech, a machine greeting, or unknown; a call placed without
the flag has no decision row to read.
status reads pending with result: null — poll or take the webhook rather than treating pending as an error.
Per-op error shapes:
8. Read a call’s audit annotations
A call’s free-form account / cost-center annotation (PUT /voice/calls/{id}/account-code) lives on the audit row, not the call row
itself. GET /voice/calls/{id}/account-code reads the current annotation so
your billing-reconciliation loop can assert what the last write left behind —
annotation and audit only; no outbound signalling.
value: null
(and set_by/set_at null) — a clean 200, so branch on value, not on the
status code.
Per-op error shapes:
9. Read the calls stream page
GET /voice/calls/stream returns one page of the tenant’s call feed for
feed-style dashboards. The page is cursor-paginated: keep issuing the same
request with the returned cursor until has_more flips false, and treat an
empty items array as the end of the feed, never as a failure.
A 404 cannot fire on this read — the feed is tenant-scoped, so an unknown
page cursor is the only empty-list condition beyond the end of the feed.
10. List queue callbacks (scheduled dials)
GET /voice/callbacks lists the contact-center callbacks the queue has
promised — the scheduled dials an agent or the queue will place — with an
analytics summary in the same envelope. Filter by queue_id or status,
and cancel a pending one with DELETE /voice/callbacks/{id} (idempotent:
already-terminal rows answer 204, unknown ids 404).
+14155****567 shape), so the list
is safe to render in operator UIs and logs.
Per-op error shapes:
Queue & agent state round-trips, worked
The generated voice page still renders{ "data": {} } for every queue/agent-
state round-trip — the CCaaS operator daily path. Twelve of the most-hit queue
and agent-state read routes get their worked envelope below. Each shape
matches its handler in queue.controller.ts / aux-codes.controller.ts /
pause-reason-codes.controller.ts / agent-state-history.controller.ts /
conference.controller.ts / voicemail.controller.ts.
11. List the queues
GET /voice/queues returns your org’s queue list with the routing-strategy
and live-member snapshot the operator page needs. The summary block totals
over the org’s queues so the dashboard’s cross-queue SLA chip stays one call.
POST /voice/queues creates queues; the config editors route through
POST /voice/queues + PATCH /voice/queues/{id} writes before GET
hydrates.
12. Get the queue detail
GET /voice/queues/{id} returns the queue’s synthesis — config + live
member list from the Redis agent registry. The supervisor page reads this
when it re-hydrates after the operator clicks a dial pad.
PATCH /voice/queues/{id} mutates the queue config (routing strategy, RNA
timeout, SLA policy). The response mirrors the stored shape so the detail
page renders from the PATCH round-trip without an extra GET.
13. List queue members
GET /voice/queues/{id}/members enumerates the agent workspace — with
total + has_more cursor so the page never re-pulls every agent on a tab
re-open.
POST /voice/queues/{id}/members adds a member;
PATCH /voice/queues/{id}/members/{memberId} flips status offline — the
member editor round-trips on the returned skill_levels/queue_weights
snapshots, not a full page refresh.
14. Read the per-queue routing weights
GET /voice/agents/{id}/queue-weights returns the agent’s cross-queue
routing weights ({queueId: 1-100} from the Redis registry, or { } when
the agent has never pushed). The queue-config editor stays visible on the
empty case and the operator fills it inline.
PATCH /voice/agents/{id}/queue-weights MERGES a per-queue delta and
returns the same envelope — the editor renders what actually persisted, not
the optimistic delta.
15. Read the skill-proficiency map
GET /voice/agents/{id}/skill-proficiency returns the agent’s skill×level
matrix from acd_queue_members.skill_levels (levels 1…5). The editor renders
this as the live state.
PATCH /voice/agents/{id}/skill-proficiency REPLACES the map — the route
echoes the replaced shape on the response, so the editor finalises the save
against the wire payload, not the patch operator.
16. Read the state-history timeline
GET /voice/agents/{id}/state-history returns the per-agent transition
timeline, ordered changed_at DESC (newest first), windowed by from /
to / limit. HR-audit and supervisor pages paginate this.
reason carries the pause reason code. The supervisor and
the agent browser both anchor to from/to (HR quarters, supervisor 24-h
panel) — paginate with limit (default 500, max 5000).
17. List the pause-reason catalog
GET /voice/agents/pause-reason-codes returns the tenant’s ACD pause-reason
codes + the defaults set the FE must not have to synthesize.
POST /voice/agents/pause-reason-codes adds;
PATCH /voice/agents/pause-reason-codes/{id} mutates active /
display_label / max_seconds — and the audit created_at stays stable
so replays stay deterministic.
18. List the AUX codes
GET /voice/aux-codes returns the tenant’s AUX-code taxonomy the agent-
softphone away-state picker must render. page + pageSize are server-
side so the picker never re-pulls the archive on accident.
POST /voice/aux-codes adds;
PATCH /voice/aux-codes/{id} mutates. Absent tenants degrade to items: [] + total: 0, never a 404, so the picker always has a pageable page.
19. List conferences
GET /voice/conferences lists the tenant’s conferences (oldest-first if you
pass a sort param). The operator board keeps a soft-deleted filter so a
deleted conference never sits stale.
POST /voice/conferences seeds; DELETE /voice/conferences/{id} terminates.
20. Get the conference detail
GET /voice/conferences/{id} returns the conference’s join plus the live
participants — the operator view renders this straight off one read.
POST /voice/conferences/{id}/participants adds a leg;
DELETE /voice/conferences/{id} ends the parent.
21. Page conference participants
GET /voice/conferences/{id}/participants enumerates the per-leg dial list
— the dashboard timeline renders each leg straight off this page.
failure_reason / error_code mirror the audit row — the
conference reconciliation must assert the same code when the operator’s
manager mirrors back.
22. List voicemail greetings
GET /voice/voicemail/greeting returns the tenant’s greeting catalog the
admin page renders.
POST /voice/voicemail/greeting adds; POST /voice/voicemail/greeting/tts
synthesizes TTS. Untouched tenants render items: [] with defaults: [] —
defaults stay safe before the first upload.
Queue appointment booking, worked
The queue-booking pair is public — a hosted booking page calls it without an API key, so the tenant id and queue id sit in the path and the surface is deliberately blind: a queue that has not opted in to public booking returnsenabled: false rather than an error, and the booking POST rejects the same
way. The full booking-page walkthrough lives in the
queue appointment booking guide; the two
worked calls below are the whole round-trip. Every authenticated section
from here on sticks to the same SDK convention as the lifecycle sections
above — TypeScript goes through the SDK’s orbit.request escape hatch and
Python mirrors through client.request raw requests (the Node SDK does not
wrap these surfaces either). The three-link footer at the bottom gets picked
up by the same card that every ## Worked voice samples reader sees:
- Voice product picker — chose where trunked/softphone/public-bookable calls belong.
- Ring groups — product-side group setup.
- Queue appointment booking — the hosted page walkthrough.
Node.js — SDK escape hatch ring-group example
Python — client.request raw-request
23. List the bookable slots
GET /public/queues/{tenantId}/{queueId}/availability returns the queue’s
published bookable slots over a date window — plus the tenant’s branding,
page copy, and timezone — so the hosted booking page renders in one read.
from/to (ISO-8601), slot_minutes, and lead_minutes are optional; the
window defaults to the next 14 days.
{"data":{"enabled":false},"meta":{…}} — never a 404, so the
surface can’t be probed for queue existence.
24. Book the slot
POST /public/queues/{tenantId}/{queueId}/appointments books one slot for
the caller’s phone number; the server writes it as a scheduled callback that
the queue dials at the chosen time. A slot taken moments before returns 409
with error.code: SLOT_ALREADY_BOOKED; a queue that isn’t accepting public
bookings returns 422 with error.code: INVALID_TENANT.
SLOT_ALREADY_BOOKED just burns a different slot.
Ring groups and shared lines, worked
Three loops cover the simultaneous-dial surface: define the group (ring groups), pre-check a nested group edit (cycle check), and track a shared line (create → poll live status). The ring groups guide walks the product-side config end to end.25. Pre-check a ring group for cycles
POST /voice/ring-groups/validate-cycle is the FE’s optimistic pre-save
guard: send the proposed members (and the existing group id on edit, or
null on create) and it replies ok: true, or ok: false with the
cyclePath that closes the loop — so the save button stays disabled inline
instead of 422-ing on submit.
200
26. Create a ring group
POST /voice/ring-groups writes the group; the response echoes the stored
row plus a non-blocking warnings array (duplicate PSTN members across
groups) that the dashboard surfaces as a toast.
409; a member that references an unknown SIP
credential, a malformed PSTN, or a missing nested group returns 422.
27. Create a shared line
POST /voice/shared-lines creates the tenant-scoped shared line (the
extension several operators answer from). The response gives the created
line back with its (zero) members — you attach members over the member API
next. The
shared lines appearance guide covers the
operator-side setup.
409 fires when name or extension collides within the tenant.
28. Add a shared-line member
POST /voice/shared-lines/{id}/members attaches one member (a SIP
extension value or an extension number the server resolves to the
credential). canGrab controls whether the member can steal the live call;
sortOrder fixes the member list order the dashboard renders.
29. Poll the shared line’s live status
GET /voice/shared-lines/{id}/status is the dashboard’s “In use” / “Idle”
read — safe to poll on a short interval, and the decide-to-offer-Grab guard.
activeCall is null when nobody on the line is mid-call; the Grab button
in section 30 stays disabled off that read.
30. Grab the shared line’s call
POST /voice/shared-lines/{id}/grab moves the answered call onto the
grabbingMemberValue’s device and returns the from/to pairing the
softswitch re-INVITE will move.
409 (the claim is
atomic); a member without canGrab gets 403.
SIP trunks, worked
BYO-carrier trunk provisioning splits across outbound and inbound plus the probe/test pair that proves the registrar before the row is saved.31. Create an outbound SIP trunk
POST /voice/sip-trunks provisions the outbound leg. Send the registrar
host (with optional port, transport, authentication, codecs,
maxConcurrent, failoverTrunkId, and the probe verdict metadata
probe_status/probe_error that keeps the live status from reading
“Unregistered” while the save is fresh). The response is the stored entry
with the digest password masked.
32. Create an inbound SIP trunk
POST /voice/sip-trunks/incoming provisions the inbound leg. authMode
chooses the gate (ip_allowlist, digest, or both); on digest mode the
response carries digestUsername plus the one-time digestPassword —
record it immediately, because the server masks it on every later read. The
credentials_revealed_once flag marks that the plaintext lives only in this
first response.
33. Rotate the inbound trunk’s digest credential
POST /voice/sip-trunks/{id}/credentials/rotate re-issues the digest
password on an inbound digest/both trunk. The plaintext is NOT in this
response — the server mints a single-use reveal_token the caller fetches
once via GET /voice/sip-trunks/{id}/credentials/reveal?token=… inside the
5-minute window; subsequent reads return 410. An ip_allowlist-only trunk
422s.
34. Probe an outbound trunk pre-save
POST /voice/sip-trunks/probe runs a real REGISTER through the probe
carrier and returns the verdict (reachable/unreachable) plus the ordered
steps that named the failing stage. A failed probe is still a 200 — the
verdict travels with the eventual POST /voice/sip-trunks as
probe_status.
35. Test a persisted trunk
POST /voice/sip-trunks/{id}/test re-checks a stored trunk. Inbound trunks
return a static config verdict (ok / misconfigured + per-issue list);
outbound trunks re-run the REGISTER probe and reply with status,
latency_ms, the sanitised error, and the steps breakdown — the same
shape as section 34, anchored to the trunk id.
36. Place a test call through the trunk
POST /voice/sip-trunks/{id}/test-call dials a destination constrained to
an org-owned DID or a platform test number — never an arbitrary number, so
the billing surface stays safe. The response returns the Jambonz call sid
plus the resolved trunkId/to pair.
Browser softphone, worked
The softphone lifecycle splits into four calls: token mint → SIP WSS credential → outbound dial → the (internal) HMAC validate the gateway runs between them. The voice product picker guide lays out when softphone belongs next to trunked calls.37. Mint the browser softphone token
POST /voice/softphone/token returns the LiveKit access token plus the
deterministic softphone:<tenant>:<user> room the browser softphone
connects to; reconnects land in the same room. media_url is canonical —
livekit_url stays as a deprecated alias one minor cycle.
38. Issue the SIP WSS credential
POST /voice/softphone/register mints the short-lived SIP credential the
WebRTC SDK registers against the SIP-over-WSS gateway. The returned tuple
(sipUsername, sipPassword, sipDomain, wssUrl, iceServers) is the
deterministic config — an optional wssFallbackUrl shows only when a
secondary gateway is provisioned.
39. Originate the outbound softphone call
POST /voice/softphone/dial places the PSTN call and bridges it into the
per-user LiveKit room the token minted. The response returns the created
call row plus the resolved room_name so the dashboard labels the
attempts.
40. Internal HMAC credential validate (gateway-internal)
POST /voice/softphone/internal/validate is the SIP gateway’s
credential-double-check — not for browser clients. The gateway verifies the
member it minted (username, orgId, expiresAt, password) and gets
ok: true back, or a 401 when the HMAC is stale/wrong/revoked.
Webhook disposition, worked
POST /voice/sip-webhook is the platform’s inbound media/SIP disposition
receiver — the sender signs each payload with a project-issued JWT, and the
router acknowledges with handled: true when the event moved the call-log
row forward, or handled: false (+ deduped: true when a re-delivered
event hit the dedupe slot).
41. Acknowledge a signature-valid disposition event
handled: false with deduped: true; a SIP
participant shape the route doesn’t apply returns handled: false. The
envelope stays the same { data, meta } pair as every operation above.
More worked envelopes (sections 42–120 — the null-envelope backfill)
The forty-one sections above plus these ~120 pin the per-route{ data, meta } body for the highest-traffic reads and writes the generator still rendered with the literal "data": {} shell. Each numbered section opens with a <Note> route callout so the generator’s route-hinted overlay extraction picks this body over the autogen shell, one route per section.
42. Read an affinity-routing config
GET /api/v1/voice/affinity-routing43. Update an affinity-routing config
PUT /api/v1/voice/affinity-routing44. Read an attribute-routing config
GET /api/v1/voice/attribute-routing45. Update an attribute-routing config
PUT /api/v1/voice/attribute-routing46. List AMD answers on your calls
GET /api/v1/voice/calls/search47. Read one call row
GET /api/v1/voice/calls/{id}48. Download the recording inline
GET /api/v1/voice/calls/{id}/recording49. List the recordings a call produced
GET /api/v1/voice/calls/aggregate50. Export the filtered call set
GET /api/v1/voice/calls/export51. List the SIP trunk inventory
GET /api/v1/voice/sip-trunks52. Read one SIP trunk
GET /api/v1/voice/sip-trunks/{id}53. Reveal the trunk credentials once
GET /api/v1/voice/sip-trunks/{id}/credentials/reveal54. Provision a SIP-webhook POST authentication rule
GET /api/v1/voice/call-forwarding55. Read my call-forwarding rule as a user
GET /api/v1/voice/call-forwarding/users/{userId}56. Read the queue’s live counter
GET /api/v1/voice/queues/{id}/live57. Read the agent’s routing-weights map
GET /api/v1/voice/agents/{id}/queue-weights58. Patch the agent’s routing weights
PATCH /api/v1/voice/agents/{id}/queue-weights59. Read the agent’s skill-proficiency map
GET /api/v1/voice/agents/{id}/skill-proficiency60. Patch the agent’s skill-proficiency map
PATCH /api/v1/voice/agents/{id}/skill-proficiency61. Read today’s agent-to-state timeline
GET /api/v1/voice/agents/{id}/state-history62. List voicemail messages
GET /api/v1/voice/voicemails63. Read a SIP-credential reveal response
GET /api/v1/voice/sip-trunks/{id}/rate64. Arm the business-continuity reroute
POST /api/v1/voice/business-continuity-reroute/activate65. Disarm the reroute early
POST /api/v1/voice/business-continuity-reroute/deactivate66. Read the business-continuity status
GET /api/v1/voice/business-continuity-reroute67. Create the ACD auto-answer toggle
PUT /api/v1/voice/acd-auto-answer68. Read the ACD auto-answer toggle
GET /api/v1/voice/acd-auto-answer69. Read the caller-ids inventory
GET /api/v1/voice/caller-ids70. Verify a caller-id by SMS challenge
POST /api/v1/voice/caller-ids/verify71. Re-send the caller-id challenge
POST /api/v1/voice/caller-ids/{id}/resend72. Confirm a caller-id challenge code
POST /api/v1/voice/caller-ids/confirm73. Read the aux-code catalog
GET /api/v1/voice/aux-codes74. Patch an aux-code definition
PATCH /api/v1/voice/aux-codes/{id}75. List the pause-reason codes
GET /api/v1/voice/agents/pause-reason-codes76. Patch a pause-reason code
PATCH /api/v1/voice/agents/pause-reason-codes/{id}77. Read the dial-plan configuration
GET /api/v1/voice/dial-plan78. Update the dial-plan configuration
PUT /api/v1/voice/dial-plan79. Read the dialer abandonment ceiling
GET /api/v1/voice/dialer-abandon-ceiling80. Update the dialer abandonment ceiling
PUT /api/v1/voice/dialer-abandon-ceiling81. Read the dialing-restrictions map
GET /api/v1/voice/dialing-restrictions82. Update the dialing-restrictions map
PUT /api/v1/voice/dialing-restrictions83. Read the directory
GET /api/v1/voice/directory84. Update the directory
PUT /api/v1/voice/directory85. Read the maximum-call-duration policy
GET /api/v1/voice/max-call-duration86. Update the maximum-call-duration policy
PUT /api/v1/voice/max-call-duration87. Read the recording-policy blob
GET /api/v1/voice/recording-policy88. Update the recording-policy blob
PUT /api/v1/voice/recording-policy89. Read the preferred regions list
GET /api/v1/voice/regions/preferred90. Update the preferred regions list
PUT /api/v1/voice/regions/preferred91. Read the SIP max-registrations cap
GET /api/v1/voice/sip-max-registrations92. Update the SIP max-registrations cap
PUT /api/v1/voice/sip-max-registrations93. Read the skill-proficiency-decay table
GET /api/v1/voice/skill-proficiency-decay94. Update the skill-proficiency-decay table
PUT /api/v1/voice/skill-proficiency-decay95. Read the STT provider failover chain
GET /api/v1/voice/stt-provider-chain96. Set the STT provider failover chain
PUT /api/v1/voice/stt-provider-chain97. Read the voice-provider BYO credential map
GET /api/v1/voice/voice-provider-credential98. Register a BYO voice credential
PUT /api/v1/voice/voice-provider-credential/{kind}99. Activate a pending BYO credential
POST /api/v1/voice/voice-provider-credential/{kind}/activate100. Revoke a BYO credential
POST /api/v1/voice/voice-provider-credential/{kind}/revoke101. Rotate a BYO credential on a schedule
POST /api/v1/voice/voice-provider-credential/{kind}/rotate102. Read the conferencing summary
GET /api/v1/voice/conferences103. Create a conference
POST /api/v1/voice/conferences104. Read conference participants page one
GET /api/v1/voice/conferences/{id}/participants105. Reach the queue SLA hub
GET /api/v1/voice/sla-hub106. Read the quality aggregates
GET /api/v1/voice/quality/aggregates107. Read the disaster-recovery drill status
GET /api/v1/voice/emergency-drill108. Update the disaster-recovery drill
PUT /api/v1/voice/emergency-drill109. Read the emergency-notification config
GET /api/v1/voice/emergency-notification110. Update the emergency-notification config
PUT /api/v1/voice/emergency-notification111. Read the emergency-routing config
GET /api/v1/voice/emergency-routing-config112. Update the emergency-routing config
PUT /api/v1/voice/emergency-routing-config113. Attach the E911 address to a number
GET /api/v1/voice/emergency-location/{numberId}114. Attach an emergency location record
POST /api/v1/voice/emergency-location115. Resolve whether an address is PSAP-usable
GET /api/v1/voice/emergency-location/{numberId}/resolve116. Read the emergency callback (PSAP) binding
GET /api/v1/voice/psap-callback/{numberId}117. Resolve whether the PSAP binding is usable
GET /api/v1/voice/psap-callback/{numberId}/resolve118. Attach a PSAP callback binding
POST /api/v1/voice/psap-callback119. Read the per-queue comparison
GET /api/v1/voice/queues/comparison120. Read the wallboard hub config
GET /api/v1/voice/supervisor/wallboard-hubError shapes across these operations
Every operation above answers failures in the same{ error, meta }
envelope with a stable error.code. The five statuses worth branching on:
The general envelope rules (the four shapes, sandbox keys, tagged request
ids) live on How to read a worked sample, and
the full code catalog sits on the
Errors matrix.