Node.js SDK
The official Node.js and TypeScript SDK for VoiceDock, with no runtime dependencies.
The official Node.js and TypeScript SDK for VoiceDock, published on npm as hmsovereign. It has no runtime dependencies and covers assistants, calls, phone numbers, campaigns, workflows, SIP trunks, voices, usage, BYOK, tool and analysis templates, domains and organizations. For the chat endpoints and the health check, call the REST API directly.
Installation
npm install hmsovereignRequires Node.js 22 or later.
Quick Start
import { HMSSovereign } from 'hmsovereign';
const client = new HMSSovereign({
apiKey: process.env.VOICEDOCK_API_KEY!,
});
// List all assistants
const assistants = await client.assistants.list();
// Create an assistant
const assistant = await client.assistants.create({
name: 'Customer Support',
first_message: 'Hello, how can I help you?',
llm_config: {
provider: 'openai',
model: 'gpt-5.4-mini',
messages: [{ role: 'system', content: 'You are a friendly customer service agent.' }],
},
stt_config: {
provider: 'deepgram',
model: 'nova-3-general',
language: 'en',
},
tts_config: {
provider: 'elevenlabs',
voice_id: 'ukiwGs47sHyibruHJ1vg',
},
});Resources
All resources are available as properties on the client:
| Resource | Description |
|---|---|
client.assistants | CRUD for AI voice assistants |
client.calls | List calls, make outbound calls, call control |
client.numbers | Phone number management |
client.campaigns | Outbound call campaigns with leads |
client.workflows | Multi-step call flows; attach one with client.numbers.update(id, { workflow_id }) |
client.sipTrunks | SIP trunk configuration |
client.voices | List available TTS voices |
client.usage | Usage logs and billing |
client.byok | Bring Your Own Key management |
client.toolTemplates | Reusable tool/function templates |
client.analysisTemplates | Post-call analysis schemas |
client.domains | Custom domain configuration |
client.organizations | Organization management |
Outbound Calls
Three configuration modes for outbound calls:
Reference mode — use a saved assistant
const call = await client.calls.create({
destination: '+31612345678',
assistant_id: 'uuid-of-saved-assistant',
});
console.log(`Call started: ${call.call_id}`);Transient mode — one-time assistant config
const call = await client.calls.create({
destination: '+31612345678',
assistant: {
first_message: 'Hello John, this is a reminder about your appointment.',
llm_config: { provider: 'openai', model: 'gpt-5.4-mini' },
stt_config: { provider: 'deepgram', model: 'nova-3-general', language: 'en' },
tts_config: { provider: 'elevenlabs', voice_id: 'ukiwGs47sHyibruHJ1vg' },
},
});Hybrid mode — saved assistant with overrides
const call = await client.calls.create({
destination: '+31612345678',
assistant_id: 'uuid-of-saved-assistant',
assistant_override: {
first_message: 'Good afternoon John, calling about your order.',
metadata: { order_id: '12345', customer_tier: 'premium' },
},
});The metadata object is passed through to all webhook events at message.assistant.metadata.
Call Control
Control active calls in real-time:
// Inject context (invisible to caller, visible to LLM)
await client.calls.injectContext(callId, 'Customer is a VIP member', true);
// Make the assistant say something
await client.calls.say(callId, 'One moment please, let me look that up for you.');
// Transfer to a human agent
await client.calls.transfer(callId, '+31201234567', 'Transferring you to a colleague now.');
// End the call
await client.calls.end(callId, 'Thank you for your call. Goodbye!');Pagination
calls.list() returns a Page with metadata:
// Single page
const page = await client.calls.list({ status: 'ended', limit: 50 });
console.log(page.data); // Call[]
console.log(page.pagination); // { total, limit, offset }
console.log(page.hasMore); // booleanAuto-pagination
Iterate through all results automatically with listAll():
for await (const call of client.calls.listAll({ status: 'ended' })) {
console.log(call.id, call.duration_seconds);
}
// Or collect everything into an array
const allCalls = await client.calls.listAll({ status: 'ended' }).toArray();Auto-pagination is available on client.calls.listAll() and client.usage.listAll().
Campaigns
Run automated outbound call campaigns:
// Create a campaign with leads
const campaign = await client.campaigns.create({
name: 'Appointment Reminders',
agent_id: assistant.id,
system_message_template: 'Call {{name}} about the appointment on {{date}}',
schedule_start_time: '09:00',
schedule_end_time: '17:00',
timezone: 'America/New_York',
leads: [
{
phone_number: '+15551234567',
variables: { name: 'John Smith', date: '2026-10-01', time: '14:00' },
},
],
});
// Add more leads later
await client.campaigns.addLead(campaign.id, {
phone_number: '+15559876543',
variables: { name: 'Peter Johnson', date: '2026-10-02', time: '10:00' },
});Webhook Verification
The SDK provides built-in webhook signature verification. See Webhook Security for background. The signature headers are only sent when a webhook secret is configured.
import { Webhooks } from 'hmsovereign';
// Express example — use raw body for signature verification
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = Webhooks.verify({
payload: req.body,
signature: req.headers['x-webhook-signature'] as string,
timestamp: req.headers['x-webhook-timestamp'] as string,
secret: process.env.WEBHOOK_SECRET!,
});
switch (event.message.type) {
case 'assistant-request':
// Return dynamic config before call is answered
res.json({ assistant: { first_message: 'Welcome!' } });
break;
case 'tool-calls':
// Handle function calls from the assistant
const toolCall = event.message.tool_call_list[0];
res.json({ result: { status: 'ok' } });
break;
case 'status-update':
// Call state: in-progress, ended, ended-with-error
console.log(event.message.call.status);
res.sendStatus(200);
break;
case 'end-of-call-report':
// Post-call summary, transcript, analysis
console.log(event.message.summary);
res.sendStatus(200);
break;
default:
res.sendStatus(200);
}
} catch (error) {
res.status(400).send('Invalid signature');
}
});Raw body required. You must use
express.raw()(or equivalent) to get the raw request body. Parsed JSON bodies will fail signature verification because the signature is computed over the original string.
Error Handling
The SDK throws typed errors that you can catch with instanceof:
import {
AuthenticationError,
NotFoundError,
RateLimitError,
InsufficientCreditsError,
} from 'hmsovereign';
try {
await client.assistants.get('non-existent-id');
} catch (error) {
if (error instanceof NotFoundError) {
console.log('Assistant not found');
} else if (error instanceof AuthenticationError) {
console.log('Invalid API key');
} else if (error instanceof RateLimitError) {
console.log(`Rate limited, retry after ${error.retryAfter}s`);
} else if (error instanceof InsufficientCreditsError) {
console.log('Top up your credits in the dashboard');
}
}Errors returned by the API extend ApiRequestError, which carries status, message, the parsed response body and, when the API lists validation problems, details. When the API returns both an error and an explanation, the message contains both. Timeouts and network errors are thrown as a plain Error, and webhook verification throws a WebhookVerificationError.
Configuration
const client = new HMSSovereign({
apiKey: process.env.VOICEDOCK_API_KEY!, // Required
baseUrl: 'https://...', // Default: https://api.hmsovereign.com/api/v1
maxRetries: 2, // Default: 2
timeout: 30_000, // Default: 30s
debug: true, // Default: false — logs requests and responses
});| Option | Default | Description |
|---|---|---|
apiKey | — | Your VoiceDock API key (required) |
baseUrl | https://api.hmsovereign.com/api/v1 | API base URL |
maxRetries | 2 | Maximum number of retries for a failed request |
timeout | 30000 | Request timeout in milliseconds |
debug | false | Log requests and responses to the console, with secret values masked |
GET, PUT and DELETE requests are retried after a 5xx response, a timeout or a network error, waiting 1, 2, 4 and at most 8 seconds. POST and PATCH requests are only retried after a 429, because a first attempt that failed for another reason may already have been processed. After a 429, the client waits for the Retry-After header, up to 30 seconds.
TypeScript
The SDK is written in TypeScript and exports types for its resources. Import them directly:
import type {
Assistant,
Call,
Campaign,
CampaignLead,
PhoneNumber,
SipTrunk,
Voice,
UsageLog,
WebhookPayload,
STTConfig,
LLMConfig,
TTSConfig,
} from 'hmsovereign';