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.
to
string
required
Destination phone number in E.164 format
from
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.
direction
string
default:"outbound"
Call direction: outbound or inbound.
agent_id
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).
record
boolean
default:"false"
Whether to record the call
amd
boolean
default:"false"
Request carrier-side Answering Machine Detection for the outbound call.
campaignId
string
Campaign ID for spend-cap tracking. Recorded on the call log for attribution.
orgTimezone
string
IANA timezone (e.g. America/New_York) used for the TCPA quiet-hours check on outbound calls.
metadata
object
Arbitrary key-value pairs attached to the call
Per-call webhooks, IVR menus, and TTS prompts are not set on this request. Configure them on the Application or IVR Flow bound to the number — there are no per-call webhook_url, ivr_id, or max_duration overrides. Fields outside the list above are silently ignored by the server.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "to": "+14155552671",
  "from": "+12025551234",
  "agent_id": "agt_abc123",
  "record": true,
  "amd": true
}'
Node.js
import { Orbit } from '@devotel/orbit-sdk'

const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY! })

const call = await orbit.voice.calls.create({
  to: '+14155552671',
  from: '+12025551234',
  agent_id: 'agt_abc123',
  record: true,
  amd: true,
})
console.log(call.data.id)
Python
import os, requests

headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls", headers=headers, json={
  "to": "+14155552671",
  "from": "+12025551234",
  "agent_id": "agt_abc123",
  "record": True,
  "amd": True
})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calls", bytes.NewBuffer([]byte(`{
  "to": "+14155552671",
  "from": "+12025551234",
  "agent_id": "agt_abc123",
  "record": true,
  "amd": true
}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
{
  "to": "+14155552671",
  "from": "+12025551234",
  "agent_id": "agt_abc123",
  "record": true,
  "amd": true
}
JSON);
echo curl_exec($ch);
{
  "data": {
    "id": "call_abc123",
    "to": "+14155552671",
    "from": "+12025551234",
    "agent_id": "agt_abc123",
    "status": "initiating",
    "record": true,
    "created_at": "2026-03-08T12:00:00Z"
  },
  "meta": {
    "request_id": "req_call_001",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

List Calls

GET /api/v1/voice/calls Retrieve call logs with cursor-based pagination and optional filters.
cursor
string
Cursor for pagination
limit
integer
default:"20"
Results per page (max 100)
status
string
Filter by status: initiating, ringing, in_progress, completed, failed
from
string
Filter by caller number
to
string
Filter by destination number
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/calls?status=completed&limit=20" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
import { Orbit } from '@devotel/orbit-sdk'

const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY! })

const calls = await orbit.voice.calls.list({ status: 'completed', limit: 20 })
console.log(calls.data)
Python
import os, requests

headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/calls", headers=headers,
                 params={"status": "completed", "limit": 20})
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/voice/calls?status=completed&limit=20", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls?status=completed&limit=20');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
]);
echo curl_exec($ch);

Get Call Details

GET /api/v1/voice/calls/{id} Retrieve full details of a call including status, duration, recording URL, and agent interaction summary.
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
const call = await orbit.voice.calls.get('call_abc123')
console.log(call.data)
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

Hang Up a Call

POST /api/v1/voice/calls/{id}/hangup Terminate an active call. AI agents receive a graceful shutdown signal before disconnection.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/hangup" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.calls.hangup('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/hangup", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/hangup", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/hangup');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/accept" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.calls.accept('call_abc123')
// or: await orbit.voice.calls.decline('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/accept", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/accept", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/accept');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/mute" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.calls.mute('call_abc123')
await orbit.voice.calls.unmute('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/mute", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/mute", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/mute');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/hold" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.calls.hold('call_abc123')
await orbit.voice.calls.unhold('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/hold", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/hold", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/hold');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

Send DTMF Tones

POST /api/v1/voice/calls/{id}/dtmf
digits
string
required
String of DTMF digits to send (09, *, #, AD). Plays at standard 100ms / 100ms gap.
Use to navigate carrier IVRs from a programmatic outbound call (PIN entry, account confirmations, etc.).
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/dtmf" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "digits": "1234#" }'
Node.js
await orbit.voice.calls.sendDTMF('call_abc123', { digits: '1234#' })
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/dtmf",
                  headers=headers, json={"digits": "1234#"})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/dtmf",
		bytes.NewBuffer([]byte(`{"digits":"1234#"}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/dtmf');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"digits":"1234#"}');
echo curl_exec($ch);

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.
to
string
required
Destination phone number, agent ID (agt_*), or extension (ext_*).
caller_id
string
Override the From number presented to the destination. Defaults to the original caller’s number.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/transfer" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+14155557890", "caller_id": "+12025551234" }'
Node.js
await orbit.voice.calls.transfer('call_abc123', {
  to: '+14155557890',
  caller_id: '+12025551234',
})
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/transfer",
                  headers=headers, json={"to": "+14155557890", "caller_id": "+12025551234"})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/transfer",
		bytes.NewBuffer([]byte(`{"to":"+14155557890","caller_id":"+12025551234"}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/transfer');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"to":"+14155557890","caller_id":"+12025551234"}');
echo curl_exec($ch);

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.
to
string
required
Destination for the warm-transfer consult leg.
whisper_message
string
Optional TTS message played to the consult party before audio bridges (e.g. “Customer Jane Doe, escalation tier 2”).
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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/warm-transfer" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+14155557890", "whisper_message": "Customer Jane Doe, escalation tier 2" }'
Node.js
await orbit.voice.calls.warmTransfer('call_abc123', {
  to: '+14155557890',
  whisper_message: 'Customer Jane Doe, escalation tier 2',
})
// later:
await orbit.voice.calls.warmTransferComplete('call_abc123')
// or to abort:
await orbit.voice.calls.warmTransferCancel('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/warm-transfer",
                  headers=headers,
                  json={"to": "+14155557890", "whisper_message": "Customer Jane Doe, escalation tier 2"})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/warm-transfer",
		bytes.NewBuffer([]byte(`{"to":"+14155557890","whisper_message":"Customer Jane Doe, escalation tier 2"}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/warm-transfer');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS,
  '{"to":"+14155557890","whisper_message":"Customer Jane Doe, escalation tier 2"}');
echo curl_exec($ch);

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 24 hours 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.
dual_channel
boolean
default:"true"
Record agent + caller on separate audio channels (left/right). Single-channel mixes both.
trim
boolean
default:"false"
Trim leading/trailing silence from the saved file.
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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/recording/start" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "dual_channel": true, "trim": false }'
Node.js
await orbit.voice.recordings.start('call_abc123', { dual_channel: true, trim: false })
await orbit.voice.recordings.stop('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/recording/start",
                  headers=headers, json={"dual_channel": True, "trim": False})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/recording/start",
		bytes.NewBuffer([]byte(`{"dual_channel":true,"trim":false}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/recording/start');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"dual_channel":true,"trim":false}');
echo curl_exec($ch);

Retrieve a Call Recording

GET /api/v1/voice/calls/{id}/recording Returns the recording metadata + a 24h-expiring signed URL. If the call is still being recorded, status is in_progress and signed_url is null.
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/recording" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
const rec = await orbit.voice.recordings.get('call_abc123')
console.log(rec.data.signed_url)
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/recording", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/recording", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/recording');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
  "data": {
    "call_id": "call_abc123",
    "status": "completed",
    "duration_seconds": 312,
    "format": "mp3",
    "channels": 2,
    "size_bytes": 4980000,
    "signed_url": "https://storage.devotel.io/recordings/call_abc123.mp3?Expires=1735689600&Signature=...",
    "signed_url_expires_at": "2026-03-09T12:00:00Z",
    "created_at": "2026-03-08T11:55:48Z"
  },
  "meta": {
    "request_id": "req_rec_001",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/recording/start" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.conferences.startRecording('conf_abc123')
await orbit.voice.conferences.stopRecording('conf_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/recording/start", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/recording/start", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/recording/start');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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
name
string
required
A descriptive name for the trunk
termination_uri
string
required
SIP URI for outbound call routing (e.g., sip:trunk.carrier.com:5060)
origination_uris
string[]
SIP URIs that Orbit should accept inbound calls from
auth_username
string
SIP authentication username
auth_password
string
SIP authentication password
codecs
string[]
default:"[\"PCMU\", \"PCMA\", \"OPUS\"]"
Preferred audio codecs in priority order
encryption
string
default:"tls_srtp"
Transport encryption: tls_srtp, tls, or none
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/sip-trunks" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Primary Carrier",
  "termination_uri": "sip:trunk.carrier.com:5060",
  "origination_uris": ["sip:orbit-gw.devotel.io"],
  "auth_username": "orbit_user",
  "auth_password": "secure_password",
  "codecs": ["OPUS", "PCMU"],
  "encryption": "tls_srtp"
}'
Node.js
import { Orbit } from '@devotel/orbit-sdk'

const orbit = new Orbit({ apiKey: process.env.ORBIT_API_KEY! })

const trunk = await orbit.voice.sipTrunks.create({
  name: 'Primary Carrier',
  termination_uri: 'sip:trunk.carrier.com:5060',
  origination_uris: ['sip:orbit-gw.devotel.io'],
  auth_username: 'orbit_user',
  auth_password: 'secure_password',
  codecs: ['OPUS', 'PCMU'],
  encryption: 'tls_srtp',
})
console.log(trunk.data.id)
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/sip-trunks", headers=headers, json={
  "name": "Primary Carrier",
  "termination_uri": "sip:trunk.carrier.com:5060",
  "origination_uris": ["sip:orbit-gw.devotel.io"],
  "auth_username": "orbit_user",
  "auth_password": "secure_password",
  "codecs": ["OPUS", "PCMU"],
  "encryption": "tls_srtp"
})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/sip-trunks", bytes.NewBuffer([]byte(`{
  "name": "Primary Carrier",
  "termination_uri": "sip:trunk.carrier.com:5060",
  "origination_uris": ["sip:orbit-gw.devotel.io"],
  "auth_username": "orbit_user",
  "auth_password": "secure_password",
  "codecs": ["OPUS", "PCMU"],
  "encryption": "tls_srtp"
}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/sip-trunks');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
{
  "name": "Primary Carrier",
  "termination_uri": "sip:trunk.carrier.com:5060",
  "origination_uris": ["sip:orbit-gw.devotel.io"],
  "auth_username": "orbit_user",
  "auth_password": "secure_password",
  "codecs": ["OPUS", "PCMU"],
  "encryption": "tls_srtp"
}
JSON);
echo curl_exec($ch);
{
  "data": {
    "id": "trunk_abc123",
    "name": "Primary Carrier",
    "termination_uri": "sip:trunk.carrier.com:5060",
    "status": "active",
    "codecs": ["OPUS", "PCMU"],
    "encryption": "tls_srtp",
    "created_at": "2026-03-08T12:00:00Z"
  },
  "meta": {
    "request_id": "req_trunk_001",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

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}
cURL
# List
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/sip-trunks" \
  -H "X-API-Key: dv_live_sk_your_key_here"
# Get
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/sip-trunks/trunk_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here"
# Update
curl -X PUT "https://api.orbit.devotel.io/api/v1/voice/sip-trunks/trunk_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Primary Carrier (renamed)" }'
# Delete
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/sip-trunks/trunk_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.sipTrunks.list()
await orbit.voice.sipTrunks.get('trunk_abc123')
await orbit.voice.sipTrunks.update('trunk_abc123', { name: 'Primary Carrier (renamed)' })
await orbit.voice.sipTrunks.delete('trunk_abc123')
Python
import os, requests
h = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
requests.get("https://api.orbit.devotel.io/api/v1/voice/sip-trunks", headers=h)
requests.get("https://api.orbit.devotel.io/api/v1/voice/sip-trunks/trunk_abc123", headers=h)
requests.put("https://api.orbit.devotel.io/api/v1/voice/sip-trunks/trunk_abc123",
             headers={**h, "Content-Type": "application/json"},
             json={"name": "Primary Carrier (renamed)"})
requests.delete("https://api.orbit.devotel.io/api/v1/voice/sip-trunks/trunk_abc123", headers=h)
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	base := "https://api.orbit.devotel.io/api/v1/voice/sip-trunks"
	key := os.Getenv("ORBIT_API_KEY")
	for _, r := range []struct{ m, u string }{{"GET", base}, {"GET", base + "/trunk_abc123"}, {"DELETE", base + "/trunk_abc123"}} {
		req, _ := http.NewRequest(r.m, r.u, nil)
		req.Header.Set("X-API-Key", key)
		http.DefaultClient.Do(req)
	}
}
PHP
<?php
$key = getenv('ORBIT_API_KEY');
$base = 'https://api.orbit.devotel.io/api/v1/voice/sip-trunks';
foreach (['GET' => $base, 'DELETE' => "$base/trunk_abc123"] as $method => $url) {
  $ch = curl_init($url);
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_HTTPHEADER => ["X-API-Key: $key"],
  ]);
  echo curl_exec($ch);
}

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.
name
string
required
IVR flow name (1–200 characters).
definition
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.
active
boolean
default:"true"
Whether the flow is active.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/ivr-flows" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Main Menu",
  "active": true,
  "definition": {
    "nodes": [
      { "id": "start", "type": "ivrStart", "data": {} },
      { "id": "menu", "type": "menu", "data": {
          "ttsText": "Welcome to Acme Corp. Press 1 for sales, 2 for support, or 3 to leave a voicemail.",
          "language": "en-US",
          "timeout": 8,
          "menuOptions": [
            { "key": "1", "label": "Sales" },
            { "key": "2", "label": "Support" },
            { "key": "3", "label": "Voicemail" }
          ]
        }
      },
      { "id": "sales", "type": "transfer", "data": { "transferTo": "+14155550101" } },
      { "id": "support", "type": "transfer", "data": { "transferTo": "+14155550102" } },
      { "id": "voicemail", "type": "voicemail", "data": { "greeting": "Please leave a message after the tone." } }
    ],
    "edges": [
      { "id": "e0", "source": "start", "target": "menu" },
      { "id": "e1", "source": "menu", "target": "sales", "sourceHandle": "1" },
      { "id": "e2", "source": "menu", "target": "support", "sourceHandle": "2" },
      { "id": "e3", "source": "menu", "target": "voicemail", "sourceHandle": "3" }
    ]
  }
}'
Node.js
const ivr = await orbit.voice.ivr.create({
  name: 'Main Menu',
  active: true,
  definition: {
    nodes: [
      { id: 'start', type: 'ivrStart', data: {} },
      {
        id: 'menu',
        type: 'menu',
        data: {
          ttsText: 'Welcome to Acme Corp. Press 1 for sales, 2 for support, or 3 to leave a voicemail.',
          language: 'en-US',
          timeout: 8,
          menuOptions: [
            { key: '1', label: 'Sales' },
            { key: '2', label: 'Support' },
            { key: '3', label: 'Voicemail' },
          ],
        },
      },
      { id: 'sales', type: 'transfer', data: { transferTo: '+14155550101' } },
      { id: 'support', type: 'transfer', data: { transferTo: '+14155550102' } },
      { id: 'voicemail', type: 'voicemail', data: { greeting: 'Please leave a message after the tone.' } },
    ],
    edges: [
      { id: 'e0', source: 'start', target: 'menu' },
      { id: 'e1', source: 'menu', target: 'sales', sourceHandle: '1' },
      { id: 'e2', source: 'menu', target: 'support', sourceHandle: '2' },
      { id: 'e3', source: 'menu', target: 'voicemail', sourceHandle: '3' },
    ],
  },
})
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/ivr-flows", headers=headers, json={
  "name": "Main Menu",
  "active": True,
  "definition": {
    "nodes": [
      {"id": "start", "type": "ivrStart", "data": {}},
      {"id": "menu", "type": "menu", "data": {
        "ttsText": "Welcome to Acme Corp. Press 1 for sales, 2 for support, or 3 to leave a voicemail.",
        "language": "en-US",
        "timeout": 8,
        "menuOptions": [
          {"key": "1", "label": "Sales"},
          {"key": "2", "label": "Support"},
          {"key": "3", "label": "Voicemail"}
        ]
      }},
      {"id": "sales", "type": "transfer", "data": {"transferTo": "+14155550101"}},
      {"id": "support", "type": "transfer", "data": {"transferTo": "+14155550102"}},
      {"id": "voicemail", "type": "voicemail", "data": {"greeting": "Please leave a message after the tone."}}
    ],
    "edges": [
      {"id": "e0", "source": "start", "target": "menu"},
      {"id": "e1", "source": "menu", "target": "sales", "sourceHandle": "1"},
      {"id": "e2", "source": "menu", "target": "support", "sourceHandle": "2"},
      {"id": "e3", "source": "menu", "target": "voicemail", "sourceHandle": "3"}
    ]
  }
})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	body := []byte(`{
  "name": "Main Menu",
  "active": true,
  "definition": {
    "nodes": [
      {"id": "start", "type": "ivrStart", "data": {}},
      {"id": "menu", "type": "menu", "data": {
        "ttsText": "Welcome to Acme Corp. Press 1 for sales.",
        "timeout": 8,
        "menuOptions": [{"key": "1", "label": "Sales"}]
      }},
      {"id": "sales", "type": "transfer", "data": {"transferTo": "+14155550101"}}
    ],
    "edges": [
      {"id": "e0", "source": "start", "target": "menu"},
      {"id": "e1", "source": "menu", "target": "sales", "sourceHandle": "1"}
    ]
  }
}`)
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/ivr-flows", bytes.NewBuffer(body))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/ivr-flows');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  'name' => 'Main Menu',
  'active' => true,
  'definition' => [
    'nodes' => [
      ['id' => 'start', 'type' => 'ivrStart', 'data' => (object) []],
      ['id' => 'menu', 'type' => 'menu', 'data' => [
        'ttsText' => 'Welcome to Acme Corp. Press 1 for sales.',
        'timeout' => 8,
        'menuOptions' => [['key' => '1', 'label' => 'Sales']],
      ]],
      ['id' => 'sales', 'type' => 'transfer', 'data' => ['transferTo' => '+14155550101']],
    ],
    'edges' => [
      ['id' => 'e0', 'source' => 'start', 'target' => 'menu'],
      ['id' => 'e1', 'source' => 'menu', 'target' => 'sales', 'sourceHandle' => '1'],
    ],
  ],
]));
echo curl_exec($ch);
{
  "data": {
    "id": "ivr_abc123",
    "name": "Main Menu",
    "status": "active",
    "options_count": 3,
    "created_at": "2026-03-08T12:00:00Z"
  },
  "meta": {
    "request_id": "req_ivr_001",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

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}
cURL
# List
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/ivr-flows" \
  -H "X-API-Key: dv_live_sk_your_key_here"
# Update
curl -X PUT "https://api.orbit.devotel.io/api/v1/voice/ivr-flows/ivr_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Renamed Menu" }'
# Delete
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/ivr-flows/ivr_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.ivr.list()
await orbit.voice.ivr.update('ivr_abc123', { name: 'Renamed Menu' })
await orbit.voice.ivr.delete('ivr_abc123')
Python
import os, requests
h = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
requests.get("https://api.orbit.devotel.io/api/v1/voice/ivr-flows", headers=h)
requests.put("https://api.orbit.devotel.io/api/v1/voice/ivr-flows/ivr_abc123",
             headers={**h, "Content-Type": "application/json"}, json={"name": "Renamed Menu"})
requests.delete("https://api.orbit.devotel.io/api/v1/voice/ivr-flows/ivr_abc123", headers=h)
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/voice/ivr-flows", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/ivr-flows');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
status
string
Comma-separated status filter — in-progress, completed, failed, ended.
limit
integer
default:"50"
Page size (max 100).
cursor
string
Opaque pagination cursor returned in the previous response’s meta.cursor.
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/conferences?status=in-progress&limit=20" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
const conferences = await orbit.voice.conferences.list({
  status: 'in-progress',
  limit: 20,
})
for (const conf of conferences.data) {
  console.log(conf.id, conf.name, conf.status)
}
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/conferences",
                 headers=headers, params={"status": "in-progress", "limit": 20})
print(r.json())
Go
package main

import (
	"net/http"
	"net/url"
	"os"
)

func main() {
	u, _ := url.Parse("https://api.orbit.devotel.io/api/v1/voice/conferences")
	q := u.Query()
	q.Set("status", "in-progress")
	q.Set("limit", "20")
	u.RawQuery = q.Encode()
	req, _ := http.NewRequest("GET", u.String(), nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences?status=in-progress&limit=20');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
  "data": [
    {
      "id": "conf_abc1234567890abcdef1234567890abcd",
      "name": "Q1 Planning Call",
      "status": "in-progress",
      "caller_id": "+18005551234",
      "started_at": "2026-03-08T12:00:00Z",
      "ended_at": null,
      "duration_seconds": null,
      "max_participants": 25,
      "recording_url": null,
      "created_by": "usr_xyz",
      "created_at": "2026-03-08T12:00:00Z"
    }
  ],
  "meta": {
    "request_id": "req_conf_list_001",
    "cursor": null,
    "has_more": false,
    "timestamp": "2026-03-08T12:00:01Z"
  }
}

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.
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
const conf = await orbit.voice.conferences.get('conf_abc123')
console.log(conf.data.name, conf.data.participants.length)
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
  "data": {
    "id": "conf_abc1234567890abcdef1234567890abcd",
    "name": "Q1 Planning Call",
    "status": "in-progress",
    "caller_id": "+18005551234",
    "started_at": "2026-03-08T12:00:00Z",
    "ended_at": null,
    "duration_seconds": null,
    "max_participants": 25,
    "recording_url": null,
    "participants": [
      {
        "id": "cpart_abc",
        "phone_number": "+14155552671",
        "status": "answered",
        "leg_call_sid": "CA-…",
        "joined_at": "2026-03-08T12:00:14Z",
        "left_at": null,
        "is_host": true
      }
    ]
  },
  "meta": {
    "request_id": "req_conf_get_001",
    "timestamp": "2026-03-08T12:00:15Z"
  }
}

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).
window_days
integer
default:"30"
Window in days the rollup spans (1-365).
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/conferences/aggregate?window_days=7" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
const metrics = await orbit.voice.conferences.aggregate({ windowDays: 7 })
console.log(
  metrics.data.avg_duration_seconds,
  metrics.data.success_rate,
  metrics.data.peak_concurrent_participants,
)
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/conferences/aggregate",
                 headers=headers, params={"window_days": 7})
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/aggregate?window_days=7", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/aggregate?window_days=7');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
  "data": {
    "total_count": 142,
    "active_count": 3,
    "avg_duration_seconds": 1820,
    "success_rate": 0.9437,
    "peak_concurrent_participants": 18,
    "total_participants_window": 612,
    "window_days": 7
  },
  "meta": {
    "request_id": "req_conf_agg_001",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

Create Conference

POST /api/v1/voice/conferences
name
string
required
Conference name (max 200 chars).
participants
string[]
required
Seed participants to dial when the conference is created. Each entry must be E.164. 1-32 entries.
from
string
E.164 caller-id used for each seed-participant dial. Must be a voice-capable number owned by your organization.
record
boolean
default:"false"
Record every participant leg on creation. Subject to per-jurisdiction recording consent gates.
maxParticipants
integer
default:"8"
Cap on simultaneously-bridged legs (max 32).
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/conferences" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Q1 Planning Call",
  "participants": ["+14155552671", "+14155552672"],
  "from": "+18005551234",
  "record": true,
  "maxParticipants": 25
}'
Node.js
const conf = await orbit.voice.conferences.create({
  name: 'Q1 Planning Call',
  participants: ['+14155552671', '+14155552672'],
  from: '+18005551234',
  record: true,
  maxParticipants: 25,
})
console.log(conf.data.id)
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/conferences", headers=headers, json={
  "name": "Q1 Planning Call",
  "participants": ["+14155552671", "+14155552672"],
  "from": "+18005551234",
  "record": True,
  "maxParticipants": 25
})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/conferences", bytes.NewBuffer([]byte(`{
  "name": "Q1 Planning Call",
  "participants": ["+14155552671", "+14155552672"],
  "from": "+18005551234",
  "record": true,
  "maxParticipants": 25
}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  'name' => 'Q1 Planning Call',
  'participants' => ['+14155552671', '+14155552672'],
  'from' => '+18005551234',
  'record' => true,
  'maxParticipants' => 25,
]));
echo curl_exec($ch);
{
  "data": {
    "id": "conf_abc123",
    "name": "Q1 Planning Call",
    "status": "waiting",
    "pin": "482901",
    "dial_in_number": "+18005559999",
    "max_participants": 25,
    "record": true,
    "created_at": "2026-03-08T12:00:00Z"
  },
  "meta": {
    "request_id": "req_conf_001",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

Add Participant

POST /api/v1/voice/conferences/{id}/participants
number
string
required
Phone number to dial into the conference
muted
boolean
default:"false"
Join the participant in muted state
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "number": "+14155552671", "muted": false }'
Node.js
await orbit.voice.conferences.addParticipant('conf_abc123', {
  number: '+14155552671',
  muted: false,
})
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants",
                  headers=headers, json={"number": "+14155552671", "muted": False})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants",
		bytes.NewBuffer([]byte(`{"number":"+14155552671","muted":false}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"number":"+14155552671","muted":false}');
echo curl_exec($ch);

Remove Participant

DELETE /api/v1/voice/conferences/{id}/participants/{participantId}
cURL
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.conferences.removeParticipant('conf_abc123', 'part_xyz')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.delete("https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz",
                    headers=headers)
print(r.status_code)
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

End Conference

POST /api/v1/voice/conferences/{id}/end
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/end" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.conferences.end('conf_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/end", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/end", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/end');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

Voicemail

Manage voicemail boxes for receiving and transcribing voice messages when calls go unanswered.

Create Voicemail Box

POST /api/v1/voice/voicemail-boxes
name
string
required
Voicemail box name
greeting
string
Custom TTS greeting (default: “Please leave a message after the tone.”)
transcribe
boolean
default:"true"
Automatically transcribe voicemail messages using STT
max_duration
integer
default:"120"
Maximum voicemail recording duration in seconds
notification_email
string
Email to send voicemail notifications to
webhook_url
string
URL for voicemail event callbacks
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/voicemail-boxes" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "General Inbox",
  "greeting": "You have reached Acme Corp. We are currently unavailable. Please leave a message.",
  "transcribe": true,
  "notification_email": "team@acme.com"
}'
Node.js
const box = await orbit.voice.voicemail.create({
  name: 'General Inbox',
  greeting: 'You have reached Acme Corp. We are currently unavailable. Please leave a message.',
  transcribe: true,
  notification_email: 'team@acme.com',
})
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/voicemail", headers=headers, json={
  "name": "General Inbox",
  "greeting": "You have reached Acme Corp. We are currently unavailable. Please leave a message.",
  "transcribe": True,
  "notification_email": "team@acme.com"
})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/voicemail", bytes.NewBuffer([]byte(`{
  "name": "General Inbox",
  "transcribe": true,
  "notification_email": "team@acme.com"
}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/voicemail');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  'name' => 'General Inbox', 'transcribe' => true, 'notification_email' => 'team@acme.com',
]));
echo curl_exec($ch);
{
  "data": {
    "id": "vmail_abc123",
    "name": "General Inbox",
    "transcribe": true,
    "max_duration": 120,
    "messages_count": 0,
    "created_at": "2026-03-08T12:00:00Z"
  },
  "meta": {
    "request_id": "req_vmail_001",
    "timestamp": "2026-03-08T12:00:00Z"
  }
}

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.
cURL
# List
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/voicemails?status=unread&limit=20" \
  -H "X-API-Key: dv_live_sk_your_key_here"
# Get
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/voicemails/vmsg_001" \
  -H "X-API-Key: dv_live_sk_your_key_here"
# Mark read
curl -X PATCH "https://api.orbit.devotel.io/api/v1/voice/voicemails/vmsg_001/read" \
  -H "X-API-Key: dv_live_sk_your_key_here"
# Delete
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/voicemails/vmsg_001" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.voicemails.list({ status: 'unread', limit: 20 })
await orbit.voice.voicemails.get('vmsg_001')
await orbit.voice.voicemails.markRead('vmsg_001')
await orbit.voice.voicemails.delete('vmsg_001')
Python
import os, requests
h = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
requests.get("https://api.orbit.devotel.io/api/v1/voice/voicemails",
             headers=h, params={"status": "unread", "limit": 20})
requests.get("https://api.orbit.devotel.io/api/v1/voice/voicemails/vmsg_001", headers=h)
requests.patch("https://api.orbit.devotel.io/api/v1/voice/voicemails/vmsg_001/read", headers=h)
requests.delete("https://api.orbit.devotel.io/api/v1/voice/voicemails/vmsg_001", headers=h)
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/voice/voicemails?status=unread&limit=20", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/voicemails?status=unread&limit=20');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
  "data": [
    {
      "id": "vmsg_001",
      "voicemail_id": "vmail_abc123",
      "caller": "+14155552671",
      "duration_seconds": 35,
      "transcription": "Hi, this is Jane. I wanted to follow up on my order. Please call me back.",
      "recording_url": "https://storage.devotel.io/voicemail/vmsg_001.wav",
      "listened": false,
      "created_at": "2026-03-08T14:30:00Z"
    }
  ],
  "meta": {
    "request_id": "req_vmsg_001",
    "timestamp": "2026-03-08T15:00:00Z",
    "pagination": {
      "cursor": "cur_vmsg_abc",
      "has_more": false,
      "total": 1
    }
  }
}

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
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz/mute" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.conferences.muteParticipant('conf_abc123', 'part_xyz')
await orbit.voice.conferences.unmuteParticipant('conf_abc123', 'part_xyz')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post(
  "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz/mute",
  headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz/mute", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz/mute');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/mute-all" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.conferences.muteAll('conf_abc123')
await orbit.voice.conferences.unmuteAll('conf_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/mute-all", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/mute-all", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/mute-all');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

Hold / Unhold a Participant

POST /api/v1/voice/conferences/{id}/participants/{participantId}/hold POST /api/v1/voice/conferences/{id}/participants/{participantId}/unhold
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz/hold" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.conferences.holdParticipant('conf_abc123', 'part_xyz')
await orbit.voice.conferences.unholdParticipant('conf_abc123', 'part_xyz')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post(
  "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz/hold",
  headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz/hold", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/participants/part_xyz/hold');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/lock" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.conferences.lock('conf_abc123')
await orbit.voice.conferences.unlock('conf_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/lock", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/lock", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123/lock');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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).
cURL
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.conferences.end('conf_abc123')
await orbit.voice.conferences.delete('conf_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
requests.delete("https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123", headers=headers)
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE",
		"https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/conferences/conf_abc123');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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
name
string
required
Queue name (visible to supervisors in the wallboard).
strategy
string
default:"longest_idle"
Distribution strategy: longest_idle, round_robin, least_calls, random.
max_wait_seconds
integer
default:"300"
Maximum hold time before the call falls through to overflow_action.
overflow_action
string
default:"voicemail"
Action when max-wait expires: voicemail, transfer_number, hangup.
hold_music_url
string
Public URL of an MP3 played while waiting.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/queues" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Support Tier 1",
  "strategy": "longest_idle",
  "max_wait_seconds": 240,
  "overflow_action": "voicemail",
  "hold_music_url": "https://cdn.example.com/hold-music.mp3"
}'
Node.js
const q = await orbit.voice.queues.create({
  name: 'Support Tier 1',
  strategy: 'longest_idle',
  max_wait_seconds: 240,
  overflow_action: 'voicemail',
  hold_music_url: 'https://cdn.example.com/hold-music.mp3',
})
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/queues", headers=headers, json={
  "name": "Support Tier 1",
  "strategy": "longest_idle",
  "max_wait_seconds": 240,
  "overflow_action": "voicemail",
  "hold_music_url": "https://cdn.example.com/hold-music.mp3"
})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/queues", bytes.NewBuffer([]byte(`{
  "name": "Support Tier 1",
  "strategy": "longest_idle",
  "max_wait_seconds": 240,
  "overflow_action": "voicemail"
}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/queues');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  'name' => 'Support Tier 1', 'strategy' => 'longest_idle',
  'max_wait_seconds' => 240, 'overflow_action' => 'voicemail',
]));
echo curl_exec($ch);

List / Update / Delete

GET /api/v1/voice/queues PUT /api/v1/voice/queues/{id} DELETE /api/v1/voice/queues/{id}
cURL
# List
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/queues" \
  -H "X-API-Key: dv_live_sk_your_key_here"
# Update
curl -X PUT "https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "max_wait_seconds": 180 }'
# Delete
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.queues.list()
await orbit.voice.queues.update('queue_abc', { max_wait_seconds: 180 })
await orbit.voice.queues.delete('queue_abc')
Python
import os, requests
h = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
requests.get("https://api.orbit.devotel.io/api/v1/voice/queues", headers=h)
requests.put("https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc",
             headers={**h, "Content-Type": "application/json"}, json={"max_wait_seconds": 180})
requests.delete("https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc", headers=h)
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/voice/queues", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/queues');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

Enqueue a Call

POST /api/v1/voice/queues/{id}/enqueue
call_id
string
required
Call ID to add to the queue.
priority
integer
default:"0"
Higher priority routes ahead of lower. VIP customers, escalations.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc/enqueue" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "call_id": "call_abc123", "priority": 5 }'
Node.js
await orbit.voice.queues.enqueue('queue_abc', { call_id: 'call_abc123', priority: 5 })
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc/enqueue",
                  headers=headers, json={"call_id": "call_abc123", "priority": 5})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc/enqueue",
		bytes.NewBuffer([]byte(`{"call_id":"call_abc123","priority":5}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc/enqueue');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"call_id":"call_abc123","priority":5}');
echo curl_exec($ch);

Queue Stats

GET /api/v1/voice/queues/{id}/stats Returns live counts: waiting, connected, abandoned_24h, avg_wait_seconds_24h, plus per-agent presence.
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc/stats" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
const stats = await orbit.voice.queues.stats('queue_abc')
console.log(stats.data.waiting)
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc/stats", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET",
		"https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc/stats", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/queues/queue_abc/stats');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

Set Agent Status

POST /api/v1/voice/agents/{id}/status
status
string
required
Agent presence: available, busy, wrap_up, offline.
Drives queue routing — available agents are eligible for the next call.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/agents/agt_jane/status" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "status": "available" }'
Node.js
await orbit.voice.agents.setStatus('agt_jane', { status: 'available' })
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/agents/agt_jane/status",
                  headers=headers, json={"status": "available"})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/agents/agt_jane/status",
		bytes.NewBuffer([]byte(`{"status":"available"}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/agents/agt_jane/status');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"status":"available"}');
echo curl_exec($ch);

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
name
string
required
Unique ring group name within the organization (max 100 chars).
strategy
string
default:"simultaneous"
Ring strategy. Implemented today: simultaneous, sequential, round_robin, longest_idle. fewest_calls was removed from the accepted set in 2026-05-24 (SCAN-UCAAS-005) — the per-username call counter the strategy required was never implemented, and leaving it accepted caused silent fall-back to simultaneous ring. Any pre-existing ring group whose strategy was fewest_calls has been backfilled to simultaneous.
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.
ringTimeoutSec
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 — REMOVED 2026-05-24 (SCAN-UCAAS-005). The verb-builder never implemented the per-username call counter the strategy required, and operators saving fewest_calls got a silent fallback to simultaneous ring. Sending strategy: "fewest_calls" now returns 422.
Wiring a ring group to an inbound number: save the route via PUT /v1/numbers/{e164}/routing with type: "ring_group", config: {}, and ring_group_id: "<group_id>" — the group identity lives on the route row, not in config.
cURL
# Create
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/ring-groups" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Sales All-Rings",
  "strategy": "simultaneous",
  "ringTimeoutSec": 30,
  "members": [
    { "kind": "sip_username", "value": "alice" },
    { "kind": "sip_username", "value": "bob" },
    { "kind": "pstn",         "value": "+14155557890" }
  ]
}'
# List / Get / Delete
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/ring-groups" -H "X-API-Key: $K"
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/ring-groups/rg_abc" -H "X-API-Key: $K"
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/ring-groups/rg_abc" -H "X-API-Key: $K"
Node.js
await orbit.voice.ringGroups.create({
  name: 'Sales All-Rings',
  strategy: 'simultaneous',
  ringTimeoutSec: 30,
  members: [
    { kind: 'sip_username', value: 'alice' },
    { kind: 'sip_username', value: 'bob' },
    { kind: 'pstn',         value: '+14155557890' },
  ],
})
await orbit.voice.ringGroups.list()
await orbit.voice.ringGroups.get('rg_abc')
await orbit.voice.ringGroups.delete('rg_abc')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/ring-groups", headers=headers, json={
  "name": "Sales All-Rings",
  "strategy": "simultaneous",
  "ringTimeoutSec": 30,
  "members": [
    {"kind": "sip_username", "value": "alice"},
    {"kind": "pstn",         "value": "+14155557890"}
  ]
})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	body := []byte(`{
  "name": "Sales All-Rings",
  "strategy": "simultaneous",
  "ringTimeoutSec": 30,
  "members": [{"kind":"sip_username","value":"alice"}]
}`)
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/ring-groups", bytes.NewBuffer(body))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/ring-groups');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  'name' => 'Sales All-Rings',
  'strategy' => 'simultaneous',
  'ringTimeoutSec' => 30,
  'members' => [['kind' => 'sip_username', 'value' => 'alice']],
]));
echo curl_exec($ch);

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 /v1/voice/sip-credentials/* (see SIP Credentials).

Calendars

Per-extension business-hours calendars route inbound calls to voicemail / a fallback number outside of working hours. Calendars carry an IANA timezone and a list of weekday windows.

Create a Calendar

POST /api/v1/voice/calendars
name
string
required
Calendar name.
timezone
string
required
IANA timezone, e.g. Europe/Istanbul.
weekday_windows
object[]
required
Array of { weekday: 0-6, start: "HH:MM", end: "HH:MM" }. weekday follows ISO-8601 (1=Mon, 7=Sun).
holidays
string[]
Array of ISO-8601 dates (YYYY-MM-DD) treated as off-hours.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calendars" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Istanbul Office Hours",
  "timezone": "Europe/Istanbul",
  "weekday_windows": [
    { "weekday": 1, "start": "09:00", "end": "18:00" },
    { "weekday": 2, "start": "09:00", "end": "18:00" }
  ],
  "holidays": ["2026-04-23", "2026-05-01"]
}'
Node.js
await orbit.voice.calendars.create({
  name: 'Istanbul Office Hours',
  timezone: 'Europe/Istanbul',
  weekday_windows: [
    { weekday: 1, start: '09:00', end: '18:00' },
    { weekday: 2, start: '09:00', end: '18:00' },
  ],
  holidays: ['2026-04-23', '2026-05-01'],
})
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calendars", headers=headers, json={
  "name": "Istanbul Office Hours",
  "timezone": "Europe/Istanbul",
  "weekday_windows": [{"weekday": 1, "start": "09:00", "end": "18:00"}],
  "holidays": ["2026-04-23"]
})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	body := []byte(`{
  "name": "Istanbul Office Hours",
  "timezone": "Europe/Istanbul",
  "weekday_windows": [{"weekday":1,"start":"09:00","end":"18:00"}],
  "holidays": ["2026-04-23"]
}`)
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/calendars", bytes.NewBuffer(body))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calendars');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  'name' => 'Istanbul Office Hours',
  'timezone' => 'Europe/Istanbul',
  'weekday_windows' => [['weekday' => 1, 'start' => '09:00', 'end' => '18:00']],
  'holidays' => ['2026-04-23'],
]));
echo curl_exec($ch);

List / Get / Delete

GET /api/v1/voice/calendars GET /api/v1/voice/calendars/{id} DELETE /api/v1/voice/calendars/{id}
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/calendars" -H "X-API-Key: $K"
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/calendars/cal_abc" -H "X-API-Key: $K"
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/calendars/cal_abc" -H "X-API-Key: $K"
Node.js
await orbit.voice.calendars.list()
await orbit.voice.calendars.get('cal_abc')
await orbit.voice.calendars.delete('cal_abc')
Python
import os, requests
h = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
requests.get("https://api.orbit.devotel.io/api/v1/voice/calendars", headers=h)
requests.get("https://api.orbit.devotel.io/api/v1/voice/calendars/cal_abc", headers=h)
requests.delete("https://api.orbit.devotel.io/api/v1/voice/calendars/cal_abc", headers=h)
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/voice/calendars", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calendars');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/listen" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.supervisor.listen('call_abc123')
await orbit.voice.supervisor.unlisten('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/listen", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/listen", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/listen');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/whisper" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.supervisor.whisper('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/whisper", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/whisper", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/whisper');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

Barge In

POST /api/v1/voice/calls/{id}/barge Add the supervisor as a third audible participant. Both call legs hear the supervisor.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/barge" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.supervisor.barge('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/barge", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/barge", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/barge');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/monitoring" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
const snap = await orbit.voice.monitoring.snapshot()
console.log(snap.data.active_calls)
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/monitoring", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/voice/monitoring", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/monitoring');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
  "data": {
    "active_calls": 14,
    "queued_calls": 3,
    "available_agents": 7,
    "busy_agents": 5,
    "calls": [
      {
        "id": "call_abc123",
        "from": "+14155552671",
        "to": "+18005551234",
        "agent_id": "agt_support_jane",
        "duration_seconds": 87,
        "queue_id": "queue_support",
        "started_at": "2026-03-08T11:58:33Z"
      }
    ]
  }
}

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:
{ "speaker": "agent" | "caller", "text": "...", "ts": "ISO-8601" }
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.
cURL
# SSE stream
curl -N -X GET "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/transcript/stream" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Accept: text/event-stream"
# Final post-call transcript
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/transcript" \
  -H "X-API-Key: dv_live_sk_your_key_here"
# Submit audio URL for async transcription
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/transcribe" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "audio_url": "https://example.com/audio.mp3", "language": "en-US" }'
Node.js
// Final transcript
const tx = await orbit.voice.transcripts.get('call_abc123')
console.log(tx.data.utterances)
// Async submit
const job = await orbit.voice.transcripts.submit({
  audio_url: 'https://example.com/audio.mp3',
  language: 'en-US',
})
console.log(job.data.transcript_id)
Python
import os, requests
h = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
requests.get("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/transcript", headers=h)
requests.post("https://api.orbit.devotel.io/api/v1/voice/transcribe",
              headers={**h, "Content-Type": "application/json"},
              json={"audio_url": "https://example.com/audio.mp3", "language": "en-US"})
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/transcribe",
		bytes.NewBuffer([]byte(`{"audio_url":"https://example.com/audio.mp3","language":"en-US"}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/transcribe');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"audio_url":"https://example.com/audio.mp3","language":"en-US"}');
echo curl_exec($ch);

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
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/intelligence" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
const intel = await orbit.voice.intelligence.getForCall('call_abc123')
console.log(intel.data.summary)
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/intelligence", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET",
		"https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/intelligence", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/intelligence');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
{
  "data": {
    "call_id": "call_abc123",
    "summary": "Customer called to dispute a duplicate charge dated 2026-03-05 for $89.00. Agent confirmed the charge was a billing system retry artifact, refunded immediately, sent confirmation email.",
    "sentiment": {
      "overall": "neutral_to_positive",
      "trajectory": "improved",
      "agent_score": 0.82,
      "caller_score": 0.65
    },
    "key_moments": [
      { "ts": 12, "type": "intent_detected", "label": "billing_dispute" },
      { "ts": 47, "type": "frustration_peak", "score": 0.78 },
      { "ts": 134, "type": "resolution_offered" },
      { "ts": 189, "type": "satisfaction_signal", "score": 0.85 }
    ],
    "action_items": [
      "Send refund confirmation email by EOD",
      "Flag billing system for duplicate-charge investigation"
    ],
    "customer_effort_score": 2.4,
    "agent_compliance_flags": [],
    "language": "en-US",
    "processed_at": "2026-03-08T12:08:14Z"
  }
}

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.
cURL
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/sentiment" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.intelligence.runSentiment('call_abc123')
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/sentiment", headers=headers)
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/sentiment", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/calls/call_abc123/sentiment');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/intelligence/calls?sentiment=negative&limit=50" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.intelligence.listCalls({ sentiment: 'negative', limit: 50 })
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/intelligence/calls",
                 headers=headers, params={"sentiment": "negative", "limit": 50})
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET",
		"https://api.orbit.devotel.io/api/v1/voice/intelligence/calls?sentiment=negative&limit=50", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/intelligence/calls?sentiment=negative&limit=50');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);
GET /api/v1/voice/intelligence/trends Aggregated time-series of sentiment + effort score, bucketed by day / week. Drives the Voice → Intelligence → Trends dashboard.
from
string
ISO-8601 start of the trend window. Defaults to 30 days ago.
to
string
ISO-8601 end of the trend window. Defaults to now.
bucket
string
default:"day"
Aggregation bucket: hour, day, week.
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/intelligence/trends?from=2026-02-01&to=2026-03-08&bucket=day" \
  -H "X-API-Key: dv_live_sk_your_key_here"
Node.js
await orbit.voice.intelligence.trends({
  from: '2026-02-01',
  to: '2026-03-08',
  bucket: 'day',
})
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
r = requests.get("https://api.orbit.devotel.io/api/v1/voice/intelligence/trends", headers=headers,
                 params={"from": "2026-02-01", "to": "2026-03-08", "bucket": "day"})
print(r.json())
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET",
		"https://api.orbit.devotel.io/api/v1/voice/intelligence/trends?from=2026-02-01&to=2026-03-08&bucket=day", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/intelligence/trends?from=2026-02-01&to=2026-03-08&bucket=day');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/quality/aggregates" -H "X-API-Key: $K"
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/quality/carriers" -H "X-API-Key: $K"
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/quality/worst-calls?limit=10" -H "X-API-Key: $K"
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/vaqi?call_id=call_abc123" -H "X-API-Key: $K"
Node.js
await orbit.voice.quality.aggregates()
await orbit.voice.quality.carriers()
await orbit.voice.quality.worstCalls({ limit: 10 })
await orbit.voice.quality.vaqi({ call_id: 'call_abc123' })
Python
import os, requests
h = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
requests.get("https://api.orbit.devotel.io/api/v1/voice/quality/aggregates", headers=h)
requests.get("https://api.orbit.devotel.io/api/v1/voice/quality/carriers", headers=h)
requests.get("https://api.orbit.devotel.io/api/v1/voice/quality/worst-calls", headers=h, params={"limit": 10})
requests.get("https://api.orbit.devotel.io/api/v1/voice/vaqi", headers=h, params={"call_id": "call_abc123"})
Go
package main

import (
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.orbit.devotel.io/api/v1/voice/quality/aggregates", nil)
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/quality/aggregates');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['X-API-Key: ' . getenv('ORBIT_API_KEY')]);
echo curl_exec($ch);

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.
cURL
# Mint a JWT
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/softphone/token" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "extension_id": "ext_alice", "ttl_seconds": 60 }'
# Server-initiated outbound dial
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/softphone/dial" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+14155557890", "extension_id": "ext_alice" }'
Node.js
const jwt = await orbit.voice.softphone.mintToken({
  extension_id: 'ext_alice',
  ttl_seconds: 60,
})
await orbit.voice.softphone.dial({ to: '+14155557890', extension_id: 'ext_alice' })
Python
import os, requests
headers = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/softphone/token", headers=headers,
                  json={"extension_id": "ext_alice", "ttl_seconds": 60})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("POST",
		"https://api.orbit.devotel.io/api/v1/voice/softphone/token",
		bytes.NewBuffer([]byte(`{"extension_id":"ext_alice","ttl_seconds":60}`)))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/softphone/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"extension_id":"ext_alice","ttl_seconds":60}');
echo curl_exec($ch);

Voice Clones

Custom-trained TTS voices for AI agents. Voices are tenant-scoped and require an upfront audio-sample upload — at least 30 seconds of clean speech from a single speaker, ≥ 16 kHz mono WAV / MP3 / FLAC.

Create / List / Delete

POST /api/v1/voice/clones 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.
cURL
# Create
curl -X POST "https://api.orbit.devotel.io/api/v1/voice/clones" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Brand Voice — EN",
  "language": "en-US",
  "sample_url": "https://storage.example.com/sample.wav"
}'
# List
curl -X GET "https://api.orbit.devotel.io/api/v1/voice/clones" -H "X-API-Key: $K"
# Delete
curl -X DELETE "https://api.orbit.devotel.io/api/v1/voice/clones/clone_abc" -H "X-API-Key: $K"
Node.js
await orbit.voice.clones.create({
  name: 'Brand Voice — EN',
  language: 'en-US',
  sample_url: 'https://storage.example.com/sample.wav',
})
await orbit.voice.clones.list()
await orbit.voice.clones.delete('clone_abc')
Python
import os, requests
h = {"X-API-Key": os.environ["ORBIT_API_KEY"], "Content-Type": "application/json"}
r = requests.post("https://api.orbit.devotel.io/api/v1/voice/clones", headers=h, json={
  "name": "Brand Voice — EN",
  "language": "en-US",
  "sample_url": "https://storage.example.com/sample.wav"
})
print(r.json())
Go
package main

import (
	"bytes"
	"net/http"
	"os"
)

func main() {
	body := []byte(`{
  "name": "Brand Voice — EN",
  "language": "en-US",
  "sample_url": "https://storage.example.com/sample.wav"
}`)
	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/voice/clones", bytes.NewBuffer(body))
	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	http.DefaultClient.Do(req)
}
PHP
<?php
$ch = curl_init('https://api.orbit.devotel.io/api/v1/voice/clones');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
  'X-API-Key: ' . getenv('ORBIT_API_KEY'),
  'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
  'name' => 'Brand Voice — EN',
  'language' => 'en-US',
  'sample_url' => 'https://storage.example.com/sample.wav',
]));
echo curl_exec($ch);

Call Statuses

StatusDescription
initiatingCall is being set up
ringingDestination phone is ringing
in_progressCall is connected and active
completedCall ended normally
failedCall could not be connected
busyDestination returned busy signal
no_answerDestination did not answer within timeout
cancelledCall was cancelled before connection