Skip to content

HTTP API

Start the server:

Terminal window
vaak serve --model faster-whisper:small --port 8000

Interactive OpenAPI docs are at http://localhost:8000/docs.

Method Path Purpose
GET /health Liveness check and the loaded model
GET /v1/models Loaded model plus every available spec
POST /v1/transcribe Transcribe one file, returns a Transcript
POST /v1/audio/transcriptions OpenAI-compatible drop-in
WS /v1/stream Stream PCM16 chunks, receive partial and final text
{"status": "ok", "model": "faster-whisper:small"}

Returns the loaded spec and the list of specs the server could load, derived from list_backends().

Multipart form fields:

Field Required Values
file yes Audio or video file, any format ffmpeg decodes
language no hi, en, …, or auto (default)
model no A spec string. Loads and caches that backend on first use

Response is a Transcript JSON object with the same fields as the Python dataclass:

{
"text": "...",
"language": "hi",
"segments": [{"start": 0.0, "end": 2.4, "text": "..."}],
"duration": 2.4,
"rtf": 0.08,
"backend": "faster-whisper",
"model": "small"
}

Accepts the same request shape as OpenAI’s audio transcription endpoint so existing clients work unchanged.

Field Values
file audio file
model a vaak spec, or any string to use the loaded model
language ISO 639-1 code, optional
response_format json (default), text, verbose_json
  • json returns {"text": "..."}.
  • text returns the transcript as text/plain.
  • verbose_json returns text, language, duration and segments with start, end and text.
  1. Open a WebSocket to /v1/stream. Query parameters: language (optional, default auto).
  2. Send audio as binary frames of PCM16LE, 16 kHz, mono. Any chunk size works; a few hundred milliseconds per frame is a good default.
  3. The server replies with JSON text frames:
    {"type": "partial", "text": "...", "start": 0.0, "end": 1.6}
    {"type": "final", "text": "...", "start": 0.0, "end": 4.2}
    partial may be revised. final will not change.
  4. Send the text frame EOS to flush. The server emits a last final for any buffered audio and then closes.

Streaming for Whisper backends is chunked: the server decodes a sliding window and emits a final when a segment ends. CTC backends emit partials more often.

Terminal window
curl -F file=@clip.mp3 -F language=hi http://localhost:8000/v1/transcribe
# OpenAI-compatible, plain text out
curl -F file=@clip.mp3 -F model=faster-whisper:small -F response_format=text \
http://localhost:8000/v1/audio/transcriptions
import requests
with open("clip.mp3", "rb") as f:
r = requests.post(
"http://localhost:8000/v1/transcribe",
files={"file": f},
data={"language": "hi"},
)
r.raise_for_status()
result = r.json()
print(result["text"], result["language"], result["rtf"])
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="unused")
with open("clip.mp3", "rb") as f:
out = client.audio.transcriptions.create(
model="faster-whisper:small",
file=f,
language="hi",
response_format="verbose_json",
)
print(out.text)
for seg in out.segments:
print(seg.start, seg.end, seg.text)

vaak does not check the API key. Pass any non-empty string to satisfy the SDK.

import { readFile } from "node:fs/promises";
const form = new FormData();
form.append("file", new Blob([await readFile("clip.mp3")]), "clip.mp3");
form.append("language", "hi");
const res = await fetch("http://localhost:8000/v1/transcribe", {
method: "POST",
body: form,
});
const result = await res.json();
console.log(result.text, result.language, result.rtf);
import asyncio, json, wave
import websockets
async def main():
uri = "ws://localhost:8000/v1/stream?language=hi"
async with websockets.connect(uri) as ws:
async def reader():
async for msg in ws:
ev = json.loads(msg)
print(ev["type"], ev["text"])
task = asyncio.create_task(reader())
with wave.open("clip16k.wav", "rb") as w:
assert w.getframerate() == 16000 and w.getnchannels() == 1 and w.getsampwidth() == 2
while frames := w.readframes(16000 // 2): # 500 ms per frame
await ws.send(frames)
await asyncio.sleep(0.5)
await ws.send("EOS")
await task
asyncio.run(main())
const ws = new WebSocket("ws://localhost:8000/v1/stream?language=en");
ws.onmessage = (e) => console.log(JSON.parse(e.data));
// Use an AudioWorklet to capture 16 kHz mono and convert Float32 -> Int16:
function toPCM16(float32) {
const out = new Int16Array(float32.length);
for (let i = 0; i < float32.length; i++) {
const s = Math.max(-1, Math.min(1, float32[i]));
out[i] = s < 0 ? s * 0x8000 : s * 0x7fff;
}
return out.buffer;
}
// ws.send(toPCM16(chunk)) for each chunk, then:
// ws.send("EOS");

When a browser page on another origin calls the API (for example the live demo on this site), the server must send CORS headers. Set the allowed origins when starting the server, or put the API behind a reverse proxy that adds them. Check vaak serve --help for the current flag.

Status Cause
400 Missing file, undecodable audio, unknown language code
404 Unknown model spec
413 File larger than the configured limit
503 Model still loading

Error bodies are {"detail": "..."}.