06Reference · Surface

Voicebot — text-voice, scoped for embeds.

Voicebot is the same engine as text-voice, billed at the same per-second rate. The split exists so embed-snippet usage shows up cleanly on your dashboard, separate from raw text-voice integrations.

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

FieldTypeNotes
scope namevoicebotKeys must include voicebot in their scope list to call this endpoint.
surface field"voicebot"Echoes back voicebot in the response body.
usage rollupby_surface.voicebotSessions are bucketed under voicebot, not textvoice, in /api/api-keys/[id]/usage.

Errors

StatusCodeMeaning
400invalid_requestBody was malformed JSON or exceeded the 4 KB cap.
401missing_api_keyAuthorization header absent or not a vv_live_… Bearer token.
401invalid_api_keyKey not found, revoked, or disabled.
402insufficient_balanceWallet is empty and autopay is not active. Top up or enable autopay.
403scope_deniedKey exists but is not authorized for this surface.
429rate_limitedPer-key budget exceeded. See X-RateLimit-* headers.
500server_errorWe failed. Retry with exponential backoff.
502upstream_errorUpstream backend (LiveKit / meeting-agent) failed. Retry.

Examples

curlvoicebot.sh
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"
# }
javascriptvoicebot.js
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");
pythonvoicebot.py
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())

Was this page helpful?