Endpoint
POST/api/public/v1/voicebot/session
When in doubt, use voicebot from the embed snippet and textvoice from your own backend. Neither is "lesser" — this is purely an attribution split.
What's identical to text-voice
The request body, response shape, WebSocket transport, frame protocol, error codes, and per-second pricing are all the same. See the text-voice spec for the full schema.
What differs
Per-row differences
| Field | Type | Notes |
|---|---|---|
scope name | voicebot | Keys must include voicebot in their scope list to call this endpoint. |
surface field | "voicebot" | Echoes back voicebot in the response body. |
usage rollup | by_surface.voicebot | Sessions are bucketed under voicebot, not textvoice, in /api/api-keys/[id]/usage. |
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Body was malformed JSON or exceeded the 4 KB cap. |
| 401 | missing_api_key | Authorization header absent or not a vv_live_… Bearer token. |
| 401 | invalid_api_key | Key not found, revoked, or disabled. |
| 402 | insufficient_balance | Wallet is empty and autopay is not active. Top up or enable autopay. |
| 403 | scope_denied | Key exists but is not authorized for this surface. |
| 429 | rate_limited | Per-key budget exceeded. See X-RateLimit-* headers. |
| 500 | server_error | We failed. Retry with exponential backoff. |
| 502 | upstream_error | Upstream backend (LiveKit / meeting-agent) failed. Retry. |
Examples
curl -X POST https://www.vaanilabs.in/api/public/v1/voicebot/session \
-H "Authorization: Bearer $VAANI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "flow_id": "your-flow-uuid-or-omit" }'
# Response 200
# {
# "ws_url": "wss://textvoice.vaanilabs.in/browser-stream?public_token=...",
# "ws_token": "<short-lived JWT>",
# "expires_at": "2026-04-25T12:34:56.000Z",
# "surface": "voicebot"
# }const apiKey = process.env.VAANI_API_KEY;
const res = await fetch(
"https://www.vaanilabs.in/api/public/v1/voicebot/session",
{
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ flow_id: flowId }),
},
);
if (!res.ok) throw new Error(`gateway ${res.status}`);
const { ws_url } = await res.json();
const ws = new WebSocket(ws_url);
ws.onopen = () => console.log("connected");
ws.onmessage = (e) => console.log("frame", e.data);
ws.onclose = () => console.log("closed");import os, asyncio, requests, websockets
API_KEY = os.environ["VAANI_API_KEY"]
r = requests.post(
"https://www.vaanilabs.in/api/public/v1/voicebot/session",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"flow_id": flow_id},
timeout=10,
)
r.raise_for_status()
session = r.json()
async def stream():
async with websockets.connect(session["ws_url"]) as ws:
async for frame in ws:
print("frame", frame)
asyncio.run(stream())