zeroclaw-labs/zeroclaw · error
Transcription API error
Error message
Transcription API error ({}): {} What it means
parse_whisper_response() is the shared parser for all Whisper-compatible endpoints — Groq, OpenAI Whisper, and local_whisper (whisper.cpp server / LocalAI). It bails when the HTTP status is non-2xx, embedding the status and the trimmed response body. Which provider produced it is identified by the 'Transcription ...' context in the surrounding error chain, not the message itself.
Solutions
- Read {status} and the body text: 401 -> fix the api_key for the selected provider ([transcription] for Groq, [transcription.openai], [transcription.local_whisper])
- 404 on local_whisper -> set api_url to the complete endpoint, e.g. http://localhost:9000/v1/audio/transcriptions
- 429 (usually Groq) -> back off and retry; stagger concurrent transcriptions
- 400 on a local server -> confirm the model is loaded and the audio format is one the server build accepts
Example fix
# before: base URL only -> 404 on local whisper.cpp server [transcription.local_whisper] api_url = "http://localhost:9000" # after: full endpoint path [transcription.local_whisper] api_url = "http://localhost:9000/v1/audio/transcriptions"
Defensive patterns
Strategy: try-catch
Try / catch
match provider.transcribe(&audio, name).await {
Ok(text) => Ok(text),
Err(e) => {
let msg = e.to_string();
if msg.contains("429") {
retry_with_backoff(|| provider.transcribe(&audio, name)).await // Groq throttling
} else if msg.contains("404") {
Err(anyhow!("local_whisper api_url must be the full /v1/audio/transcriptions path"))
} else {
Err(e) // 401: fix api_key; 400: model/format issue on the server
}
}
} Prevention
- Configure api_url as the complete endpoint path (…/v1/audio/transcriptions), never a bare host
- Throttle concurrent Groq transcriptions to stay under rate limits
- Smoke-test local whisper servers at startup with a 1-second clip to catch 404/400 before real traffic
When it happens
Trigger: POST <api_url>/audio/transcriptions returns non-success: Groq 429 rate limit (very common on the free tier) or 401 invalid key; OpenAI 401 invalid api_key or 429; local_whisper 404 when api_url doesn't point at the full endpoint path (must include /v1/audio/transcriptions), 400 from a model not loaded, or connection-level errors surfacing as 502 from a proxy.
Common situations: Groq free-tier bursts of voice notes hitting RPM/TPD limits; whisper.cpp server started without --convert or missing the model so it 400s; LocalAI model not preloaded; api_url configured as a base URL (http://localhost:9000) instead of the full path; OpenAI key rotated.
Related errors
- AssemblyAI poll error
- AssemblyAI transcription error
- AssemblyAI upload error
- Deepgram API error ( )
- Google STT API error
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c2c1817e42c7a8cd.
Report an issue: GitHub.
Appendix: source
Thrown at crates/zeroclaw-channels/src/transcription.rs:891
let resp = req
.multipart(Form::new().part("file", file_part))
.timeout(std::time::Duration::from_secs(self.timeout_secs))
.send()
.await
.context("Failed to send audio to local Whisper endpoint")?;
parse_whisper_response(resp).await
}
}
// ── Shared response parsing ─────────────────────────────────────
async fn parse_whisper_response(resp: reqwest::Response) -> Result<String> {
let status = resp.status();
if !status.is_success() {
let body = resp.text().await.unwrap_or_default();
bail!("Transcription API error ({}): {}", status, body.trim());
}
let body: serde_json::Value = resp
.json()
.await
.context("Failed to parse transcription response")?;
let text = body["text"]
.as_str()
.context("Transcription response missing 'text' field")?
.to_string();
Ok(text)
}
// ── TranscriptionManager ────────────────────────────────────────
/// Manages multiple transcription / STT providers and routes transcriptionView on GitHub (pinned to 88bb9c8533)