Hmbown/CodeWhale · error · anyhow::Error
Speech synthesis failed: HTTP
Error message
Speech synthesis failed: HTTP {status}: {error_text} What it means
The speech synthesis HTTP request completed but the provider returned a non-success status. The client reads a bounded slice of the error body, passes it through `sanitize_http_error_body` (provider-attributed and redacted), and surfaces the provider's status and message so the real cause (auth, quota, bad voice payload, unsupported format) is visible. Raised in `crates/tui/src/client.rs` around line 3384.
Solutions
- Read the sanitized `error_text` in the message — it is the provider's own error payload; fix the cause it names.
- Verify the provider API key and account access to the requested model.
- Check quota/billing if the status is 402/429.
- For 4xx validation errors, check the audio format and voice payload.
Defensive patterns
Strategy: try-catch
Validate before calling
assert!(voice.as_deref().map(str::trim).filter(|v| !v.is_empty()).is_some(), "voice required"); assert!(["mp3", "wav"].contains(&audio_format.as_str()), "unsupported audio format");
Try / catch
match client.synthesize_speech(req).await {
Err(e) if e.to_string().contains("Speech synthesis failed: HTTP") => {
let status = extract_status(&e.to_string());
match status {
429 | 503 => retry_with_backoff(),
401 | 403 => refresh_credentials(),
_ => report_provider_error(&e),
}
}
Err(e) => return Err(e),
Ok(resp) => resp,
} Prevention
- Check API key validity and model entitlement before synthesis calls.
- Respect rate limits with backoff on 429.
- Validate the voice data URI and audio format client-side.
When it happens
Trigger: Any non-2xx response from POST `{base_url}/chat/completions` for a speech synthesis request: invalid API key, exhausted credits/quota, malformed voice data URI, unsupported `audio_format`, model not available to the account.
Common situations: Expired or missing provider API keys; free-tier accounts hitting a model they cannot access; oversized or corrupt clone audio; region-restricted endpoints returning 403.
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
- ${compactRuntimeError(response.status, result)}
- DeepSeek
- detail || ("HTTP " + res.status)
- FIM API error: HTTP
- iLink API failed: HTTP
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7d9ce2d3aa641267.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/client.rs:3384
"format": audio_format.clone(),
});
if let Some(voice) = voice.as_deref() {
audio["voice"] = json!(voice);
}
let body = build_speech_synthesis_body(&model, &text, instruction, audio);
let url = api_url(&self.base_url, "chat/completions");
let response = self.send_json_with_retry(&url, &body).await?;
let status = response.status();
if !status.is_success() {
let raw_error_text = bounded_error_text(response, ERROR_BODY_MAX_BYTES).await;
let error_text = sanitize_http_error_body(
Some(self.api_provider.display_name()),
status.as_u16(),
&raw_error_text,
);
anyhow::bail!("Speech synthesis failed: HTTP {status}: {error_text}");
}
let response_text = response
.text()
.await
.context("Failed to read speech synthesis response body")?;
let payload: Value = serde_json::from_str(&response_text)
.context("Failed to parse speech synthesis response JSON")?;
let (audio_bytes, transcript) = parse_speech_audio_response(&payload)?;
Ok(SpeechSynthesisResponse {
model,
audio_format,
audio_bytes,
transcript,
voice,
})
}View on GitHub (pinned to 73e0f67d83)