Skip to main content
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 a multipart/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 to wss://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:
followed by WebSocket close code 1003 (reason: the same message).

Server frames

  • Partial{"type": "partial", "partial_text": "...", "is_final": false}. partial_text is the complete rolling hypothesis; replace your previous value rather than appending. Partials never carry model or model_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 carries model and model_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 never is_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_chunk messages, at the rate declared in config (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 from luchs-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(...)
None of the three SDKs has a WebSocket streaming client for speech-to-text yet — each only wraps the REST upload above. For streaming, use the raw WebSocket protocol described in Stream live audio directly. This is expected to change in the next major release of each SDK.
Last modified on September 13, 2026