Getting started
API v1RESTWebSocket

Quickstart

From a fresh key to playable audio in about a minute. Every snippet is complete: paste it, set your key, run it.

Preview The request and response shapes are confirmed; some fields and limits may change before general availability.

1. Get a key

Create a key in the dashboard. New accounts start with 100,000 free characters, so nothing on this page costs anything. Export it once and every sample below runs as written:

shell
export QUICKVOICE_KEY="qtts_live_xxxxxxxxxxxxxxxxxxxx"

2. Turn text into speech

The response body is the audio itself, and it starts arriving before synthesis finishes. Every sample sets an explicit timeout, because an unbounded request is the most common way a voice integration hangs in production.

POST /v1/tts
curl https://api.quickdial.ai/v1/tts \
  -H "Authorization: Bearer $QUICKVOICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Table confirmed.","voice":"azelma"}' \
  --max-time 30 \
  --output speech.opus
import os, requests

r = requests.post(
    "https://api.quickdial.ai/v1/tts",
    headers={"Authorization": f"Bearer {os.environ['QUICKVOICE_KEY']}"},
    json={"text": "Table confirmed.", "voice": "azelma"},
    stream=True,
    timeout=(3.05, 30),   # connect, read - never leave this unset
)
r.raise_for_status()

with open("speech.opus", "wb") as f:
    for chunk in r.iter_content(4096):
        f.write(chunk)
import { writeFile } from "node:fs/promises";

const ac = new AbortController();
const t = setTimeout(() => ac.abort(), 30000);  // bound every call

try {
  const res = await fetch("https://api.quickdial.ai/v1/tts", {
    method: "POST",
    signal: ac.signal,
    headers: {
      Authorization: "Bearer " + process.env.QUICKVOICE_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ text: "Table confirmed.", voice: "azelma" }),
  });
  if (!res.ok) throw new Error("tts " + res.status);
  await writeFile("speech.opus", Buffer.from(await res.arrayBuffer()));
} finally { clearTimeout(t); }
import java.net.URI;
import java.net.http.*;
import java.nio.file.Path;
import java.time.Duration;

HttpClient client = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(5))
    .build();

HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.quickdial.ai/v1/tts"))
    .timeout(Duration.ofSeconds(30))
    .header("Authorization", "Bearer " + System.getenv("QUICKVOICE_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(
        "{\"text\":\"Table confirmed.\",\"voice\":\"azelma\"}"))
    .build();

HttpResponse<Path> res = client.send(
    req, HttpResponse.BodyHandlers.ofFile(Path.of("speech.opus")));

if (res.statusCode() != 200)
    throw new IllegalStateException("tts " + res.statusCode());
package main

import (
  "bytes"
  "context"
  "io"
  "net/http"
  "os"
  "time"
)

func main() {
  ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  defer cancel()

  body := []byte("{\"text\":\"Table confirmed.\",\"voice\":\"azelma\"}")
  req, _ := http.NewRequestWithContext(ctx, "POST",
    "https://api.quickdial.ai/v1/tts", bytes.NewReader(body))
  req.Header.Set("Authorization", "Bearer "+os.Getenv("QUICKVOICE_KEY"))
  req.Header.Set("Content-Type", "application/json")

  res, err := http.DefaultClient.Do(req)
  if err != nil { panic(err) }
  defer res.Body.Close()

  f, _ := os.Create("speech.opus")
  defer f.Close()
  io.Copy(f, res.Body)
}

3. Turn speech into text

Send the audio as multipart form data.

POST /v1/stt
curl https://api.quickdial.ai/v1/stt \
  -H "Authorization: Bearer $QUICKVOICE_KEY" \
  -F "audio=@meeting.wav" \
  --max-time 120
import os, requests

with open("meeting.wav", "rb") as f:
    r = requests.post(
        "https://api.quickdial.ai/v1/stt",
        headers={"Authorization": f"Bearer {os.environ['QUICKVOICE_KEY']}"},
        files={"audio": f},
        timeout=(3.05, 120),
    )
r.raise_for_status()
print(r.json()["text"])
import { openAsBlob } from "node:fs";

const form = new FormData();
form.append("audio", await openAsBlob("meeting.wav"), "meeting.wav");

const res = await fetch("https://api.quickdial.ai/v1/stt", {
  method: "POST",
  headers: { Authorization: "Bearer " + process.env.QUICKVOICE_KEY },
  body: form,
});
if (!res.ok) throw new Error("stt " + res.status);
console.log((await res.json()).text);
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
import java.time.Duration;

// java.net.http ships no multipart builder, so assemble the body
String boundary = "----quickvoice" + System.currentTimeMillis();
byte[] audio = Files.readAllBytes(Path.of("meeting.wav"));

ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(("--" + boundary + "\r\n"
  + "Content-Disposition: form-data; name=\"audio\"; filename=\"meeting.wav\"\r\n"
  + "Content-Type: audio/wav\r\n\r\n").getBytes());
out.write(audio);
out.write(("\r\n--" + boundary + "--\r\n").getBytes());

HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.quickdial.ai/v1/stt"))
    .timeout(Duration.ofSeconds(120))
    .header("Authorization", "Bearer " + System.getenv("QUICKVOICE_KEY"))
    .header("Content-Type", "multipart/form-data; boundary=" + boundary)
    .POST(HttpRequest.BodyPublishers.ofByteArray(out.toByteArray()))
    .build();

HttpResponse<String> res = HttpClient.newHttpClient()
    .send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
package main

import (
  "bytes"
  "context"
  "io"
  "mime/multipart"
  "net/http"
  "os"
  "time"
)

func main() {
  var buf bytes.Buffer
  w := multipart.NewWriter(&buf)
  fw, _ := w.CreateFormFile("audio", "meeting.wav")
  f, _ := os.Open("meeting.wav")
  io.Copy(fw, f)
  f.Close()
  w.Close()

  ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
  defer cancel()

  req, _ := http.NewRequestWithContext(ctx, "POST",
    "https://api.quickdial.ai/v1/stt", &buf)
  req.Header.Set("Authorization", "Bearer "+os.Getenv("QUICKVOICE_KEY"))
  req.Header.Set("Content-Type", w.FormDataContentType())

  res, _ := http.DefaultClient.Do(req)
  defer res.Body.Close()
  out, _ := io.ReadAll(res.Body)
  os.Stdout.Write(out)
}

The transcript comes back as text. Further response fields are proposed but not frozen; see Speech to Text.

Then what

For a live microphone or an agent turn you want the streaming endpoints. They exist, but their message contract is not published yet, so Streaming sets out exactly what is and is not settled. If you are on LiveKit or Pipecat, use the official plugins rather than writing the transport yourself.

plugins
pip install livekit-plugins-quickdial
pip install pipecat-quickdial