API Reference#
Base URL: http://127.0.0.1:8010 (default).
All endpoints return JSON unless noted. The transcription endpoints also set
the X-Session-ID response header; clients that want multi-upload sessions
should read it and pass it back as the session_id form field.
GET /health#
Liveness probe.
Response:
{"status": "ok"}
GET /devices#
Returns detected ALSA capture devices in hw:<card>,<device> format.
POST /v1/audio/transcriptions#
OpenAI-compatible transcription endpoint that returns a single response.
Form fields:
Field |
Required |
Description |
|---|---|---|
|
Yes |
Audio upload. |
|
No |
Accepted value is |
|
No |
Reuse to continue an existing session. |
|
No |
Language hint passed to the ASR backend. |
|
No |
Accepted but currently ignored. |
|
No |
One of |
|
No |
Decoding temperature. |
|
No |
|
|
No |
Stable key that keeps speaker enrollment across utterances of one conversation. |
|
No |
Narrowing override: |
The optional device field accepts CPU, GPU, or NPU. When omitted, it
uses the service configuration.
The language hint is forwarded to the ASR backend unchanged, except when the
configured model is an English-only Whisper checkpoint (a .en suffix, such as
distil-small.en). Those checkpoints have no language token at all and reject
any hint, so the service drops it for them only — multilingual deployments
always receive the caller’s language.
Example:
curl --noproxy '*' \
-F file=@question_store_hours.wav \
-F response_format=verbose_json \
http://127.0.0.1:8010/v1/audio/transcriptions
If session_id is omitted, the service creates one and returns it in
X-Session-ID. Reusing that value with another upload continues the same
session and appends transcript state.
Streaming with stream=true (OpenAI-compatible SSE)#
Setting stream=true returns text/event-stream instead of a single JSON
body, emitting the event shape OpenAI documents for this endpoint — so
official OpenAI SDKs can consume it directly.
Event |
Meaning |
|---|---|
|
Incremental text for a transcribed chunk ( |
|
Final event with the complete |
|
Stream terminator sentinel. |
curl --noproxy '*' -N \
-F file=@question_store_hours.wav \
-F stream=true \
http://127.0.0.1:8010/v1/audio/transcriptions
data: {"type": "transcript.text.delta", "delta": "Hello, what time do you open?"}
data: {"type": "transcript.text.done", "text": "Hello, what time do you open?", "language": "en", "duration": 2.4}
data: [DONE]
stream=true requires response_format to be json or verbose_json;
other formats return HTTP 400.
The optional device applies to both single-response and stream=true
requests on this endpoint. It controls ASR inference only. Diarization, when
enabled, continues to use models.diarization.device.
POST /v1/audio/transcriptions/stream#
Streaming transcription endpoint that emits NDJSON events. This endpoint uses the service-configured ASR device and does not accept a per-request device override.
Form fields:
Field |
Required |
Description |
|---|---|---|
|
Yes |
Audio upload. |
|
No |
Reuse to continue an existing session. |
|
No |
Language hint. |
|
No |
Decoding temperature. |
Event types:
transcription.chunk— Emitted as each audio chunk is transcribed.transcription.completed— Emitted once, when the upload is fully processed.
Example:
curl --noproxy '*' \
-F file=@question_store_hours.wav \
http://127.0.0.1:8010/v1/audio/transcriptions/stream
WS /v1/realtime#
Continuous, live audio streaming transcription over WebSocket, shaped after OpenAI’s Realtime transcription API. Unlike the HTTP endpoints (which take a finite upload), this accepts an open-ended audio feed and returns transcripts utterance by utterance.
Connect to ws://<host>:8010/v1/realtime?intent=transcription.
Query parameters:
Parameter |
Required |
Description |
|---|---|---|
|
No |
Must be |
|
No |
Reuse to continue an existing session. |
|
No |
Language hint passed to the ASR backend. |
Audio must be PCM16 (signed 16-bit little-endian), mono, base64-encoded.
The default sample rate is 16000 Hz; change it with session.update. No
resampling is done in the socket layer — downstream ffmpeg/Whisper handle it.
Client → server events#
Event |
Payload |
|---|---|
|
|
|
|
|
Force-close the current utterance and transcribe it. |
|
Transcribe the trailing ~2 s of buffered audio without clearing the buffer or persisting anything. Best-effort progress signal only. |
|
Discard buffered audio. |
|
Flush the buffer and close the socket. |
Server → client events#
Event |
Meaning |
|---|---|
|
Current session configuration. |
|
Server VAD detected the start/end of speech. |
|
An utterance was closed and is being transcribed. |
|
Buffer discarded. |
|
Incremental transcript text for an utterance. |
|
Final |
|
Response to |
|
|
Turn detection (VAD)#
By default the server splits the stream using energy-based VAD:
{"type": "session.update",
"session": {"turn_detection": {"type": "server_vad",
"threshold": 0.02,
"silence_duration_ms": 500,
"prefix_padding_ms": 300}}}
threshold is normalized RMS in 0..1 (not a VAD probability). Lower it
for quiet audio; raise it for noisy environments. Set "turn_detection": null
to disable VAD and control boundaries yourself with
input_audio_buffer.commit.
Limits: 5 MB per append message, and any single utterance is force-committed
at 120 seconds.
Previews#
input_audio_buffer.preview gives a low-latency progress snapshot while the
speaker is still talking. It is deliberately cheap and deliberately lossy:
Only the trailing ~2 s of the buffer is decoded, so the cost of a preview does not grow with utterance length.
The cut is a plain time boundary, not a VAD boundary — a preview can begin mid-word.
Nothing is persisted: previews never write
session_state.jsonand never affect the transcript produced by a real commit.A preview never delays a real commit. The preview decode runs as a background task, so the server keeps reading from the socket while it is in progress, and it holds the session lock only long enough to copy the trailing audio out of the buffer. A
input_audio_buffer.commitsent during a preview is therefore processed immediately rather than queued behind it. A preview is additionally skipped outright if a commit is already decoding, or if an earlier preview has not finished.Because previews are concurrent, a preview event may arrive interleaved with commit events. Match events by
type, and usesequencefor preview ordering, rather than assuming a fixed arrival order.A pending preview is cancelled when the socket closes, so no preview event arrives after
session.close.A preview whose decode finishes after a commit landed is discarded rather than sent, so a stale snapshot never arrives after the final transcript for the same audio. Preview
sequencenumbers may therefore have gaps.Previews can be routed to a different ASR model/device via
models.asr.previewinconfig.yaml(by default they reuse the main ASR configuration).
A complete, runnable client example is in the Get Started guide.
Sessions#
A session is identified by session_id and corresponds to the directory
storage/<session_id>/. The same id can be reused across multiple uploads to
append transcript state and (when sentiment is enabled) update the
session-level sentiment summary.
VSS (Video Search & Summarization) compatibility#
These endpoints match the contract used by VSS’s pipeline-manager
(sample-applications/video-search-and-summarization in a previous Edge AI
Libraries release) and are not OpenAI-compatible.
Route prefix. VSS builds request URLs as <AUDIO_HOST>/api/v1/<endpoint>,
so these routes are served both unprefixed (/models, /transcriptions,
/health) and under /api/v1 (/api/v1/models, /api/v1/transcriptions,
/api/v1/health). Both forms are identical; use /api/v1 for VSS.
Listen port. VSS’s Compose expects this service on container port 8000,
while the standalone default is 8010. Override it with the
AUDIO_ANALYZER_SERVER_PORT environment variable rather than editing the
image.
GET /models#
Lists ASR model(s) available for POST /transcriptions. This service
currently transcribes with a single configured model (models.asr.name in
config.yaml), so the response always contains exactly one entry.
{
"models": [{"model_id": "whisper-base", "display_name": "whisper-base", "description": "openai provider on CPU"}],
"default_model": "whisper-base"
}
POST /transcriptions#
Accepts either a direct file upload or a MinIO source, but not both.
Form fields:
Field |
Required |
Description |
|---|---|---|
|
If not using MinIO source |
Video/audio upload. |
|
If not uploading a file |
MinIO bucket containing the source video. |
|
If not uploading a file |
Prefix/ID of the video object within the bucket. |
|
If not uploading a file |
Name of the video object within the bucket. |
|
No |
|
|
No |
Must match the single configured ASR model (see |
|
No |
|
|
No (query param) |
Language hint passed to the ASR backend. |
When a MinIO source is used, the service downloads the video from that
bucket, transcribes it, and uploads the resulting transcript back into the
same bucket at {video_id}/{video_name-stem}.{srt|txt} — this mirrors how
VSS reads the transcript directly from MinIO rather than through this API.
The extension follows include_timestamps: VSS sends true and parses the
object as SRT, so the default output is a valid SRT subtitle file.
MinIO connection details are configured via minio.endpoint /
minio.access_key / minio.secret_key / minio.secure in config.yaml
(or AUDIO_ANALYZER__MINIO__* env vars). If minio.endpoint is empty and a
MinIO source is requested, the endpoint returns 503.
Response:
{
"status": "completed",
"message": "Transcription completed successfully",
"job_id": "20260720-123456-ab12",
"transcript_path": "minio://my-bucket/video-1/clip.srt",
"video_name": "clip.mp4",
"video_duration": 45.2
}
A failed transcription returns HTTP 500 with "status": "failed" in the
body (not HTTP 200) so that callers checking only the HTTP status code still
detect the failure.
Example (MinIO source):
curl --noproxy '*' \
-F minio_bucket=my-bucket \
-F video_id=video-1 \
-F video_name=clip.mp4 \
http://127.0.0.1:8010/transcriptions
Supporting Resources#
Startup and deployment guides:
Configuration of ASR and sentiment backends: