voxplo
v0.1.0
Published
Voxplo SDK: give an AI a phone (outbound objective-driven calls).
Maintainers
Readme
Voxplo Node SDK
Zero-dependency Node 18+ SDK for the Voxplo Agent Outbound-Call API. Place objective-driven AI calls, poll for results, and verify webhooks.
Quickstart
import { Client, ByoursideError, verifyWebhook } from './src/index.js';
const client = new Client({ apiKey: 'bys_ak_...' });Place a call and wait for the result
try {
// Place the call
const { callId } = await client.placeCall({
to: '+14155550123',
objective: 'Confirm the appointment for tomorrow at 2 PM and ask if they need to reschedule.',
fields: [
{ name: 'confirmed', type: 'boolean' },
{ name: 'new_time', type: 'string' },
],
});
// Poll until the call reaches a terminal status (default timeout: 3 minutes)
const call = await client.waitForCall(callId, { timeoutMs: 120_000, intervalMs: 5_000 });
console.log('Status:', call.status); // completed | no_answer | voicemail | declined | failed
console.log('Extracted fields:', call.extracted);
} catch (err) {
if (err instanceof ByoursideError) {
console.error(`[${err.code}] ${err.message}`);
} else {
throw err;
}
}List recent calls
const calls = await client.listCalls({ limit: 20 });
calls.forEach((c) => console.log(c.callId, c.status));Verify a webhook (Express example)
Receive real-time call events by setting a webhookUrl when placing a call. The header X-BYS-Signature carries the signature.
import express from 'express';
import { verifyWebhook } from './src/index.js';
const app = express();
// IMPORTANT: parse the body as raw bytes so the signature can be verified.
app.post('/webhooks/bys', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-bys-signature'];
const rawBody = req.body.toString('utf8');
if (!verifyWebhook(sig, rawBody, process.env.BYS_WEBHOOK_SECRET)) {
return res.status(400).send('Bad signature');
}
const event = JSON.parse(rawBody);
console.log('Webhook event:', event.callId, event.status);
res.status(200).send('OK');
});Call status values
| Status | Meaning |
|---|---|
| queued | Accepted, not yet dialing |
| in_progress | Call is active |
| completed | Call finished; summary + fields available |
| no_answer | Rang but nobody picked up |
| voicemail | Reached voicemail |
| declined | Call rejected by the recipient |
| failed | Carrier or trunk error |
Terminal statuses (waitForCall stops here): completed, no_answer, voicemail, declined, failed.
Error codes
ByoursideError instances carry .code (a token string) and .status (HTTP status, 0 for network errors).
| Code | Meaning |
|---|---|
| destination_blocked | That destination is not allowed (premium, IRSF, or unsupported country). |
| invalid_number | The destination number is invalid (use full E.164, e.g. +14155550123). |
| to_required | A destination number (to) is required. |
| objective_required | An objective for the call is required. |
| caller_id_not_owned | That caller ID is not a number on your account. |
| rate_limited | Rate limit reached. Try again shortly. |
| over_minute_cap | Outbound usage limit reached for now. |
| unauthorized | Invalid or missing API key. |
| not_found | No call found with that id (or it does not belong to your account). |
| placement_failed | The call could not be placed (carrier/trunk issue). Try again shortly. |
| store_error | Temporary service error. Please retry shortly. |
| timeout | waitForCall hit the timeout before reaching a terminal status. |
| network_error | Could not reach the API (network failure). |
Constructor options
new Client({
apiKey, // required
baseUrl, // default: 'https://api.voxplo.ai'
fetchImpl, // override global fetch (useful for tests)
sleep, // override the poll sleep (useful for tests)
})Development
node --check src/*.js # syntax check
node --test # run all tests (run from sdks/node/)No dependencies are required. Node 18+ is needed for built-in fetch.
