Kuberwastaken/claurst · error
Whisper API returned
Error message
Whisper API returned {}: {} What it means
transcribe() POSTs the recorded WAV to the OpenAI Whisper transcription endpoint (or WHISPER_ENDPOINT_URL). If the HTTP response status is not a success (2xx), it reads the response body and returns an anyhow error embedding the status code and body. This means the upload completed but the server rejected the transcription request.
Solutions
- Verify OPENAI_API_KEY is set to a valid OpenAI key with billing enabled (the fallback to ANTHROPIC_API_KEY will not work against api.openai.com).
- Check the response body in the error message for the exact reason (e.g. invalid_api_key, quota_exceeded).
- If WHISPER_ENDPOINT_URL is set, confirm it points to a Whisper-compatible endpoint that accepts multipart form fields 'file' and 'model'.
- Reduce recording length/compression to stay under the 25 MB upload limit.
- Retry with backoff if the status was 429 or 5xx (transient rate limit or server error).
Example fix
// before
match voice_capture::transcribe(wav, &key).await {
Ok(text) => use(text),
Err(e) => eprintln!("{}", e), // opaque
}
// after
match voice_capture::transcribe(wav, &key).await {
Ok(text) => use(text),
Err(e) => {
let msg = format!("{e:#}");
if msg.contains("401") { prompt_for_api_key(); }
else if msg.contains("429") { schedule_retry(); }
else { show_error(&msg); }
}
} Defensive patterns
Strategy: try-catch
Validate before calling
let key = std::env::var("OPENAI_API_KEY").ok().filter(|k| !k.trim().is_empty());
if key.is_none() { return Err(anyhow!("OPENAI_API_KEY required for Whisper transcription")); }
if wav_bytes.len() > 25 * 1024 * 1024 { return Err(anyhow!("audio exceeds OpenAI 25 MB transcription limit")); } Type guard
fn is_success_status(status: u16) -> bool { (200..300).contains(&status) } Try / catch
match transcribe(wav, &key).await {
Ok(text) => handle(text),
Err(e) => {
let msg = format!("{e:#}");
if msg.contains("401") { reauth(); } else if msg.contains("429") { retry_with_backoff(); } else { log_error(&msg); }
}
} Prevention
- Always set OPENAI_API_KEY; do not rely on the ANTHROPIC_API_KEY fallback — it will be rejected by api.openai.com.
- Keep recordings short enough to stay under the 25 MB multipart limit.
- Trim and validate WHISPER_ENDPOINT_URL when overriding it.
- Treat 429/5xx as retryable with exponential backoff.
- Log the response body from the error message; it names the exact failure reason.
When it happens
Trigger: Calling transcribe() with an invalid/expired OPENAI_API_KEY (401), a wrong model name or endpoint (404), audio too large or malformed (400), or rate/quota limits (429) — any non-2xx from the transcription endpoint.
Common situations: Key rotated or revoked; using an ANTHROPIC_API_KEY fallback that Whisper does not accept; WHISPER_ENDPOINT_URL pointing to a non-OpenAI service with different auth; uploads exceeding the 25 MB audio limit; exhausted monthly quota.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- GrowthBook API returned status
- Transcription API returned
- Bridge register: server returned
- Token exchange failed
- Token exchange failed
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/e03f0213ae2bc7d8.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/tui/src/voice_capture.rs:246
let file_part = reqwest::multipart::Part::bytes(wav_bytes)
.file_name("audio.wav")
.mime_str("audio/wav")?;
let form = reqwest::multipart::Form::new()
.text("model", "whisper-1")
.part("file", file_part);
let response = client
.post(&url)
.bearer_auth(api_key)
.multipart(form)
.send()
.await?;
let status = response.status();
if !status.is_success() {
let body = response.text().await.unwrap_or_default();
return Err(anyhow::anyhow!(
"Whisper API returned {}: {}",
status,
body
));
}
let json: serde_json::Value = response.json().await?;
let text = json["text"].as_str().unwrap_or("").trim().to_string();
Ok(text)
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/// Resolve the API key to use for transcription.
///
/// Priority: `OPENAI_API_KEY` env var → `ANTHROPIC_API_KEY` env var → `None`.
View on GitHub (pinned to b0637c97ec)