Documentation Index
Fetch the complete documentation index at: https://orbit-docs.devotel.io/llms.txt
Use this file to discover all available pages before exploring further.
Node.js SDK
The official Devotel Node.js SDK provides a fully typed client for the Devotel API. Built with TypeScript, it supports all Devotel services — messaging, voice, agents, flows, numbers, and verify.
Installation
npm install @devotel/orbit-sdk
Or with other package managers:
pnpm add @devotel/orbit-sdk
yarn add @devotel/orbit-sdk
Quick Start
import { Devotel } from '@devotel/orbit-sdk';
const orbit = new Devotel({
apiKey: 'dv_live_sk_your_key_here',
});
// Send an SMS
const message = await orbit.messages.send({
channel: 'sms',
to: '+14155552671',
body: 'Hello from Orbit!',
});
console.log(message.data.message_id); // msg_abc123
Messaging
// Send WhatsApp template
const waMessage = await orbit.messages.send({
channel: 'whatsapp',
to: '+14155552671',
type: 'template',
template: {
name: 'order_confirmation',
language: 'en',
components: [
{ type: 'body', parameters: [{ type: 'text', text: 'ORD-12345' }] },
],
},
});
// Send Email
const email = await orbit.messages.send({
channel: 'email',
to: 'user@example.com',
from: 'hello@yourdomain.com',
subject: 'Welcome!',
html: '<h1>Welcome to Acme</h1>',
});
Voice
The Voice resource exposes call control, recordings, transcripts, conferences, IVR, dialer campaigns, SIP trunks, and analytics. See Voice operations below for the full surface.
// Make an outbound call
const call = await orbit.voice.calls.create({
to: '+14155552671',
from: '+18005551234',
webhookUrl: 'https://yourapp.com/webhooks/voice',
});
// Connect to an AI agent
const agentCall = await orbit.voice.calls.create({
to: '+14155552671',
from: '+18005551234',
agentId: 'agent_support_bot',
});
// Retrieve call details
const callDetails = await orbit.voice.calls.get(call.data.id);
Voice operations
A complete tour of the live call-control surface. Every snippet matches the actual SDK API in packages/sdk-node/src/resources/voice.ts.
Make a call
const call = await orbit.voice.calls.create({
to: '+14155552671',
from: '+18005551234',
agentId: 'agent_support_bot', // route to an AI voice agent
// ivrFlowId: 'flow_main_menu', // …or to an IVR flow
record: true,
machineDetection: 'enable', // 'enable' | 'detect-only' | 'detect-beep'
webhookUrl: 'https://yourapp.com/webhooks/voice',
metadata: { orderId: 'ord_42' },
});
console.log(call.data.id); // call_abc123
Get call details
const detail = await orbit.voice.calls.get('call_abc123');
console.log(detail.data.status); // 'in-progress' | 'completed' | 'failed' | …
console.log(detail.data.duration); // seconds
You can also list with pagination + filters:
const page = await orbit.voice.calls.list({
direction: 'outbound',
status: 'completed',
limit: 50,
});
for (const c of page.data) console.log(c.id, c.duration);
Hang up a call
await orbit.voice.calls.hangup('call_abc123');
Transfer a call
Both cold (blind) and warm (consult-first) transfers are supported on the same transfer() method:
// Cold / blind transfer — REFER straight to the new destination.
await orbit.voice.calls.transfer('call_abc123', {
to: '+14155552500',
mode: 'blind',
});
// Warm transfer — consult the agent first, then bridge. The dedicated
// `warmTransfer()` helper accepts an optional AI context whisper that
// reads the call summary to the receiving agent before the bridge.
await orbit.voice.calls.warmTransfer('call_abc123', {
destination: '+14155552500',
from: '+18005551234',
contextWhisper: true,
context: 'Customer is calling about order #ord_42, refund request.',
});
Conference call
// 1. Create the conference bridge.
const conf = await orbit.voice.conferences.create({
name: 'support-room-42',
region: 'us-east',
});
// 2. Add a live call leg by call_id.
await orbit.voice.conferences.addParticipant(conf.data.id, {
callId: 'call_abc123',
muted: false,
});
// 3. …or dial out and add a new participant by phone number.
await orbit.voice.conferences.addParticipant(conf.data.id, {
phoneNumber: '+14155552671',
coaching: true, // whisper mode — they hear but aren't heard
});
Record a call
Recording is started by passing record: true at call creation; pause/resume happens via call-control endpoints. After the call ends, fetch the recording metadata and a signed download URL:
// Start recording on call creation:
const call = await orbit.voice.calls.create({
to: '+14155552671',
from: '+18005551234',
record: true,
});
// After the call ends, list recordings for that call:
const recordings = await orbit.voice.recordings.list({ callId: call.data.id });
const recording = recordings.data[0];
// Stop recording is implicit on call hangup; to delete after retrieval:
await orbit.voice.recordings.delete(recording.id);
Listen to a recording
const recording = await orbit.voice.recordings.get('rec_xyz789');
const signed = await orbit.voice.recordings.getDownloadUrl(recording.data.id);
console.log(signed.data.url); // short-lived signed S3 URL — expires in ~15 min
// Download in Node:
const audio = await fetch(signed.data.url).then((r) => r.arrayBuffer());
require('node:fs').writeFileSync('/tmp/call.wav', Buffer.from(audio));
Live transcript stream (Node-only)
const handle = orbit.voice.calls.streamTranscript('call_abc123', {
onPartial: (frame) => console.log('partial:', frame.speaker, frame.text),
onFinal: (frame) => console.log('FINAL:', frame.speaker, frame.text),
onStatus: (s) => console.log('call status:', s.status),
onError: (e) => console.error(e),
onEnd: (reason) => console.log('stream ended:', reason),
});
// Tear down when you're done:
// handle.close();
Voice webhook handling
Verify the signature with the shared SDK helper, then dispatch on event.type:
import express from 'express';
import { verifyWebhookSignature } from '@devotel/orbit-sdk';
const app = express();
app.use(express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf; } }));
app.post('/webhooks/voice', (req, res) => {
const ok = verifyWebhookSignature(
(req as any).rawBody,
req.headers['x-devotel-signature'] as string,
process.env.DEVOTEL_WEBHOOK_SECRET!,
);
if (!ok) return res.status(401).send('invalid signature');
const event = req.body as { type: string; data: Record<string, unknown> };
switch (event.type) {
case 'call.initiated': /* dial-out started */ break;
case 'call.ringing': /* far-end ringing */ break;
case 'call.answered': /* bridged */ break;
case 'call.completed': /* normal hangup */ break;
case 'call.failed': /* signalling failure, busy, no-answer */ break;
case 'call.recording.ready': /* recording.id is in event.data */ break;
case 'call.transcript.ready':/* call.transcript.get(id) for full text */ break;
default: /* forward-compat */ break;
}
res.status(200).json({ received: true });
});
Verify
// Send OTP
const verification = await orbit.verify.send('+14155552671', 'sms');
// Check OTP
const result = await orbit.verify.check('+14155552671', '482901');
console.log(result.data.status); // 'approved'
Webhooks
// Verify webhook signature in an Express handler
import { verifyWebhookSignature } from '@devotel/orbit-sdk';
app.post('/webhooks/orbit', (req, res) => {
const isValid = verifyWebhookSignature(
req.rawBody,
req.headers['x-devotel-signature'],
'whsec_your_secret',
);
if (!isValid) return res.status(401).send('Invalid signature');
const event = req.body;
switch (event.type) {
case 'message.delivered':
// Handle delivery confirmation
break;
case 'call.completed':
// Handle call completion
break;
}
res.status(200).json({ received: true });
});
Error Handling
import { Devotel, OrbitApiError } from '@devotel/orbit-sdk'
try {
await orbit.messages.send({ channel: 'sms', to: 'invalid', body: 'Hi' });
} catch (error) {
if (error instanceof OrbitApiError) {
console.error(error.code); // 'INVALID_PHONE_NUMBER'
console.error(error.statusCode); // 422
console.error(error.message); // 'The to field must be a valid E.164 phone number'
}
}
Configuration
| Option | Type | Default | Description |
|---|
apiKey | string | — | Your Devotel API key (required) |
baseUrl | string | https://orbit-api.devotel.io/api/v1 | API base URL |
Requirements
- Node.js 18 or later
- TypeScript 5.0+ (recommended but not required)