Skip to main content

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

OptionTypeDefaultDescription
apiKeystringYour Devotel API key (required)
baseUrlstringhttps://orbit-api.devotel.io/api/v1API base URL

Requirements

  • Node.js 18 or later
  • TypeScript 5.0+ (recommended but not required)