API reference
API v1RESTWebSocket

Streaming

Two WebSocket endpoints for when a round trip is too slow: push text in as it is generated, or push audio in as it is spoken.

Proposed: not yet implemented The two streaming endpoints exist and use the same bearer key, but the message contract below is a design proposal. Frame types, event schemas, heartbeat, close codes, reconnect and resume semantics are not frozen. Do not build against it yet.

When to use it

The REST endpoints already stream their response. Reach for WebSocket when you also need to stream the request: piping LLM tokens into speech before the sentence is complete, or transcribing a live microphone. For batch jobs, REST is simpler and just as fast.

Endpoints

wss
wss://api.quickdial.ai/v1/tts/stream
wss://api.quickdial.ai/v1/stt/stream

Authenticate with the same bearer key. Browsers cannot set headers on a WebSocket handshake, so the key may instead be passed as a query parameter, but only ever from a server or a short-lived proxy token, never from client-side code holding your live key.

handshake
# preferred, from a server
Authorization: Bearer $QUICKVOICE_KEY

# fallback where headers are impossible
wss://api.quickdial.ai/v1/tts/stream?key=qtts_live_xxxxxxxx

Text to speech

Send JSON text frames; receive binary audio frames. Send a flush message to force generation of everything buffered so far, and close when the utterance is finished.

Client to server
{ "type": "start", "voice": "azelma", "format": "opus" }
{ "type": "text", "value": "Your table is " }
{ "type": "text", "value": "confirmed for eight." }
{ "type": "flush" }
{ "type": "close" }
node
import WebSocket from "ws";

const ws = new WebSocket("wss://api.quickdial.ai/v1/tts/stream", {
  headers: { Authorization: `Bearer ${process.env.QUICKVOICE_KEY}` },
});

ws.on("open", () => {
  ws.send(JSON.stringify({ type: "start", voice: "azelma" }));
  // pipe tokens straight from your model as they arrive
  for (const token of tokens) {
    ws.send(JSON.stringify({ type: "text", value: token }));
  }
  ws.send(JSON.stringify({ type: "close" }));
});

ws.on("message", (frame, isBinary) => {
  if (isBinary) speaker.write(frame);   // audio chunk
  else console.log(JSON.parse(frame));  // status / usage
});

Speech to text

Send binary audio frames; receive JSON transcript frames. Partial results arrive continuously and are replaced as context improves. Render partial optimistically, then commit on final.

Server to client
{ "type": "partial", "text": "I found three" }
{ "type": "partial", "text": "I found three matching options" }
{ "type": "final",   "text": "I found three options that match your request.",
  "words": [ { "word": "I", "start_ms": 40, "end_ms": 210 } ] }

Expected audio

  • 16-bit signed PCM, mono.
  • 16 kHz sample rate.
  • Frames of roughly 20–100 ms. Smaller frames lower latency but raise overhead.

Backpressure and reconnects

  • Watch the socket's buffered amount before sending more. If it grows, the network is the bottleneck, not the API.
  • Idle sockets close after 60 seconds without a frame. Send a WebSocket ping to hold one open between utterances.
  • Reconnect with exponential backoff starting around 500 ms. A dropped socket never loses billing state; usage is recorded per completed request.
  • An error frame arrives before the close for anything actionable. See Errors & limits.