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.
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)
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
- Deepgram API error ({}): {}
- AssemblyAI upload error ({}): {}
- AssemblyAI transcription error ({}): {}
- AssemblyAI poll 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.