HTTP API
Start the server:
vaak serve --model faster-whisper:small --port 8000Interactive OpenAPI docs are at http://localhost:8000/docs.
Routes
Section titled “Routes”| 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 |
GET /health
Section titled “GET /health”{"status": "ok", "model": "faster-whisper:small"}GET /v1/models
Section titled “GET /v1/models”Returns the loaded spec and the list of specs the server could load, derived from list_backends().
POST /v1/transcribe
Section titled “POST /v1/transcribe”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"}POST /v1/audio/transcriptions
Section titled “POST /v1/audio/transcriptions”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 |
jsonreturns{"text": "..."}.textreturns the transcript astext/plain.verbose_jsonreturnstext,language,durationandsegmentswithstart,endandtext.
WS /v1/stream?language=hi
Section titled “WS /v1/stream?language=hi”- Open a WebSocket to
/v1/stream. Query parameters:language(optional, default auto). - Send audio as binary frames of PCM16LE, 16 kHz, mono. Any chunk size works; a few hundred milliseconds per frame is a good default.
- 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}
partialmay be revised.finalwill not change. - Send the text frame
EOSto flush. The server emits a lastfinalfor 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.
Examples
Section titled “Examples”curl -F file=@clip.mp3 -F language=hi http://localhost:8000/v1/transcribe
# OpenAI-compatible, plain text outcurl -F file=@clip.mp3 -F model=faster-whisper:small -F response_format=text \ http://localhost:8000/v1/audio/transcriptionsPython (requests)
Section titled “Python (requests)”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"])Python (openai SDK)
Section titled “Python (openai SDK)”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.
Node (fetch)
Section titled “Node (fetch)”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);WebSocket streaming (Python)
Section titled “WebSocket streaming (Python)”import asyncio, json, waveimport 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())WebSocket streaming (browser)
Section titled “WebSocket streaming (browser)”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.
Errors
Section titled “Errors”| 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": "..."}.