# 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](#dvaarikweb-browser-calls).

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](/docs/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](/docs/projects-and-keys),
a `verified` [provider account](/docs/providers), and a
[carrier number](/docs/telephony) 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

| 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 `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](/docs/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](/docs/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](/docs/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](/console/publishable-keys); the full guide, including
origins, limits and every error code, is on
[Browser calls](/docs/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.

---

Source: https://developers.dvaarik.com/docs/sdks · every page as one file: https://developers.dvaarik.com/docs.md
