@osmapi/osmtalk-sdk
v0.4.0
Published
Official TypeScript SDK for the osmTalk voice AI platform
Maintainers
Readme
@osmapi/osmtalk-sdk
Official TypeScript / JavaScript SDK for the osmTalk voice AI platform.
npm install @osmapi/osmtalk-sdk
# or: pnpm add @osmapi/osmtalk-sdkWorks in Node 18+, Deno, Bun, Cloudflare Workers, and browsers.
Quick start
import { Osmtalk } from "@osmapi/osmtalk-sdk";
const client = new Osmtalk({ apiKey: process.env.OSMTALK_API_KEY! });
// Place an outbound call with per-call personalization
const call = await client.calls.outbound({
agentId: "agent_xxx",
phoneNumberId: "pn_xxx",
destination: "+919876543210",
dynamicVariables: {
first_name: "Arjun",
company: "Acme",
renewal_date: "May 28, 2026",
},
});
console.log("Call started:", call.callId);Resources
| Resource | Operations |
|---|---|
| client.agents | list, get, create, update, delete, connect, publishVersion, listVersions, getVersion, rollbackToVersion |
| client.calls | list (paginated), get, outbound, end, transfer, waitUntilEnded |
| client.campaigns | list, get, create, update, delete, start, pause, resume, stop, report, uploadLeadsCsv, uploadLeads, listLeads |
| client.phoneNumbers | list, update |
| client.dnc | list, add, bulkAdd, remove |
| client.eval | simulate, createTestCase, listTestCases, runTestCase, runAll, listRuns, getRun |
| client.settings | get, getStorage, updateStorage, getWebhook, updateWebhook, getCompliance, updateCompliance |
| client.platform | getRates, listProviders, getPresets, getModelHealth, getTemplates, getTemplate, saveTemplate, deleteTemplate |
Plus the standalone helpers verifyWebhookSignature / verifyWebhookSignatureAsync (see below).
What's new in 0.4.0
- Breaking:
client.calls.list()now returnsPaginated<CallRecord>—{ data, total, limit, offset }— not a raw array. The server always returned this shape; the prior type was wrong. See CHANGELOG.md for a one-line migration. - New
client.phoneNumbersresource —list()andupdate()for managing org-owned numbers. calls.list()accepts filters:status,agentId,channel,from,to,search,limit,offset.
What's new in 0.3.0
- Auto-retry on 5xx, 429, and network errors with
Retry-Afterhonored and exponential backoff. No more hand-rolling retry wrappers. client.calls.waitUntilEnded(callId)— one-line polling helper for the "place call → wait → get result" pattern.AbortSignalsupport on every method viaRequestOptions.signal.User-Agentheader sent automatically.- Per-org request override via
RequestOptions.organizationId. OsmtalkError.isRetryable/.isClientError/.retryAttemptsfor cleaner error branching.
Full version history: CHANGELOG.md.
Examples
Run an outbound campaign
const camp = await client.campaigns.create({
name: "Q2 Renewals",
agentId: "agent_xxx",
phoneNumberId: "pn_xxx",
maxConcurrent: 5,
schedule: { timezone: "Asia/Kolkata", windowStart: "10:00", windowEnd: "18:00" },
retryPolicy: { maxAttempts: 3, backoffMinutes: 60, retryOn: ["no_answer", "busy"] },
webhookUrl: "https://your-crm/webhooks/osmtalk",
});
await client.campaigns.uploadLeadsCsv(camp.id, `
phone,first_name,renewal_date
+919876543210,Arjun,2026-05-28
+919876543211,Meera,2026-06-02
`.trim());
await client.campaigns.start(camp.id);
// Poll the report
const report = await client.campaigns.report(camp.id);
console.log(report.counts.byStatus);Listing recent calls with filters
const { data, total } = await client.calls.list({
status: "completed",
channel: "phone",
from: "2026-05-01",
limit: 25,
});
console.log(`Showing ${data.length} of ${total} matching calls`);
for (const call of data) {
console.log(call.id, call.durationSeconds, "s", call.disposition);
}Wait for a call to finish (without writing a poll loop)
const { callId } = await client.calls.outbound({
agentId: "agent_xxx",
phoneNumberId: "pn_xxx",
destination: "+919876543210",
});
// Default: poll every 5s, give up after 30 minutes. All configurable.
const final = await client.calls.waitUntilEnded(callId, {
pollIntervalMs: 5_000,
timeoutMs: 15 * 60 * 1000,
});
console.log("Final status:", final.status);
console.log("Duration: ", final.durationSeconds, "s");
console.log("Disposition: ", final.disposition);
console.log("Recording: ", final.recordingUrl);For production, prefer webhooks — see the receiver example below.
Cancel an in-flight request
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 2_000);
try {
await client.agents.list({ signal: ctrl.signal });
} catch (err) {
if (ctrl.signal.aborted) console.log("Cancelled by us");
else throw err;
}signal, timeoutMs, and organizationId are accepted on every method via the trailing RequestOptions argument.
Publish a new agent version and A/B test
// Publish current draft as v2
const v2 = await client.agents.publishVersion("agent_xxx", { label: "Tighter qualifier" });
// Call with v2 explicitly
await client.calls.outbound({
agentId: "agent_xxx",
phoneNumberId: "pn_xxx",
destination: "+919876543210",
agentVersion: v2.version,
});Simulate before going live
const sim = await client.eval.simulate("agent_xxx", [
{ role: "user", content: "Hi, who's calling?" },
{ role: "user", content: "Sure, tell me more about renewal options" },
]);
for (const turn of sim.transcript) {
console.log(`${turn.role}: ${turn.content}`);
}Verify webhooks (Node, sync)
import express from "express";
import { verifyWebhookSignature } from "@osmapi/osmtalk-sdk";
const app = express();
// IMPORTANT: use express.raw() — NOT express.json(). The signature was
// computed over the exact bytes; re-serialized JSON will not match.
app.use("/webhooks/osmtalk", express.raw({ type: "application/json" }));
app.post("/webhooks/osmtalk", (req, res) => {
const ok = verifyWebhookSignature(
req.body,
req.header("x-osmtalk-signature"),
process.env.OSMTALK_WEBHOOK_SECRET!,
);
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString());
if (event.event === "call.completed") {
console.log("Call ended:", event.call.id, event.analysis?.disposition);
}
res.json({ ok: true });
});Verify webhooks in Workers / Deno / Bun (async, WebCrypto)
import { verifyWebhookSignatureAsync } from "@osmapi/osmtalk-sdk";
export default {
async fetch(req: Request) {
const raw = await req.text();
const ok = await verifyWebhookSignatureAsync(
raw,
req.headers.get("x-osmtalk-signature"),
env.OSMTALK_WEBHOOK_SECRET,
);
if (!ok) return new Response("invalid signature", { status: 401 });
const event = JSON.parse(raw);
// …handle event
return new Response("ok");
},
};Error handling
import { Osmtalk, OsmtalkError } from "@osmapi/osmtalk-sdk";
try {
await client.calls.outbound({ /* ... */ });
} catch (err) {
if (err instanceof OsmtalkError) {
console.log("HTTP", err.status, err.body);
console.log("Retries attempted:", err.retryAttempts);
if (err.isRetryable) console.log("Server might recover — try again later.");
if (err.isClientError) console.log("Bad input — check err.body.details.");
} else {
throw err;
}
}| Status | Meaning | OsmtalkError flag |
|---|---|---|
| 400 | Validation — err.body.details has zod field errors | isClientError |
| 401 | Bad API key | isClientError |
| 402 | Insufficient credits | isClientError |
| 404 | Resource not found | isClientError |
| 408 | Request timeout | isRetryable |
| 429 | Concurrency or rate limit | isRetryable |
| 5xx | Server error / provider outage | isRetryable |
The SDK already auto-retries 408/429/5xx and network errors twice by default. Mutating requests (POST/PUT/DELETE) are only retried when you pass idempotencyKey so the SDK never silently double-charges.
Options
new Osmtalk({
apiKey: "osm_live_…",
baseUrl: "https://api.osmtalk.com", // default
timeoutMs: 30_000, // per-request, 0 to disable
maxRetries: 2, // auto-retry count for 5xx/429
retryInitialDelayMs: 250, // doubles per retry, jittered
organizationId: "org_xxx", // for multi-org accounts
defaultHeaders: { "X-Trace-Id": "…" },// added to every request
fetch: customFetch, // optional, defaults to global fetch
});Per-request overrides:
await client.calls.outbound(input, {
idempotencyKey: `dest-${destination}-${date}`,
signal: controller.signal,
timeoutMs: 60_000,
organizationId: "org_yyy",
});Runnable examples
See github.com/osm-API/osmtalk-examples for three end-to-end projects:
- Personalized outbound call — dynamic per-user prompts
- Bulk campaign from CSV — scale to thousands
- Verified webhook receiver — close the loop with
verifyWebhookSignature
License
MIT
