05Reference · Surface

Text-voice — open a session, hold a socket.

Text-voice gives you a browser-grade voice agent over a single WebSocket. POST returns a signed token that the WS gateway accepts for a few minutes; pipe mic in, speakers out.

Endpoint

POST/api/public/v1/textvoice/session

Authenticates with your Bearer key, gates on the textvoice scope, mints an HMAC-signed short-lived token, returns the ready-to-use WebSocket URL.

Request

Body (JSON, optional)

FieldTypeNotes
flow_idstringOptional. The UUID of a conversation flow you've designed in the dashboard. Omit for a free-form conversation against the default agent.

Response · 200

JSON

FieldTypeNotes
ws_urlstringWebSocket URL with the signed public_token already attached as a query param. Connect to this directly.
ws_tokenstringSame token as in the URL, exposed separately so you can move it into a header if you prefer.
expires_atISO 8601 stringWhen the token stops being accepted by the WS gateway. Open the socket immediately.
surface"textvoice" | "voicebot"Echoes the surface you called — useful for client-side switching.

WebSocket protocol

Connect to the returned ws_url. The token is a query parameter — do not strip it. Frames are a mix of JSON control messages and binary audio.

WebSocket frames (server → client)

FieldTypeNotes
{ type: "transcript_partial", text }JSONStreaming ASR partials — overwrite the previous partial in your UI.
{ type: "transcript_final", text }JSONFinalised ASR turn.
{ type: "agent_speech", text }JSONWhat the agent said — for transcript display.
audio framesbinaryOpus-encoded TTS audio frames. Pipe into a WebAudio decoder.
{ type: "end" }JSONBackend will close the socket cleanly after this frame.

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

curltextvoice.sh
curl -X POST https://www.vaanilabs.in/api/public/v1/textvoice/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": "textvoice"
# }
javascripttextvoice.js
const apiKey = process.env.VAANI_API_KEY;

const res = await fetch(
"https://www.vaanilabs.in/api/public/v1/textvoice/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");
pythontextvoice.py
import os, asyncio, requests, websockets

API_KEY = os.environ["VAANI_API_KEY"]

r = requests.post(
"https://www.vaanilabs.in/api/public/v1/textvoice/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?