A self-hosted voice-to-text transcription API service using Whisper AI. Supports multiple audio formats including WAV, MP3, OGG (Vorbis), and Opus.
- Speech-to-text transcription using Whisper AI
- Support for multiple audio formats:
- WAV (16-bit PCM)
- MP3 (MPEG Layer-3)
- FLAC (Free Lossless Audio Codec)
- AAC (Advanced Audio Coding) - metadata parsing only
- Speex (Speech codec) - metadata parsing only
- OGG/Vorbis
- OGG/Opus
- Automatic format detection and conversion:
- Sample rate conversion to 16kHz
- Mono channel conversion
- Bit depth normalization
- Rich metadata for each transcription:
- Word-level timing
- Confidence scores
- Audio format details
- Performance metrics
- Prometheus monitoring with detailed metrics
- Swagger API documentation
- Authentication:
- Bearer token authentication
- Multi-layer token validation:
- Redis cache (fast)
- PostgreSQL database (persistent)
- Static tokens (fallback)
- Configurable token expiration
- Optional authentication mode
Core requirements:
- Go 1.20 or later
- Whisper model file (
ggml-base.bin) from ggerganov/whisper.cpp on Hugging Face
For test fixtures generation:
- Python 3.x
- FFmpeg
- gTTS (
pip install gtts) - pydub (
pip install pydub)
Additional requirements for authentication:
- Redis (optional, for token caching)
- PostgreSQL (optional, for token storage)
-
Clone the repository:
git clone https://github.com/VA7DBI/whisperAPI.git cd whisperAPI -
Download the Whisper model:
mkdir models curl -L https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin -o models/ggml-base.bin
-
Optional: download local Parakeet backend models (for
engine=parakeetdeployments):
make models # int8 Parakeet models (default)
# or
make models-fp32 # fp32 Parakeet models-
Install dependencies:
go mod download
-
Generate test fixtures (optional):
cd test_fixtures python makewave.py "This is a test audio file" cd ..
-
Run API tests (optional):
go test -v ./...Note, if whisper.h is not in your default system include path, you may need to use the CGO_*FLAGS environment variables before running your go test or go build, for example:
export CGO_CFLAGS="-I/usr/local/include"
export CGO_LDFLAGS="-L/usr/local/lib"-
Build and run:
go build ./whisperAPI
- Create the PostgreSQL token table:
CREATE TABLE api_tokens (
token VARCHAR(255) PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
valid_until TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
scopes TEXT[]
);
-- Example token insertion
INSERT INTO api_tokens (token, user_id, valid_until, scopes)
VALUES (
'your-api-token-here',
'user123',
NOW() + INTERVAL '30 days',
ARRAY['transcribe', 'read']
);- Configure authentication in
config.yaml:
auth:
enabled: true # Enable/disable auth
tokens: # Static fallback tokens
- "your-static-token"
redis:
enabled: true
host: "localhost"
port: 6379
db: 0
password: ""
key_ttl: 3600 # Cache TTL in seconds
postgres:
enabled: true
host: "localhost"
port: 5432
user: "postgres"
password: "secret"
dbname: "whisperapi"
table: "api_tokens"
query: "SELECT EXISTS(SELECT 1 FROM api_tokens WHERE token = $1 AND valid_until > NOW())"The API supports configurable CORS via the cors section in config.yaml.
Development (allow-all origins):
cors:
enabled: true
allow_all_origins: true
allowed_origins: []
allowed_methods:
- GET
- POST
- PUT
- PATCH
- DELETE
- HEAD
- OPTIONS
allowed_headers:
- Origin
- Content-Type
- Content-Length
- Authorization
- X-API-Key
expose_headers:
- Content-LengthProduction (restrict to known frontend origins):
cors:
enabled: true
allow_all_origins: false
allowed_origins:
- "https://app.example.com"
- "https://admin.example.com"
allowed_methods:
- GET
- POST
- OPTIONS
allowed_headers:
- Content-Type
- Authorization
- X-API-Key
expose_headers:
- Content-LengthNotes:
- Browser preflight (
OPTIONS) requests are handled automatically when CORS is enabled. - If
allow_all_originsisfalse, requests are only accepted fromallowed_origins. - Keep
allow_all_origins: truefor development only.
Model download notes:
make modelsdownloads Parakeet int8 model artifacts to./models.make models-silero-vaddownloadssilero_vad.onnxand verifies it with a pinned sha256 checksum.- These targets do not install ONNX Runtime system libraries.
Swagger UI for API documentation.
Health check endpoint.
Response:
{
"status": "ok",
"parakeet_enabled": true,
"parakeet_mode": "embedded"
}Prometheus metrics endpoint providing:
- Request counts by format and status
- Processing durations (histogram)
- Audio durations (histogram)
- Memory usage (gauge)
- CPU/GPU time (histogram)
Upload an audio file for transcription.
Request:
- Method: POST
- Content-Type: multipart/form-data
- Form field: "audio" (file)
- Optional form field: "engine" (string, defaults to "whisper")
- Optional form field: "diarize" (boolean, defaults to false; supported for
engine=whisper) - Optional form field: "expected_speakers" (integer, 2-8, requires
diarize=true) - Supported formats: WAV, OGG/Vorbis, OGG/Opus
- Supported engines: whisper, parakeet
Parakeet engine configuration (optional):
parakeet:
endpoint: "" # Optional external endpoint. Leave empty to use in-process embedded runtime.
timeout_seconds: 30
language: en
model: parakeet-tdt-0.6b
embedded:
enabled: true
models_dir: models
workers: 2Embedded mode behavior:
- If
parakeet.endpointis empty andparakeet.embedded.enabledis true, whisperAPI initializes an in-process ONNX Parakeet transcriber at startup. - If
parakeet.endpointis set, whisperAPI uses that endpoint directly. - Embedded initialization failures do not stop whisperAPI startup; the Parakeet engine is disabled and Whisper remains available.
- External endpoint mode still probes at startup and fails fast if unreachable.
When engine=parakeet is sent, whisperAPI forwards the full audio clip using multipart/form-data to the Parakeet OpenAI-compatible transcription endpoint.
Endpoint behavior:
- If
parakeet.endpointis a base URL (for examplehttp://localhost:5092), whisperAPI appends/v1/audio/transcriptions. - If
parakeet.endpointalready includes a path, that path is used as-is.
Forwarded form fields:
file(uploaded audio)model(defaults toparakeet-tdt-0.6b)language(optional)response_format=verbose_json
Expected Parakeet response JSON (OpenAI-compatible):
{
"text": "Transcribed text content",
"segments": []
}Response:
{
"text": "Transcribed text content",
"engine": "whisper",
"model": "models/ggml-base.bin",
"diarization": {
"enabled": true,
"method": "acoustic-kmeans",
"speaker_count": 2,
"speakers": [
{
"id": "SPEAKER_00",
"duration_seconds": 5.1,
"segment_count": 4,
"average_energy": 0.032
},
{
"id": "SPEAKER_01",
"duration_seconds": 4.8,
"segment_count": 3,
"average_energy": 0.041
}
]
},
"segments": [
{
"text": "Hello there",
"speaker": "SPEAKER_00",
"start_time": 0.4,
"end_time": 1.2,
"tokens": []
}
],
"audio_info": {
"format": "WAV",
"codec": "PCM",
"sample_rate": 16000,
"channels": 1,
"bit_depth": 16,
"duration_seconds": 10.5,
"original_size_bytes": 336000,
"bitrate_kbps": 256
},
"compute_time": {
"cpu_time_seconds": 1.23,
"gpu_time_seconds": null
},
"timestamp": "2024-02-14T12:34:56Z"
}See the swagger documentation for more details
All protected endpoints require a Bearer token:
curl -X POST http://localhost:8080/transcribe \
-H "Authorization: Bearer your-token-here" \
-F "audio=@sample.wav"Token validation flow:
- Check Redis cache for fast validation
- If not in cache, check PostgreSQL database
- If found in database, cache in Redis
- If not found, check static tokens
- If no match found, return 401 Unauthorized
Run the test suite:
go test -v ./...Generate test coverage:
go test -v -cover ./...The API returns detailed error responses:
{
"error": "Detailed error message"
}Common error scenarios:
- Invalid audio format
- Unsupported codec
- File read/write errors
- Processing timeouts
- Memory limits exceeded
Authentication errors:
{
"error": "Authorization header required"
}{
"error": "Invalid token"
}Prometheus metrics available at /metrics:
whisperapi_transcription_requests_total{status="success|error",format="wav|ogg|opus"}whisperapi_transcription_duration_secondswhisperapi_audio_duration_secondswhisperapi_memory_usage_bytes{type="allocated|system|heap"}whisperapi_cpu_time_seconds{operation="user|system|total"}
- Fork the repository
- Create your feature branch
- Run tests:
go test -v ./... - Commit changes
- Push to your branch
- Create Pull Request
Create a FreeBSD service file at /usr/local/etc/rc.d/whisperapi:
#!/bin/sh
#
# PROVIDE: whisperapi
# REQUIRE: NETWORKING
# KEYWORD: shutdown
. /etc/rc.subr
name="whisperapi"
rcvar="whisperapi_enable"
whisperapi_user="www"
whisperapi_group="www"
pidfile="/var/run/${name}.pid"
command="/usr/local/bin/whisperAPI"
command_args="&"
load_rc_config $name
run_rc_command "$1"Make it executable and enable the service:
chmod +x /usr/local/etc/rc.d/whisperapi
echo 'whisperapi_enable="YES"' >> /etc/rc.conf
service whisperapi startCreate a systemd service file at /etc/systemd/system/whisperapi.service:
[Unit]
Description=Whisper API Service
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
User=www-data
Group=www-data
Restart=always
RestartSec=1
WorkingDirectory=/opt/whisperapi
ExecStart=/opt/whisperapi/whisperAPI
# Security settings
PrivateTmp=true
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
CapabilityBoundingSet=
AmbientCapabilities=
# Resource limits
LimitNOFILE=65535
MemoryMax=2G
CPUQuota=80%
[Install]
WantedBy=multi-user.targetInstall and start the service:
# Copy application to /opt/whisperapi
sudo mkdir -p /opt/whisperapi
sudo cp whisperAPI /opt/whisperapi/
sudo cp -r models /opt/whisperapi/
# Set permissions
sudo chown -R www-data:www-data /opt/whisperapi
# Enable and start service
sudo systemctl daemon-reload
sudo systemctl enable whisperapi
sudo systemctl start whisperapi
# View logs
sudo journalctl -u whisperapi -fMonitor service status:
sudo systemctl status whisperapiCommon systemctl commands:
sudo systemctl stop whisperapi
sudo systemctl restart whisperapi
sudo systemctl disable whisperapiThis project is licensed under the BSD 3-Clause License - see the LICENSE file for details.
Copyright (c) 2024-2025, Darcy Buskermolen darcy@dbitech.ca. All rights reserved.