Install
npm install @yotel/client
Runs in Node 18+, Deno, Bun, and modern browsers (though you
probably don’t want to call the API from a browser — the key would
be exposed).
Quickstart
import { YotelClient } from "@yotel/client";
const client = new YotelClient({
apiKey: process.env.YOTEL_KEY!,
});
// Create campaign
const c = await client.campaigns.create({
name: "Q4 outreach",
dial_mode: "predictive",
predictive_target_abandon: 0.02,
});
// Push lead
const lead = await client.leads.create({
campaign_id: c.id,
phone: "9876543210",
name: "Priya",
email: "priya@example.com",
});
// Bulk push
const result = await client.leads.bulk({
campaign_id: c.id,
leads: [
{ phone: "9876543211" },
{ phone: "9876543212", name: "Arjun" },
],
});
console.log(result.valid, "added,", result.duplicates, "skipped");
Verifying webhooks
import express from "express";
import { verifyWebhook, InvalidSignature } from "@yotel/client";
const app = express();
app.post(
"/webhooks/yotel",
express.raw({ type: "application/json" }),
(req, res) => {
try {
const event = verifyWebhook({
rawBody: req.body,
headers: req.headers,
signingSecret: process.env.YOTEL_WEBHOOK_SECRET!,
});
if (event.event_type === "call.ended") {
const callId = event.data.call_id;
// ...
}
res.status(200).send();
} catch (e) {
if (e instanceof InvalidSignature) {
return res.status(401).send();
}
throw e;
}
},
);
Rate-limit handling
The client auto-retries 429s up to 3 times, respecting Retry-After:
// default
const client = new YotelClient({ apiKey: "..." });
// disable
const client = new YotelClient({ apiKey: "...", retryOn429: false });
Error handling
import {
YotelClient,
AuthenticationError,
RateLimitError,
ValidationError,
APIError,
} from "@yotel/client";
try {
await client.leads.create({ campaign_id: "c-1", phone: "bad" });
} catch (e) {
if (e instanceof ValidationError) {
console.log(e.errors);
} else if (e instanceof RateLimitError) {
console.log("retry after", e.retryAfter);
} else if (e instanceof AuthenticationError) {
// ...
} else if (e instanceof APIError) {
console.log(e.statusCode, e.detail);
}
}
Types
Every request + response is typed. Hover in VS Code for docs on each
field.
import type { Campaign, Lead, PredictiveStats } from "@yotel/client";
OAuth helpers
For partner apps that act on behalf of individual Yotel users, the
SDK ships PKCE-ready OAuth helpers — works in browser + Node.
import { AuthorizationCodeFlow, generateCodeVerifier } from "@yotel/client";
const flow = new AuthorizationCodeFlow({
clientId: "sfdc_yotel",
clientSecret: process.env.YOTEL_CLIENT_SECRET!,
redirectUri: "https://app.example/cb",
scopes: ["campaigns:read", "openid", "offline_access"],
});
const verifier = generateCodeVerifier();
const url = await flow.buildAuthorizeUrl({ state: "csrf-1", codeVerifier: verifier });
// ... redirect user, callback handler ...
const tokens = await flow.exchangeCode({ code, codeVerifier: verifier });
const next = await tokens.refresh(); // rotated refresh_token
JWTBearerFlow is Node-only (it signs with node:crypto). Browser
bundles that only use the authorization-code flow won’t include any
Node-specific imports.
See OAuth 2.0 for the full flow reference.
Recordings
Access call recordings — signed URLs, raw bytes, or streamed.
// Get a signed URL (7-day expiry)
const recording = await client.recordings.getSignedUrl("call_id");
console.log(recording.url, recording.stereo);
// Download to disk (Node.js only)
await client.recordings.download("call_id", "recording.wav");
// Load into memory
const audio = await client.recordings.fetchBytes("call_id");
// Stream to a writable (Node.js only)
import { createWriteStream } from "fs";
await client.recordings.streamTo("call_id", createWriteStream("out.wav"));
getSignedUrl requires calls:read scope. fetchBytes, download,
and streamTo require the separate recordings:download scope.
See Recordings API for response details.
Voice agents
Full CRUD for voice agent routing aliases.
// Create
const va = await client.voiceAgents.create({
name: "Support bot",
ws_url: "wss://ai.example.com/ws",
max_concurrent: 50,
});
// List / Get
const all = await client.voiceAgents.list();
const one = await client.voiceAgents.get(va.id);
// Update (PATCH)
await client.voiceAgents.update(va.id, { max_concurrent: 100 });
// Archive (soft delete) or hard delete
await client.voiceAgents.archive(va.id);
await client.voiceAgents.delete(va.id);
// Tenant default
await client.voiceAgents.setAsDefault(va.id);
const defaultId = await client.voiceAgents.getDefault();
See Voice agents CRUD and
Voice agent defaults.
AI sessions
Control active voice AI sessions with typed verb methods.
// Transfer call to a human agent
await client.aiSessions.transfer("call_abc", {
destinationType: "agent",
destination: "agent_xyz",
callbackToken: "yt_cb_...",
});
// Hang up
await client.aiSessions.hangup("call_abc", {
callbackToken: "yt_cb_...",
});
// Set disposition
await client.aiSessions.setDisposition("call_abc", {
disposition: "interested",
notes: "Wants premium plan",
callbackToken: "yt_cb_...",
});
// Get current call state
const state = await client.aiSessions.getCallState("call_abc", {
callbackToken: "yt_cb_...",
});
All 25 control verbs are available as typed methods:
transfer, hangup, log, mute, unmute, hold, unhold,
sendDtmf, playAudio, setDisposition, recordingPause,
recordingResume, getCallState, conferenceStart,
conferenceAdd, conferenceRemove, conferenceLeave,
requestSupervisor, whisper, barge, monitorStart,
monitorStop, setLeadField, setLeadStatus,
scheduleCallback.
See AI sessions and the
Control API reference for verb parameters.
Additional error types
Beyond the base errors, voice agent and AI session
operations can throw:
import {
NoVoiceAgentConfigured, // 424 — no agent resolved in hierarchy
VoiceAgentInUse, // 409 — active sessions prevent deletion
MaxConcurrentExceeded, // 429 — concurrency limit reached
ConferenceMembershipRequired, // 403 — whisper/barge needs conference
AISessionTerminated, // 410 — call already ended
} from "@yotel/client";
See Error reference for details.
Source
github.com/kamal-zetta/dialer/tree/main/sdks/typescript