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.
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.
# 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.
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.wavVoices
List every available voice with its language and gender. This endpoint is public — no key required.
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.
curl https://api.quickdial.ai/v1/stt \
-H "Authorization: Bearer qdl_live_your_key" \
-F audio=@speech.wavWebSocket 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.
| Direction | Text → Speech | Speech → Text |
|---|---|---|
| You send | One JSON message (text + voice) | A stream of audio frames, then eos |
| You receive | Binary PCM audio frames | A JSON transcript |
| Audio format | 16-bit PCM · mono · 24 kHz | 16-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.
// 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.
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.
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
| Type | Sent by | Payload |
|---|---|---|
start | TTS & STT | { type, requestId, voice?, sample_rate } — session opened |
| binary | TTS | Raw 16-bit little-endian PCM, mono, 24 kHz — one or more frames |
ready | STT | Acknowledges a {params} config message |
transcript | STT | { type, text, language, duration_seconds, segments:[{start,end,text}] } |
end | TTS | { type, requestId } — synthesis complete |
error | both | { type, message } |
Client → server
| Message | Endpoint | Meaning |
|---|---|---|
{ text, voice, params? } | TTS | Synthesize this text (one per socket message) |
{ params } | STT | Optional config before audio (language, translate, …) |
| binary | STT | 16-bit PCM mono @ 16 kHz audio frame |
{ type: "eos" } | STT | End 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.
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.
Plans
| Plan | Monthly | Per connected minute |
|---|---|---|
| Starter | $8 | 1.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
- Sign in at agentbox.quickdial.ai with Google and pick your org name — that becomes
<your-org>.agentbox.quickdial.ai. - Give it your website. Agent Box crawls it and summarises what it learns into categories you can pick from.
- Choose the tools it may use, set the greeting, and finish — your agent is live.
- Embed the widget on your site, and/or connect a phone number.
Your dashboard
| Page | What it does |
|---|---|
| Dashboard | Call volume, outcomes, sentiment and peak hours, with period-over-period deltas. |
| Conversations | Every finished call with its full transcript, summary, notes and bookmarks. Filter by status, outcome or customer. |
| Playground | Talk to your agent in the browser before you put it in front of customers. |
| Embed | Your snippet, publishable key, allowed domains, branding and session limits. |
| Billing | Plan, payment methods and invoices. |
| Support | Raise 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.
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:
| Stage | Typical |
|---|---|
| Turn detection — deciding you finished | 380 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.
- In the Twilio console open Phone Numbers → Manage → Active numbers and pick your number.
- Under Voice Configuration — not Messaging — set A call comes in to Webhook,
HTTP POST. - Paste your box's TwiML URL and save.
https://your-box.quickdial.ai/twimlThe endpoint returns TwiML that connects the call's media stream to the agent, carrying an auth token as a stream parameter:
<Response>
<Connect>
<Stream url="wss://your-box.quickdial.ai/media">
<Parameter name="token" value="…"/>
</Stream>
</Connect>
</Response><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>:
<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
| Attribute | Value |
|---|---|
data-org | Your AgentBox org (the subdomain label). Required. |
data-key | Your publishable widget key (pk_…). Required. Safe to put in page HTML — see below. |
data-endpoint | Optional. 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:
<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.
| Entry | Matches |
|---|---|
https://www.acmesalon.com | That exact origin. |
*.acmesalon.com | The domain and every subdomain — acmesalon.com, www.acmesalon.com, shop.acmesalon.com. |
localhost | Local 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:
| Setting | What it controls |
|---|---|
| Display name | The name in the panel header (e.g. your business name). |
| Accent colour | Visitor message bubbles and highlights. |
| Button label | Text on the launcher pill (e.g. “Talk to us”). |
| Greeting bubble | The teaser that floats in a few seconds after page load; dismissible, shown once per visit. |
| Logo URL | White-label the launcher and header with your own mark (falls back to the Quickdial logo). |
| Position / theme | Bottom-right or bottom-left; light, dark or auto. |
| Session limits | Max session length, concurrent sessions and new sessions per minute — clamped to platform ceilings. |
| Enabled | Master switch. Off means the widget never mounts, anywhere. |
Under the hood
| Property | Value |
|---|---|
| Audio | 16 kHz PCM in, 24 kHz out, streamed over WebSocket. Echo cancellation + noise suppression on. |
| Isolation | Closed shadow DOM — no CSS or global leakage into or out of the host page. |
| Size | One ~8.5 kB gzipped script (26 kB raw), zero dependencies, loaded async. |
| Conversation | The 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. |
| Accessibility | Keyboard-navigable with a focus trap, aria-modal dialog, role="log" transcript, and full prefers-reduced-motion support. |
| Resilience | Transient 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
| Property | Value |
|---|---|
| Concurrent calls | 2 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 format | Phone: 8 kHz μ-law, bidirectional, 20 ms frames. Browser: 16 kHz PCM in, 24 kHz out. |
| Barge-in | Enabled. Speaking over the agent stops synthesis and clears buffered audio within a few hundred milliseconds; a cough or background noise will not. |
| Turn length | A reply is always produced within 6 s of you speaking, even if a noisy line prevents clean turn detection. |
| Reply length | Capped at two sentences — long monologues are wrong for a phone call. |
| Languages | English. Hindi is in development. |
| Tool calling | Not yet exposed on the phone path — bookings, lookups and transfers are the next milestone. |
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
# 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_...// 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
# 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([...])/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 fixed — 24 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.