Reference
SDKs
Three official SDKs#
| Package | Language | Install | Door |
|---|---|---|---|
@dvaarik/node | TypeScript / JavaScript, Node 20+ | npm install @dvaarik/node | machine |
dvaarik | Python 3.9+ | pip install dvaarik | machine |
@dvaarik/web | TypeScript / JavaScript, browser | npm install @dvaarik/web | browser |
The two server SDKs wrap the machine door: the /v2/* routes that authenticate with a project key (X-Api-Key: dvk_live_…). Neither wraps the /console/* routes, which take an account browser session and do not belong on a server. @dvaarik/web is the odd one out and is described below.
They share one shape on purpose — the same namespaces, the same error hierarchy, the same retry and idempotency rules, the same pagination helper, and the same webhook verifier — so a team running one service in Node and another in Python reads one mental model. Neither SDK is required: every operation is an ordinary HTTPS request, documented on the API reference.
The Node package runs on any fetch-capable runtime (Bun, Deno, Cloudflare Workers, Vercel Edge) and ships ESM, CommonJS, and type declarations. The Python package ships a sync Dvaarik and an async AsyncDvaarik over one transport, with a py.typed marker and httpx as its only dependency.
> Release status. The source lives in the platform repository under sdk/typescript, sdk/python and sdk/web. Publishing to npm and PyPI is a manual release step; until the first publish lands, install from those directories.
60-second quickstart#
Create an agent, ring a number, verify the delivery that reports the result. You need a project API key from Projects and keys, a verified provider account, and a carrier number with outbound enabled.
import { Dvaarik } from "@dvaarik/node";
const client = new Dvaarik({
apiKey: process.env.DVAARIK_API_KEY!, // never hard-code this
projectId: process.env.DVAARIK_PROJECT_ID!, // fills the webhook routes' path
});
// 1 — Create an agent. `webhook_secret` comes back exactly once: store it.
const { agent, webhook_secret } = await client.agents.create({
name: "Reception",
prompt: "You are the receptionist for Blue Orchid Salon. Book appointments.",
greeting: "Blue Orchid, how can I help?",
pipeline_mode: "cascade",
pipeline_config: { stt: "sarvam", llm: "gemini", tts: "elevenlabs" },
language: "en-IN",
});
// 2 — Ring a customer. The idempotency key is what makes a timed-out request
// safe to send again: the repeat returns the first call with
// `duplicate: true` instead of dialling twice.
const { session, duplicate } = await client.voiceSessions.createOutbound(
{ agent_id: agent.id, to: "+919876543210", from: "+911140000000" },
{ idempotencyKey: orderId },
);
// 3 — Verify the delivery. RAW BYTES, before any JSON parsing.
app.post("/webhooks/dvaarik", express.raw({ type: "application/json" }), async (req, res) => {
try {
const delivery = await client.webhooks.verify(req.body, req.headers, webhook_secret);
if (delivery.event === "call.completed") handle(delivery.payload);
res.sendStatus(200);
} catch {
res.sendStatus(400); // bad signature, or outside the five-minute window
}
});import os
from dvaarik import Dvaarik, DvaarikWebhookSignatureError
client = Dvaarik(
api_key=os.environ["DVAARIK_API_KEY"], # never hard-code this
project_id=os.environ["DVAARIK_PROJECT_ID"], # fills the webhook routes' path
)
# 1 — Create an agent. webhook_secret comes back exactly once: store it.
created = client.agents.create({
"name": "Reception",
"prompt": "You are the receptionist for Blue Orchid Salon. Book appointments.",
"greeting": "Blue Orchid, how can I help?",
"pipeline_mode": "cascade",
"pipeline_config": {"stt": "sarvam", "llm": "gemini", "tts": "elevenlabs"},
"language": "en-IN",
})
agent, webhook_secret = created.agent, created.webhook_secret
# 2 — Ring a customer. The idempotency key makes a timed-out request safe to
# send again: the repeat comes back duplicate=True instead of dialling twice.
result = client.voice_sessions.create_outbound(
{"agent_id": agent.id, "to": "+919876543210", "from": "+911140000000"},
idempotency_key=order_id,
)
# 3 — Verify the delivery. RAW BYTES, before any JSON parsing.
@app.post("/webhooks/dvaarik")
async def dvaarik_webhook(request: Request) -> Response:
try:
delivery = client.webhooks.verify(await request.body(), request.headers, webhook_secret)
except DvaarikWebhookSignatureError:
return Response(status_code=400)
if delivery.event == "call.completed":
handle(delivery.payload)
return Response(status_code=200)Deduplicate on delivery.deliveryId / delivery.delivery_id: retries of the same delivery reuse it.
Namespaces#
| Node | Python | Routes |
|---|---|---|
client.agents | client.agents | /v2/agents — list, create, get, update, delete, rotate secret |
client.voiceSessions | client.voice_sessions | /v2/voice/sessions and /outbound — list, create, get |
client.providerAccounts | client.provider_accounts | /v2/provider-accounts — list, save, get, probe, rotate, disconnect |
client.carriers | client.carriers | /v2/carriers — list, catalog, create, get, verify, disconnect |
client.carrierNumbers | client.carrier_numbers | /v2/carriers/numbers — list, attach, import, update, delete |
client.webhooks | client.webhooks | /v2/projects/{project_id}/webhooks — list, create, disable, test, dead letters, verify |
client.projects | client.projects | /v2/projects — account token, not a project key |
client.apiKeys | client.api_keys | /v2/api-keys — account token, not a project key |
projects, apiKeys and the provider catalogue are the account door: minting a project or a key is account administration that a project-scoped key deliberately cannot do. Construct a second client with an account access token for those, and calling one without it fails locally rather than sending a request that would come back 401.
What both SDKs do for you#
- Typed errors. One base carrying
statusand the request id, with a class per status: auth (401/403), payment required (402), not found (404), conflict (409), validation (422, exposing the field errors), rate limit (429, exposingRetry-After), and server (5xx). See Errors. - Retries. Exponential backoff with jitter on
429,502and503only, honouringRetry-After. APOSTis never retried unless you supplied an idempotency key — an outbound call that timed out may already be ringing. - Pagination.
iterate()walks a limit/offset list route as an async iterator, withpages(),all()and amaxItemsbound. - Webhook verification. The timestamped HMAC described in Webhooks, including the replay-window check, in one call.
- Rate-limit visibility.
client.rateLimit/client.rate_limitreports theX-RateLimit-*headers from the most recent response, so you can pace againstremaininginstead of waiting to be refused.
Money stays an integer nano-USD string in both SDKs. Parse it with BigInt in JavaScript and int in Python; never with Number or float. The platform fee is $0.005 per connected minute, rounded up to the next minute — see Billing.
@dvaarik/web — browser calls#
The browser SDK is not a fourth client with the same shape. It runs in a public web page with a publishable key (pk_live_…), and the only route that key can reach is POST /v2/browser/sessions — one voice call, on one agent, from an origin on the key's list. So it has no namespaces, no pagination and no webhook verifier: none of those belong in a page a stranger can view the source of. What it has instead is a microphone, a socket, and a call button.
<script src="https://cdn.jsdelivr.net/npm/@dvaarik/web/dist/dvaarik-web.global.js"></script>
<dvaarik-call publishable-key="pk_live_…" agent-id="…" label="Talk to us"></dvaarik-call>import { DvaarikCall } from "@dvaarik/web";
const call = new DvaarikCall({ publishableKey: "pk_live_…", agentId: "…" });
call.on("status", (status) => render(status)); // idle → live → ended
call.on("transcript", (line) => append(line)); // { role, text, final }
call.on("error", (error) => show(error.message)); // a sentence, already visitor-safe
button.addEventListener("click", () => call.start());Zero runtime dependencies, ESM + CommonJS + a single-file <script> build, and elapsedSeconds / minutesBilled so a page can show the running cost honestly. Mint keys under Publishable keys; the full guide, including origins, limits and every error code, is on Browser calls.
Not in the SDKs#
This release is voice only, and no SDK exposes anything else. @dvaarik/web deliberately stops at starting a call: minting, rotating and revoking publishable keys is console work behind an account session, not something a public page may do.
Use one project per boundary
Provider secrets stay encrypted and project API keys stay on your server.