WebSocket Agent Box Dashboard →
⚡ Real-time TTS & STT · run it right here

Quickdial API

Text-to-speech and speech-to-text over REST and WebSocket. Try every endpoint live below — synthesize a voice and download the audio, or upload a clip and get a transcript.

Overview

The Quickdial API has two directions, each on REST and WebSocket. REST is a single request/response; WebSocket streams audio and text continuously for the lowest latency. All examples below are runnable against this server.

🔊  TTS  POST /v1/tts 📝  STT  POST /v1/stt 🎙  Voices  GET /v1/voices  Streaming  wss://…/v1/*/stream 📞  Agent Box  full voice agent

Authentication

Authenticate with a Bearer API key (create one in the dashboard). Paste it once here — the runnable examples on this page will use it. Leave it blank to use the free, rate-limited public demo instead.

🔑 Your API key used by every “Run” below
Stored only in this browser tab, never sent anywhere except your requests to this API.
auth
# Every REST call takes an Authorization header
curl https://api.quickdial.ai/v1/voices \
  -H "Authorization: Bearer qdl_live_your_key"

TTS Text-to-Speech

Send text and a voice, get audio back. Choose opus for the cheapest streaming, or wav for uncompressed PCM.

POST /v1/tts
▶ Run Text-to-Speech plays + downloads audio
Free demo caps text length; a key unlocks full requests.
tts.sh
curl https://api.quickdial.ai/v1/tts \
  -H "Authorization: Bearer qdl_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "text": "Hello world", "voice": "alba", "format": "wav" }' \
  --output hello.wav

Voices

List every available voice with its language and gender. This endpoint is public — no key required.

GET /v1/voices
▶ List voices
Also populates the voice picker above.
voices.sh
curl https://api.quickdial.ai/v1/voices \
  -H "Authorization: Bearer qdl_live_your_key"

STT Speech-to-Text

Upload an audio file and get an accurate transcript with word-level timestamps, powered by whisper.cpp.

POST /v1/stt
▶ Run Speech-to-Text
stt.sh
curl https://api.quickdial.ai/v1/stt \
  -H "Authorization: Bearer qdl_live_your_key" \
  -F audio=@speech.wav

WebSocket streaming

For the lowest latency, keep a socket open so audio and text flow continuously — no request/response round-trip per chunk. Same voices and models as REST, relayed byte-for-byte. Streaming isn't runnable from this page, but the protocol and full code are below.

DirectionText → SpeechSpeech → Text
You sendOne JSON message (text + voice)A stream of audio frames, then eos
You receiveBinary PCM audio framesA JSON transcript
Audio format16-bit PCM · mono · 24 kHz16-bit PCM · mono · 16 kHz

Authentication

Browsers can't set headers on a WebSocket handshake, so pass your key as a query parameter. Non-browser clients may send an Authorization header instead.

connect.js
// Browser — key in the query string
const ws = new WebSocket("wss://api.quickdial.ai/v1/tts/stream?key=qdl_live_your_key");

// Node / server — Authorization header on the handshake
const ws = new WebSocket("wss://api.quickdial.ai/v1/tts/stream", {
  headers: { Authorization: "Bearer qdl_live_your_key" },
});

TTS Text → Speech streaming

Open the socket, send one JSON message, and receive PCM audio frames as they're synthesized — start playing before the sentence finishes.

ClientServer
text →
{"text":"Hello world","voice":"alba"}
← text
{"type":"start","sample_rate":24000}
← binary ×N
16-bit PCM mono @ 24 kHz frames
← text
{"type":"end"}
tts-stream.js
const KEY = "qdl_live_your_key";
const ctx = new AudioContext({ sampleRate: 24000 });
let playhead = ctx.currentTime;

const ws = new WebSocket(`wss://api.quickdial.ai/v1/tts/stream?key=${KEY}`);
ws.binaryType = "arraybuffer";

ws.onopen = () =>
  ws.send(JSON.stringify({ text: "Hello from Quickdial, streamed in real time.", voice: "jane" }));

ws.onmessage = (e) => {
  if (typeof e.data === "string") {           // control frames are text
    const msg = JSON.parse(e.data);
    if (msg.type === "error") console.error(msg.message);
    return;
  }
  const pcm = new Int16Array(e.data);         // 16-bit PCM mono @ 24 kHz → Web Audio
  const buf = ctx.createBuffer(1, pcm.length, 24000);
  const ch = buf.getChannelData(0);
  for (let i = 0; i < pcm.length; i++) ch[i] = pcm[i] / 32768;
  const src = ctx.createBufferSource();
  src.buffer = buf; src.connect(ctx.destination);
  playhead = Math.max(playhead, ctx.currentTime);
  src.start(playhead);
  playhead += buf.duration;
};

STT Speech → Text streaming

Open the socket, (optionally) set params, stream 16 kHz PCM frames as you capture them, then send eos to receive the transcript.

ClientServer
← text
{"type":"start","sample_rate":16000}
binary → ×N
16-bit PCM mono @ 16 kHz frames
text →
{"type":"eos"}
← text
{"type":"transcript","text":"…","segments":[…]}
stt-stream.js
const KEY = "qdl_live_your_key";
const ws = new WebSocket(`wss://api.quickdial.ai/v1/stt/stream?key=${KEY}`);

ws.onopen = async () => {
  ws.send(JSON.stringify({ params: { language: "en" } }));      // optional config
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  const ac = new AudioContext({ sampleRate: 16000 });
  const src = ac.createMediaStreamSource(stream);
  const node = ac.createScriptProcessor(4096, 1, 1);
  src.connect(node); node.connect(ac.destination);
  node.onaudioprocess = (e) => {
    const f32 = e.inputBuffer.getChannelData(0);
    const pcm = new Int16Array(f32.length);                     // float32 → 16-bit PCM
    for (let i = 0; i < f32.length; i++) pcm[i] = Math.max(-1, Math.min(1, f32[i])) * 32767;
    if (ws.readyState === 1) ws.send(pcm.buffer);
  };
  setTimeout(() => { node.disconnect(); ws.send(JSON.stringify({ type: "eos" })); }, 5000);
};

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  if (msg.type === "transcript") console.log(msg.text, msg.segments);
};

Message reference

Server → client

TypeSent byPayload
startTTS & STT{ type, requestId, voice?, sample_rate } — session opened
binaryTTSRaw 16-bit little-endian PCM, mono, 24 kHz — one or more frames
readySTTAcknowledges a {params} config message
transcriptSTT{ type, text, language, duration_seconds, segments:[{start,end,text}] }
endTTS{ type, requestId } — synthesis complete
errorboth{ type, message }

Client → server

MessageEndpointMeaning
{ text, voice, params? }TTSSynthesize this text (one per socket message)
{ params }STTOptional config before audio (language, translate, …)
binarySTT16-bit PCM mono @ 16 kHz audio frame
{ type: "eos" }STTEnd of utterance → returns a transcript

AGENT BOX Voice agents, end to end

The endpoints above are the pieces. Agent Box is the whole thing assembled: a caller speaks, an agent answers out loud, and the conversation flows — speech-to-text, language model and speech synthesis running together on one machine, priced per connected minute.

📞  Call it  +1 (424) 567-8978 🌐  Try in browser  quickdial.ai 💵  $0.0098  per connected minute, all-in

All-in means everything — transcription, the model, and the voice. There is no separate orchestration fee and no per-token bill on top, because no third-party model vendor sits in the serving path.

i
Interruptible by design. Talk over the agent and it stops to listen mid-sentence, the way a person does — on the phone and in the browser.

Plans

PlanMonthlyPer connected minute
Starter$81.96¢
Premium$18$0.0098 — under a cent

Both plans include the web widget and phone answering; the per-minute rate is all-in (speech-to-text + language model + speech synthesis). Telephony is passed through at carrier cost. Manage your plan and payment method in your dashboard → Billing.

Getting started

  1. Sign in at agentbox.quickdial.ai with Google and pick your org name — that becomes <your-org>.agentbox.quickdial.ai.
  2. Give it your website. Agent Box crawls it and summarises what it learns into categories you can pick from.
  3. Choose the tools it may use, set the greeting, and finish — your agent is live.
  4. Embed the widget on your site, and/or connect a phone number.

Your dashboard

PageWhat it does
DashboardCall volume, outcomes, sentiment and peak hours, with period-over-period deltas.
ConversationsEvery finished call with its full transcript, summary, notes and bookmarks. Filter by status, outcome or customer.
PlaygroundTalk to your agent in the browser before you put it in front of customers.
EmbedYour snippet, publishable key, allowed domains, branding and session limits.
BillingPlan, payment methods and invoices.
SupportRaise a ticket and follow the thread without leaving the portal.

How it works

Every stage runs in one process on one box. That co-location is the latency design — there is no network hop between hearing you and answering.

pipeline
caller audio ──▶ streaming STT ──partial words──▶ language model (prefills while you speak)
                      │                                      │
                 voice activity detection ──▶ you stopped ──▶ generate ──▶ speech ──▶ caller

Two details do most of the work. Speculative prefill: the model starts reading your sentence while you are still saying it, so when you stop, most of the thinking is already done. Sentence streaming: speech synthesis begins on the first finished sentence rather than the whole reply, so audio starts while the rest is still being written.

Measured latency

From the moment you stop speaking to the first audio leaving the server, measured on the production box over a real carrier leg:

StageTypical
Turn detection — deciding you finished380 ms
Transcript finalised~20 ms
Model first token (with speculative prefill)~65 ms
First speech audio~100 ms
Total, single call~0.58 s (522–701 ms)
Total, two calls answering simultaneously~0.95 s worst case

Phone figures include the 8 kHz carrier leg. The browser demo runs on clean 16 kHz audio and is faster. Network time to the caller is on top and depends on their carrier.

Connect your own number

Point any Twilio voice number at your Agent Box and it answers. The box speaks Twilio's Media Streams protocol directly — bidirectional 8 kHz μ-law over a WebSocket.

  1. In the Twilio console open Phone Numbers → Manage → Active numbers and pick your number.
  2. Under Voice Configuration — not Messaging — set A call comes in to Webhook, HTTP POST.
  3. Paste your box's TwiML URL and save.
A call comes in
https://your-box.quickdial.ai/twiml

The endpoint returns TwiML that connects the call's media stream to the agent, carrying an auth token as a stream parameter:

response
<Response>
  <Connect>
    <Stream url="wss://your-box.quickdial.ai/media">
      <Parameter name="token" value="…"/>
    </Stream>
  </Connect>
</Response>
!
Two things catch people out: the webhook must sit under Voice (a number configured under Messaging never reaches the agent), and the token travels as a <Parameter> because Twilio does not forward query strings into <Stream>.

Boxes are provisioned per customer today — email hello@quickdial.ai for a number and endpoint. SIP trunking and self-serve provisioning are on the roadmap.

Embed the voice widget on your website

Add a tap-to-talk voice agent to any site with one line. A floating launcher (bottom-right) opens a live voice conversation grounded on your business's own knowledge — no backend to build.

Copy your snippet from your dashboard → Embed at https://<your-org>.agentbox.quickdial.ai. Paste it just before </body>:

index.html
<script async
        src="https://agentbox.quickdial.ai/widget.js"
        data-org="acme-salon"
        data-key="pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"></script>

That's it — it works in plain HTML, React, Vue, Angular, WordPress, Webflow, Shopify and anything else that lets you add a script tag. The widget renders inside a shadow DOM, so it never clashes with your site's styles.

Attributes

AttributeValue
data-orgYour AgentBox org (the subdomain label). Required.
data-keyYour publishable widget key (pk_…). Required. Safe to put in page HTML — see below.
data-endpointOptional. Overrides the host the script is served from (defaults to https://agentbox.quickdial.ai).

If your tag manager strips data-* attributes (or you load the script as a module), set the same three values on window.agentboxWidget before the script runs:

tag manager
<script>window.agentboxWidget = { org: "acme-salon", key: "pk_…" };</script>
<script async src="https://agentbox.quickdial.ai/widget.js"></script>

Security — publishable key + allowed domains

The widget uses a publishable key (pk_…), not your secret API key (ak_…). It only names your org and is revocable — it is meant to sit in public page HTML. Never embed your ak_… key in a website.

!
The real boundary is your allowed-domains list. Add the sites where the widget may run in the Embed page. The widget only initialises on those domains — it silently does nothing elsewhere, and the voice connection is refused for any other origin.
EntryMatches
https://www.acmesalon.comThat exact origin.
*.acmesalon.comThe domain and every subdomain — acmesalon.com, www.acmesalon.com, shop.acmesalon.com.
localhostLocal development on any port (http://localhost:3000, :8080, …).

Only https origins are accepted, except localhost / 127.0.0.1. Your own portal (<your-org>.agentbox.quickdial.ai) is always allowed, so the Playground works without listing it. Changes take effect within seconds.

Everything runs over HTTPS/WSS. The microphone prompt belongs to your own site; audio streams to the agent and back and is not stored by the widget. Per-org rate limits, a concurrency cap and a maximum session length protect against abuse. Need to invalidate a leaked key? Rotate key in the Embed page — the old one stops working immediately, everywhere.

Branding & limits

Style the launcher and panel from the Embed page — changes go live within seconds, no re-embedding:

SettingWhat it controls
Display nameThe name in the panel header (e.g. your business name).
Accent colourVisitor message bubbles and highlights.
Button labelText on the launcher pill (e.g. “Talk to us”).
Greeting bubbleThe teaser that floats in a few seconds after page load; dismissible, shown once per visit.
Logo URLWhite-label the launcher and header with your own mark (falls back to the Quickdial logo).
Position / themeBottom-right or bottom-left; light, dark or auto.
Session limitsMax session length, concurrent sessions and new sessions per minute — clamped to platform ceilings.
EnabledMaster switch. Off means the widget never mounts, anywhere.

Under the hood

PropertyValue
Audio16 kHz PCM in, 24 kHz out, streamed over WebSocket. Echo cancellation + noise suppression on.
IsolationClosed shadow DOM — no CSS or global leakage into or out of the host page.
SizeOne ~8.5 kB gzipped script (26 kB raw), zero dependencies, loaded async.
ConversationThe agent greets first, then it's hands-free turn-taking (barge-in supported) until the visitor closes it. A live mic-level ring, mute button and end-of-session countdown are built in.
AccessibilityKeyboard-navigable with a focus trap, aria-modal dialog, role="log" transcript, and full prefers-reduced-motion support.
ResilienceTransient busy/rate-limit closes retry automatically with backoff; the transcript survives closing and reopening the panel.

Every finished conversation — web or phone — appears in Conversations in your dashboard with its transcript.

Limits & behaviour

PropertyValue
Concurrent calls2 per box. A third caller hears a spoken busy message and is pointed at the web demo. More capacity = more boxes or a larger machine.
Audio formatPhone: 8 kHz μ-law, bidirectional, 20 ms frames. Browser: 16 kHz PCM in, 24 kHz out.
Barge-inEnabled. Speaking over the agent stops synthesis and clears buffered audio within a few hundred milliseconds; a cough or background noise will not.
Turn lengthA reply is always produced within 6 s of you speaking, even if a noisy line prevents clean turn detection.
Reply lengthCapped at two sentences — long monologues are wrong for a phone call.
LanguagesEnglish. Hindi is in development.
Tool callingNot yet exposed on the phone path — bookings, lookups and transfers are the next milestone.
i
Transcription on phone audio. The 8 kHz carrier channel is lossy, and unusual proper nouns are the first thing to suffer. If your use case leans on names or reference numbers, tell us — a higher-accuracy model is available at a small latency cost.

Integrations LiveKit Pipecat

Official plugins let you drop Quickdial STT & TTS into the popular voice-agent frameworks. Set QUICKDIAL_API_KEY in your environment, then swap in the components below.

LiveKit Agents Python v0.1.4 Node v0.1.4

agent.py
# pip install livekit-plugins-quickdial   (v0.1.4)
from livekit.agents import AgentSession
from livekit.plugins import quickdial, silero

session = AgentSession(
    stt=quickdial.STT(language="en"),   # POST /v1/stt (whisper.cpp)
    tts=quickdial.TTS(voice="alba"),    # POST /v1/tts, 24 kHz
    vad=silero.VAD.load(),
)
# export QUICKDIAL_API_KEY=qtts_live_...
agent.ts
// npm i @quickdial-ai/livekit-plugins-quickdial   (v0.1.4)
import * as quickdial from '@quickdial-ai/livekit-plugins-quickdial';

const session = new voice.AgentSession({
  stt: new quickdial.STT({ language: 'en' }),
  tts: new quickdial.TTS({ voice: 'alba' }),
});
// QUICKDIAL_API_KEY=qtts_live_...

Pipecat Python v0.1.0

pipeline.py
# pip install pipecat-quickdial   (v0.1.0)
from pipecat_quickdial import QuickdialSTTService, QuickdialTTSService

stt = QuickdialSTTService(language="en")   # QUICKDIAL_API_KEY from env
tts = QuickdialTTSService(voice="alba")
# add stt / tts to your Pipecat Pipeline([...])
i
Same Bearer key powers every plugin. All three wrap the same REST endpoints (/v1/tts, /v1/stt) — see the sections above for parameters and voices.

Errors & tips

  • 401 / handshake close 4401 — missing or invalid API key. Check the header or ?key= param.
  • 402 — free credits exhausted; add a payment method under Billing.
  • 429 — rate limit or demo cap hit; slow down or use a key.
  • Sample rates are fixed24 kHz for TTS, 16 kHz for STT, mono, 16-bit signed little-endian.
  • Reconnect with backoff — on unexpected close, retry with exponential backoff; sockets are stateless between utterances.
Building something? Create a key in the dashboard and start with 1000 free credits.