luchs-1 transcribes English speech. Upload a complete recording over REST, or
stream live PCM16 audio over WebSocket and receive revisable partial
hypotheses plus an authoritative final result.
luchs-1 is the only accepted model identifier for this endpoint. There is
no alias for any previous identifier — see Migrating from
qwen3-asr below.Upload a complete recording
Two request shapes are accepted: a JSON body with base64 audio, or amultipart/form-data upload in the OpenAI Whisper API shape.
Request fields
string
Base64-encoded audio (JSON body only). Mutually exclusive with the
multipart
file field.file
The audio file (multipart upload only).
string
default:"luchs-1"
Optional. Omit it to select
luchs-1. An explicit value must equal
luchs-1 exactly — any other value, including a previously valid
identifier, is rejected before the file is read or usage is recorded.string
Optional. Accepted and forwarded, but does not currently change what gets
recognized — see Supported language.
integer
default:"16000"
JSON body only, 8000–48000. The endpoint reads the real sample rate out of
the uploaded container, so this field only matters for raw PCM without a
container header.
string
default:"json"
Only
json is currently accepted. srt, vtt, verbose JSON and SSE
upload streaming are rejected explicitly rather than silently downgraded.Response fields
Examples
Errors
Rejection for an unsupported model happens after authentication and before any
transcription or usage recording — a rejected request is never billed.
Stream live audio
Connect towss://api.kugelaudio.com/v1/audio/transcriptions/stream, send a
config frame, then base64 PCM16 audio chunks, then an explicit
end-of-speech signal.
Client frames
model on the config frame is optional. Omit it, or send "luchs-1". Any
other value is rejected before any audio is forwarded:
1003 (reason: the same message).
Server frames
- Partial —
{"type": "partial", "partial_text": "...", "is_final": false}.partial_textis the complete rolling hypothesis; replace your previous value rather than appending. Partials never carrymodelormodel_revision— that absence is how you tell a revisable guess from an attributable result. Partials arrive roughly every 4 seconds of accepted audio, not wall-clock time; a turn shorter than 4 seconds produces no partial at all, only the final frame. - Final —
{"type": "partial", "partial_text": "...", "is_final": true, "model": "luchs-1", "model_revision": "...", "turn_end_reason": "client_end_of_speech", "word_alternatives": []}. The only frame that carriesmodelandmodel_revision, and the authoritative result for the turn. - Alternatives —
{"type": "alternatives", "partial_text": "...", "model": "luchs-1", "model_revision": "...", "word_alternatives": [...]}. Arrives after the final, is neveris_final, and repeats the final’s text so it can be handled independently. An empty list means none were produced, not that scoring was skipped.
turn_end_reason on the final frame is one of exactly three values:
client_end_of_speech, model_end_of_turn, silence_timeout. Treat any
other value as a protocol error rather than guessing at its meaning.
Close codes
See Error Codes for the shared HTTP/WebSocket error
body shape.
Supported language
luchs-1 transcribes English. The language field — on the JSON body,
the multipart form, and the WebSocket config frame — is optional and
accepted, but it does not currently change what gets recognized: audio is
transcribed as English regardless of what you send. The field is forwarded
rather than rejected, so setting it does not break your request; it just has
no effect on recognition yet. The response’s language field always reports
what was actually transcribed.
Audio limits
- Containers: complete WAV/RIFX/RF64, FLAC and Ogg files, plus most other common container formats. Sample rates from 8 kHz to 48 kHz are accepted; audio is resampled internally as needed.
- Streaming input: raw PCM16 mono, base64-encoded in
audio_chunkmessages, at the rate declared inconfig(default 16000 Hz). - No advertised maximum duration. A very long turn can fail while the final result is being produced rather than being rejected up front — keep turns to a length appropriate for a live conversation rather than uploading arbitrarily long recordings over the streaming path.
- Partial cadence: partials are due roughly every 4 seconds of accepted audio; see Server frames above.
Not promised
No word or segment timestamps and no diarization fromluchs-1 today.
word_timestamps exists in the response shape for backends that produce it;
for luchs-1 it is always empty, meaning “not produced,” never “no speech
detected.”
Migrating from qwen3-asr
The previously published model identifier qwen3-asr is retired with no
alias. Any request that names it explicitly is rejected; omitting the field
entirely, or naming luchs-1, keeps working.
Also update anything that reads the response
model field — it now always
returns "luchs-1" instead of a prior backend name. If you need to identify
the exact underlying artifact (for qualification, debugging, or an audit
trail), read model_revision instead; it is the immutable checkpoint
revision and is never rewritten.
SDKs
Python
client.asr.transcribe(...)JavaScript
client.asr.transcribe({ ... })Java
client.asr().transcribe(...)