Platform
Quickstart
From sign-up to a signed webhook#
Nine steps, in order, every one a real request. The bodies below are the shapes the published OpenAPI document declares, and the responses are what the API actually returns. There is no SDK yet — this is curl, with the same flow in Node and Python at the bottom.
export DVAARIK_API="https://api.developers.dvaarik.com"1. Create an account#
curl -sS -X POST "$DVAARIK_API/console/auth/signup" \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"password": "<a long random password>",
"name": "Your Name",
"company": "Your Company",
"accept_terms": true
}'What you should see: 202 with no body, and a code in your inbox. Confirm it with POST /console/auth/verify-email and { "email", "code" }. Until the address is verified every console route answers 403. You can also do this step in the browser at /signup.
2. Get an account session#
curl -sS -X POST "$DVAARIK_API/console/auth/login" \
-H "Content-Type: application/json" \
-d '{ "email": "you@example.com", "password": "<password>" }'{
"user": {
"id": "3d2f…", "email": "you@example.com", "name": "Your Name",
"company": "Your Company", "email_verified": true, "tier": "…"
},
"tokens": { "access_token": "eyJ…", "refresh_token": "…", "token_type": "bearer" }
}What you should see: 200 with both tokens. Export the access token; it is the account door and it is short-lived.
export DVAARIK_TOKEN="eyJ…"Only the account session creates projects and keys. It is never sent to a machine route, and a project key is never sent to a console route.
3. Create a project#
curl -sS -X POST "$DVAARIK_API/v2/projects" \
-H "Authorization: Bearer $DVAARIK_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "production", "external_reference": "acct_4821" }'{
"id": "8b41…", "name": "production", "external_reference": "acct_4821",
"status": "active", "revision": 1, "is_default": false,
"voice_concurrency": 2, "api_rpm": 120, "max_call_seconds": 600,
"social_rpm": 0, "social_daily_sends": 0,
"created_at": "2026-09-13T09:00:00Z", "updated_at": "2026-09-13T09:00:00Z"
}What you should see: 201 and a project id. Everything after this belongs to it.
export PROJECT_ID="8b41…"4. Mint a project API key#
Ask for the scopes this quickstart uses and nothing else. The names are exact; an unknown scope is refused with 400.
curl -sS -X POST "$DVAARIK_API/v2/api-keys" \
-H "Authorization: Bearer $DVAARIK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "backend-server",
"project_id": "'"$PROJECT_ID"'",
"scopes": ["provider_account:read", "provider_account:write",
"voice:read", "voice:write",
"webhook:read", "webhook:write"]
}'{
"key": {
"id": "c07a…", "project_id": "8b41…", "name": "backend-server",
"key_prefix": "dvk_1a2b", "scopes": ["provider_account:read", "…"],
"created_at": "2026-09-13T09:01:00Z", "last_used_at": null, "revoked_at": null
},
"plaintext": "dvk_1a2b…"
}What you should see: 201, and plaintext exactly once. No later response returns it. Put it in a secret manager, then:
export DVAARIK_API_KEY="dvk_1a2b…"5. Connect one of your AI provider accounts#
Read the catalogue first — it names every provider id, the credential fields to send, and the model ids. Never hard-code a list you copied.
curl -sS "$DVAARIK_API/v2/provider-catalog" \
-H "Authorization: Bearer $DVAARIK_TOKEN"Then save a credential on the machine door and ask the provider to confirm it:
curl -sS -X POST "$DVAARIK_API/v2/provider-accounts" \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "<provider id from the catalogue>",
"name": "Production account",
"credentials": { "<credential field from the catalogue>": "<secret>" }
}'
curl -sS -X POST "$DVAARIK_API/v2/provider-accounts/<account uuid>/probe" \
-H "X-Api-Key: $DVAARIK_API_KEY"{
"id": "1f9c…", "project_id": "8b41…", "provider": "…", "name": "Production account",
"capabilities": ["stt"], "hint": { "…": "••••1a2b" },
"status": "verified", "last_error": null,
"key_version": 1, "revision": 2, "verified_at": "2026-09-13T09:02:10Z",
"created_at": "2026-09-13T09:02:00Z", "updated_at": "2026-09-13T09:02:10Z"
}What you should see: the save returns 201 with status: "configured"; the probe returns status: "verified" with a verified_at. A rejected credential comes back invalid with the provider's own message in last_error, and a transient provider failure leaves it configured rather than guessing. Only a verified account is admitted into an agent or a call.
A cascade agent needs three roles — speech-to-text, language model, text-to-speech — from one account or three. A realtime agent needs one realtime-capable account.
6. Create an agent#
Model ids come from the catalogue; account_id values are the provider accounts you verified in step 5.
curl -sS -X POST "$DVAARIK_API/v2/agents" \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "support",
"prompt": "You are the receptionist for {{company}}. Answer questions and book visits.",
"pipeline_mode": "cascade",
"pipeline_config": {
"stt": { "account_id": "<uuid>", "model": "<catalogue model id>" },
"llm": { "account_id": "<uuid>", "model": "<catalogue model id>" },
"tts": { "account_id": "<uuid>", "model": "<catalogue model id>", "voice": "<provider voice id>" }
},
"language": "en-IN",
"greeting": "Hello, this is support.",
"variables": [{ "name": "company", "required": true }]
}'{
"agent": {
"id": "a44e…", "project_id": "8b41…", "name": "support", "version": 1,
"is_active": true, "pipeline_mode": "cascade", "language": "en-IN",
"has_webhook_secret": true, "…": "…"
},
"webhook_secret": "whsec_…"
}What you should see: 201, and webhook_secret once. Store it if you set the agent's own webhook_url; POST /v2/agents/{agent_id}/rotate-secret issues a new one.
export AGENT_ID="a44e…"7. Start a call#
A browser session. Your server asks for the session and hands the client only the returned URL — never the key.
curl -sS -X POST "$DVAARIK_API/v2/voice/sessions" \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "'"$AGENT_ID"'",
"variables": { "company": "Northline Dental" },
"sample_rate_in": 16000,
"sample_rate_out": 24000,
"max_duration_seconds": 300
}'{
"session": { "id": "9c1b…", "status": "created", "direction": "inbound",
"cost_nano_usd": "0", "rate_nano_usd_per_min": "5000000",
"money_scale": 1000000000, "currency": "USD", "…": "…" },
"ws_url": "wss://api.developers.dvaarik.com/v2/voice/sessions/9c1b…/stream?token=…",
"expires_in_seconds": 60
}What you should see: 201 with a single-use ws_url. Connect before expires_in_seconds elapses, send little-endian PCM16 mono at sample_rate_in, and read PCM16 at sample_rate_out; JSON text frames carry transcripts and turn control. The frame and close-code tables are on Voice sessions.
Or an outbound call on a number you already own, once a carrier is connected under Telephony:
curl -sS -X POST "$DVAARIK_API/v2/voice/sessions/outbound" \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Idempotency-Key: <unique per call attempt>" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "'"$AGENT_ID"'",
"to": "+15550000002",
"from": "+15550000001",
"max_duration_seconds": 300
}'What you should see: 202 with { "session": { … }, "duplicate": false }. Replaying the same Idempotency-Key returns the first call with duplicate: true instead of dialling twice.
To hear an agent without writing any client code, use the playground — it opens a browser session with your account session, so no key ever reaches a browser.
8. Receive a webhook#
curl -sS -X POST "$DVAARIK_API/v2/projects/$PROJECT_ID/webhooks" \
-H "X-Api-Key: $DVAARIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "primary",
"url": "https://example.com/hooks/dvaarik",
"event_types": ["call.completed", "call.failed"]
}'{
"id": "6ee0…", "project_id": "8b41…", "name": "primary",
"url": "https://example.com/hooks/dvaarik",
"event_types": ["call.completed", "call.failed"],
"status": "active", "revision": 1, "secret_hint": "••••9f2c",
"signing_secret": "whsec_…",
"created_at": "2026-09-13T09:05:00Z", "updated_at": "2026-09-13T09:05:00Z"
}Then send yourself a ping:
curl -sS -X POST "$DVAARIK_API/v2/projects/$PROJECT_ID/webhooks/<endpoint uuid>/test" \
-H "X-Api-Key: $DVAARIK_API_KEY"What you should see: the create returns 201 with signing_secret once — store it now. The test returns 202 with a delivery_id, meaning the delivery was recorded, not that it arrived. Your endpoint then receives a webhook.ping carrying X-Webhook-Signature: t=<unix>,v1=<hex>. Verify it before parsing: the signed bytes are "<timestamp>.<raw body>". Copy-paste verifiers for Node and Python are on Webhooks. Deliveries that exhaust their retries appear under GET .../webhooks/dead-letters.
9. Fund the wallet#
A session is refused with 402 when the spendable balance cannot cover its maximum duration, so top up before the first real call. Voice orchestration is $0.005 per connected minute, rounded up to the next minute; your AI providers and your carrier bill you directly for the same call. There is no monthly fee and no minimum spend — you top up what you want, spend what you use, and the rest stays on the wallet. Top up under Account and see Billing.
The same flow in Node#
No SDK exists yet. Plain fetch on Node 18 or newer is the whole client.
const API = "https://api.developers.dvaarik.com";
async function call(path, { token, key, method = "GET", body } = {}) {
const res = await fetch(API + path, {
method,
headers: {
...(token ? { Authorization: "Bearer " + token } : {}),
...(key ? { "X-Api-Key": key } : {}),
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const text = await res.text();
const data = text ? JSON.parse(text) : null;
if (!res.ok) throw new Error(res.status + " " + JSON.stringify(data?.detail ?? data));
return data;
}
const { tokens } = await call("/console/auth/login", {
method: "POST",
body: { email: process.env.EMAIL, password: process.env.PASSWORD },
});
const token = tokens.access_token;
const project = await call("/v2/projects", { token, method: "POST", body: { name: "production" } });
const created = await call("/v2/api-keys", {
token,
method: "POST",
body: { name: "backend-server", project_id: project.id, scopes: ["voice:read", "voice:write"] },
});
const key = created.plaintext; // the only time you will see it
const session = await call("/v2/voice/sessions", {
key,
method: "POST",
body: { agent_id: process.env.AGENT_ID, sample_rate_in: 16000, sample_rate_out: 24000 },
});
console.log(session.ws_url, "expires in", session.expires_in_seconds, "s");Money fields are integer nano-USD strings. Parse them with BigInt, never Number: BigInt(session.session.cost_nano_usd).
The same flow in Python#
import os, requests
API = "https://api.developers.dvaarik.com"
def call(path, token=None, key=None, method="GET", body=None):
headers = {}
if token: headers["Authorization"] = f"Bearer {token}"
if key: headers["X-Api-Key"] = key
res = requests.request(method, API + path, headers=headers, json=body, timeout=30)
if res.status_code >= 400:
raise RuntimeError(f"{res.status_code} {res.text}")
return res.json() if res.content else None
token = call("/console/auth/login", method="POST",
body={"email": os.environ["EMAIL"], "password": os.environ["PASSWORD"]})["tokens"]["access_token"]
project = call("/v2/projects", token=token, method="POST", body={"name": "production"})
created = call("/v2/api-keys", token=token, method="POST", body={
"name": "backend-server", "project_id": project["id"],
"scopes": ["voice:read", "voice:write"],
})
key = created["plaintext"] # the only time you will see it
session = call("/v2/voice/sessions", key=key, method="POST", body={
"agent_id": os.environ["AGENT_ID"], "sample_rate_in": 16000, "sample_rate_out": 24000,
})
print(session["ws_url"], "expires in", session["expires_in_seconds"], "s")Use int() or decimal.Decimal on the nano-USD strings; never float.
Where to go next#
- Authentication — the two doors, and what a
401means. - API reference — one page per resource, every operation with a curl example.
- Errors — every status this API returns and what to do about it.
Use one project per boundary
Provider secrets stay encrypted and project API keys stay on your server.