Self-hosted, OpenAI-compatible speech-to-text (STT) HTTP server in Rust. Drop-in replacement for the OpenAI Whisper API — runs on a single ARM64 CPU, no GPU. 8 languages. Real-time WebSocket streaming. Word-level timestamps. Built on sherpa-onnx + Moonshine v2.
Built for voice AI agents, live captioning, edge deployments, and privacy-sensitive transcription on Oracle Free Tier / Hetzner CAX / Raspberry Pi 5.
Quick start · API · Languages · Benchmarks · Metrics · Limitations
| ox-whisper | faster-whisper | whisper.cpp | OpenAI Whisper API | |
|---|---|---|---|---|
| Runtime | Rust + axum | Python | C++ | Cloud |
| Protocol | HTTP + WebSocket | library | library / examples | HTTPS |
| OpenAI-compatible API | yes | no | partial | n/a |
| Real-time WebSocket | yes | no | no | no |
| CPU RTF (aarch64) | ~0.05 | ~0.075 (tiny) | ~0.13 (tiny) | n/a |
| GPU required | no | optional | optional | n/a |
ox-whisper wins on a narrow but real wedge: a polished HTTP/WebSocket server with OpenAI-compatible API, sub-100 ms RTF on a CPU, no GPU, ARM64-native. Pair it with Pipecat / LiveKit / Vapi for self-hosted voice agents.
curl -fsSL https://raw.githubusercontent.com/anatolykoptev/ox-whisper/master/install.sh | bashLinux aarch64. Docker auto-installed if missing. Pulls ghcr.io/anatolykoptev/ox-whisper:latest, fetches ~463 MB models, starts on :8092 (HTTP/WS) + :9092 (metrics).
curl http://localhost:8092/health
curl http://localhost:8092/v1/modelsdocker run · docker compose · download models manually
docker run -d --name ox-whisper --restart unless-stopped \
-p 127.0.0.1:8092:8092 -p 127.0.0.1:9092:9092 \
-v $(pwd)/models/en:/models:ro \
-v $(pwd)/models/ru:/ru-models:ro \
-v $(pwd)/models/vad:/vad:ro \
-v $(pwd)/models/punct-en:/punct:ro \
ghcr.io/anatolykoptev/ox-whisper:latestservices:
ox-whisper:
image: ghcr.io/anatolykoptev/ox-whisper:latest
restart: unless-stopped
ports:
- "127.0.0.1:8092:8092"
- "127.0.0.1:9092:9092"
environment:
MOONSHINE_THREADS: "4"
POOL_SIZE: "2"
volumes:
- ./models/en:/models:ro
- ./models/ru:/ru-models:ro
- ./models/vad:/vad:ro
- ./models/punct-en:/punct:rocurl -fsSL https://raw.githubusercontent.com/anatolykoptev/ox-whisper/master/scripts/download-models.sh | bashDrop-in replacement for openai.audio.transcriptions.create. Point your OpenAI SDK at http://localhost:8092/v1.
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8092/v1", api_key="unused")
result = client.audio.transcriptions.create(
model="whisper-1", file=open("recording.mp3", "rb"), language="en"
)
print(result.text)import asyncio, websockets, json
async def stream():
async with websockets.connect("ws://localhost:8092/v1/listen?language=en") as ws:
await ws.send(pcm_16khz_mono_chunk) # repeat
async for msg in ws:
print(json.loads(msg)) # {"type":"partial"|"final","text":"..."}
asyncio.run(stream())curl -X POST http://localhost:8092/transcribe \
-H 'Content-Type: application/json' \
-d '{"audio_path":"/data/recording.wav","language":"en"}'Response: { "text", "duration_ms", "words":[{"word","start","end","confidence"}], "confidence" }. Optional fields: vad, punctuate, max_chunk_len.
curl -F file=@recording.mp3 -F language=en http://localhost:8092/transcribe/uploadcurl -N -F file=@long.mp3 -F language=en http://localhost:8092/transcribe/stream
# data: {"index":0,"text":"...","type":"chunk"}
# data: {"type":"done"}| Code | Language | Model |
|---|---|---|
en |
English | Moonshine v2 Base |
ru |
Russian | GigaAM v3 / Zipformer-RU INT8 (auto-detected) |
ar |
Arabic | Moonshine v2 Base |
es |
Spanish | Moonshine v2 Base |
ja |
Japanese | Moonshine v2 Base |
uk |
Ukrainian | Moonshine v2 Base |
vi |
Vietnamese | Moonshine v2 Base |
zh |
Chinese | Moonshine v2 Base |
Need 99 languages? Use whisper-large-v3 instead — ox-whisper trades coverage for speed and CPU footprint.
Oracle Cloud A1 free tier, 4 ARM threads, CPU-only. Best of 3 runs after warmup.
| Audio length | Language | Latency | RTF |
|---|---|---|---|
| 9.2 s | EN (Moonshine v2) | 215 ms | 0.023 |
| 9.2 s | RU (Zipformer) | 338 ms | 0.037 |
| 123 s | EN, VAD, 44 s speech | 2.0 s | 0.05 |
| 123 s | RU, VAD, 44 s speech | 2.4 s | 0.06 |
For comparison on the same box: faster-whisper tiny int8 ~0.075 RTF, whisper.cpp tiny-q8_0 ~0.13 RTF.
| Variable | Default | Description |
|---|---|---|
MOONSHINE_PORT |
8092 |
HTTP/WS port |
MOONSHINE_THREADS |
4 |
ONNX inference threads |
POOL_SIZE |
2 |
Recognizer instances per language |
MAX_AUDIO_DURATION_S |
0 |
Max input length, 0=unlimited |
VAD_MIN_DURATION_S |
10 |
Auto-enable VAD above this length |
OXWHISPER_PROM_PORT |
9092 |
Prometheus metrics port |
Model paths and tuning knobs
| Variable | Default |
|---|---|
MOONSHINE_MODELS_DIR |
/models |
ZIPFORMER_RU_DIR |
/ru-models |
SILERO_VAD_MODEL |
/vad/silero_vad.onnx |
PUNCT_MODEL |
/punct/model.int8.onnx |
PUNCT_VOCAB |
/punct/bpe.vocab |
VAD_THRESHOLD |
0.5 |
VAD_MIN_SILENCE_S |
0.5 |
VAD_SPEECH_PAD_S |
0.05 |
VAD_MIN_SPEECH_S |
0.25 |
VAD_MAX_CHUNK_S |
20 |
MAX_CHUNK_S |
20 |
HALLUCINATION_THRESHOLD |
2.4 |
MAX_BODY_SIZE_MB |
50 |
ONNX_PROVIDER |
cpu |
| Model | Languages | Size | Source |
|---|---|---|---|
| Moonshine v2 Base | AR · EN · ES · JA · UK · VI · ZH | 135 MB | HF |
| Zipformer-RU INT8 | RU | 67 MB | sherpa-onnx |
| GigaAM v3 RNNT | RU | ~220 MB | HF |
| Silero VAD | — | 0.6 MB | bundled |
| Punctuation CNN-BiLSTM | EN, RU | 7 MB | sherpa-onnx |
Downloaded by scripts/download-models.sh (run automatically by install.sh).
Prometheus exposition on port 9092. Scrape:
scrape_configs:
- job_name: ox-whisper
static_configs:
- targets: ['ox-whisper:9092']| Metric | Type | Labels |
|---|---|---|
oxwhisper_requests_total |
counter | endpoint, status |
oxwhisper_request_duration_seconds |
histogram | endpoint |
oxwhisper_transcribe_duration_seconds |
histogram | lang |
oxwhisper_audio_duration_seconds |
histogram | — |
oxwhisper_vad_speech_ratio |
gauge | lang |
oxwhisper_chunks_total |
counter | lang |
oxwhisper_hallucination_rejected_total |
counter | lang |
oxwhisper_recognizer_pool_size · _busy |
gauge | lang |
oxwhisper_ws_active_connections |
gauge | — |
- aarch64 only. Pre-built
.solibs are ARM64; x86_64 needs source rebuild ofvendor/sherpa-rs-sys. - No streaming for
/transcribe. Whole-file responses only. Use/transcribe/stream(SSE) or/v1/listen(WebSocket) for incremental output. - No auth. Bind to
0.0.0.0; put behind nginx / Caddy if exposed to the network. - 8 languages. For broader coverage, use
whisper-large-v3via faster-whisper or speaches. - EN punctuation only via the CNN-BiLSTM model. RU punctuation comes from GigaAM v3 when loaded.
git clone https://github.com/anatolykoptev/ox-whisper && cd ox-whisper
cargo build --release # native aarch64
docker build -t ox-whisper . # BuildKit + cargo-chef layer cacheCI publishes ghcr.io/anatolykoptev/ox-whisper:vX.Y.Z on every v* tag.
MIT. Built on sherpa-onnx, Moonshine, Silero VAD.