Developer platform · v2

Reference

View as MarkdownAll docs

SDKs

Three official SDKs#

PackageLanguageInstallDoor
@dvaarik/nodeTypeScript / JavaScript, Node 20+npm install @dvaarik/nodemachine
dvaarikPython 3.9+pip install dvaarikmachine
@dvaarik/webTypeScript / JavaScript, browsernpm install @dvaarik/webbrowser

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.

ts
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
  }
});
python
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#

NodePythonRoutes
client.agentsclient.agents/v2/agents — list, create, get, update, delete, rotate secret
client.voiceSessionsclient.voice_sessions/v2/voice/sessions and /outbound — list, create, get
client.providerAccountsclient.provider_accounts/v2/provider-accounts — list, save, get, probe, rotate, disconnect
client.carriersclient.carriers/v2/carriers — list, catalog, create, get, verify, disconnect
client.carrierNumbersclient.carrier_numbers/v2/carriers/numbers — list, attach, import, update, delete
client.webhooksclient.webhooks/v2/projects/{project_id}/webhooks — list, create, disable, test, dead letters, verify
client.projectsclient.projects/v2/projects — account token, not a project key
client.apiKeysclient.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 status and 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, exposing Retry-After), and server (5xx). See Errors.
  • Retries. Exponential backoff with jitter on 429, 502 and 503 only, honouring Retry-After. A POST is 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, with pages(), all() and a maxItems bound.
  • Webhook verification. The timestamped HMAC described in Webhooks, including the replay-window check, in one call.
  • Rate-limit visibility. client.rateLimit / client.rate_limit reports the X-RateLimit-* headers from the most recent response, so you can pace against remaining instead 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.

html
<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>
ts
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.