Swedish transcription routed to the dedicated KB-Whisper worker.
- Timestamps
- Segment
- VAD
- Silero 6.2
API reference
A model-neutral HTTP boundary for local Swedish and English transcription. The API accepts complete audio or video files and also exposes endpointed realtime microphone transcription.
http://localhost:8080multipart/form-data
Every transcription request must specify sv or
en. Automatic language selection is intentionally
unsupported for short utterances.
Getting started
Download the pinned models, start the stack, then send a multipart request.
cp .env.example .env
bun run models:download
docker compose up --build
curl http://localhost:8080/readyz
curl http://localhost:8080/v1/audio/transcriptions \
-F file=@recording.wav \
-F language=sv
Discovery
Public aliases stay stable even when the worker implementation
changes. Discover them from GET /v1/models. Fast
aliases prioritize lower latency with reduced accuracy; small
aliases provide higher quality and can be much slower on CPU-only
hosts.
Swedish transcription routed to the dedicated KB-Whisper worker.
English transcription routed to the dedicated English worker.
Lower-latency Swedish transcription using the Fast mode.
Lower-latency English transcription using the Fast mode.
Operations
/healthz
Process-only liveness. It does not contact model workers.
{"status":"ok"}
/readyz
Checks both required workers concurrently. Returns
503 if either worker is unavailable.
{
"status": "ready",
"workers": { "sv": "ready", "en": "ready" }
}
Core endpoint
/v1/audio/transcriptions
Transcribes one audio or video file and returns normalized output.
| Field | Type | Required | Description |
|---|---|---|---|
file |
media file | Yes | One non-empty WAV, MP3, M4A/MP4, MOV, MKV, FLAC, OGG, or WebM file. Supported video containers are decoded by the worker. |
language |
sv | en |
Yes | Selects the dedicated worker. |
model |
string | No | Stable alias. If supplied, it must agree with the language. |
prompt |
string | No | Context or expected vocabulary, up to 1,000 characters. |
response_format |
enum | No |
json, verbose_json, or
text. Defaults to JSON.
|
temperature |
number | No | OpenAI-compatible decoding value from 0 to 1. |
const form = new FormData();
form.append("file", audioFile);
form.append("language", "en");
form.append("response_format", "verbose_json");
const response = await fetch("/v1/audio/transcriptions", {
method: "POST",
body: form,
});
const transcript = await response.json();
Studio's Subtitles mode requests verbose_json, turns
normalized segments into WebVTT and SRT in the browser, and
synchronizes them with the uploaded audio or video. These are Studio
exports—not additional API response formats—and captions are not
burned into the video.
Live audio
/v1/audio/transcriptions/realtime
Streams mono PCM16 audio and emits accumulated transcript updates after detected speech pauses. This is endpointed Whisper inference, not token-by-token decoder streaming.
{
"type": "session.start",
"language": "en",
"model": "english-small",
"sample_rate": 48000,
"temperature": 0
}
After session.ready, send mono signed PCM16
little-endian binary frames. Finish with
{"type":"session.stop"}. The gateway drains queued
utterances before finalizing.
session.ready
speech.started
speech.stopped
transcript.partial
transcript.final
session.completed
Partials contain accumulated text and absolute normalized segments. Sessions do not resume after disconnect. Model speed and queueing determine how far transcript updates lag behind speech.
Schemas
{
"text": "Hej, hur mår du?",
"language": "sv",
"duration": 1.84,
"model": "swedish-small"
}
{
"text": "Hej, hur mår du?",
"language": "sv",
"duration": 1.84,
"model": "swedish-small",
"segments": [
{ "id": 0, "start": 0.12, "end": 1.72, "text": "Hej, hur mår du?" }
]
}
The text format returns only UTF-8 transcript text with
text/plain.
Failure contract
Errors have stable public codes and a request ID. Worker stderr, paths, stack traces, audio, and prompts are never included.
{
"error": {
"code": "invalid_language",
"message": "language must be either sv or en",
"request_id": "3e80…"
}
}
invalid_requestinvalid_languageunsupported_media_typefile_too_largeworker_unavailabletranscription_timeouttranscription_failedrate_limited
Security
Authentication is disabled when API_KEYS is empty. When
configured, HTTP /v1/* routes require a Bearer key.
Browser realtime clients send the key inside their first
session.start message because WebSocket APIs cannot set
an Authorization header. Health and readiness remain public.
Authorization: Bearer your-local-key
Runtime
| Variable | Default | Purpose |
|---|---|---|
PORT |
8080 | Gateway container port |
GATEWAY_PORT |
8080 | Host port in Compose |
WORKER_TIMEOUT_MS |
120000 | Transcription timeout |
READINESS_TIMEOUT_MS |
2000 | Per-worker readiness timeout |
MAX_UPLOAD_BYTES |
26214400 | Maximum audio upload |
API_KEYS |
empty | Comma-separated Bearer keys |
WORKER_CONCURRENCY |
1 | Concurrent inference per worker |
WORKER_QUEUE_SIZE |
4 | Queued requests per worker |
REALTIME_MAX_SESSIONS |
2 | Concurrent realtime connections |
REALTIME_MAX_SESSION_MS |
120000 | Maximum audio duration per live session |
REALTIME_MAX_CHUNK_BYTES |
65536 | Maximum binary WebSocket frame |
Deployment
The primary stack contains a Bun gateway and two private, long-lived CPU workers. Only the gateway port is published.
Both images build natively for Linux amd64 and
arm64. Keep worker ports private and place TLS in front
of the gateway for public deployments.
Boundaries
Ready to send a real request?
Open Transcription Studio