Billionmail/BillionMail · error
cartesia TTS API error %d: %s
Error message
cartesia TTS API error %d: %s
What it means
When the Cartesia /tts/bytes endpoint responds with a status other than 200, TextToSpeech reads the response body and returns 'cartesia TTS API error <code>: <body>'. Like the clone counterpart, the embedded body is Cartesia's own error message explaining the rejection.
Source
Thrown at core/internal/service/video_gen/voice.go:195
OutputFormat: DefaultTTSOutputFormat(),
Language: "en",
}
httpReq, err := BuildTTSRequest(cfg, req)
if err != nil {
return "", err
}
httpReq = httpReq.WithContext(ctx)
resp, err := cfg.doHTTP(httpReq)
if err != nil {
return "", fmt.Errorf("cartesia TTS API call: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("cartesia TTS API error %d: %s", resp.StatusCode, string(body))
}
outPath := filepath.Join(cfg.OutputDir, filename)
f, err := os.Create(outPath)
if err != nil {
return "", fmt.Errorf("create output file: %w", err)
}
defer f.Close()
if _, err := io.Copy(f, resp.Body); err != nil {
return "", fmt.Errorf("write audio data: %w", err)
}
return outPath, nil
}
View on GitHub (pinned to fc36c76c05)
Solutions
- Read the embedded Cartesia body — it identifies invalid voice, model, or auth.
- Verify voiceID exists and belongs to the account (list voices or re-clone).
- Validate the TTSOutputFormat combo (wav/44100/pcm_f32le) is supported by sonic-2.
- Handle 429 with backoff; check quota in the Cartesia dashboard.
- Upgrade or pin Cartesia-Version if the model_id was rejected.
Example fix
// before
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("cartesia TTS API error %d: %s", resp.StatusCode, string(body))
}
// after
if voiceID == "" {
return "", errors.New("voice id is empty")
}
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 {
return "", retryable(fmt.Errorf("cartesia TTS API error %d", resp.StatusCode))
}
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("cartesia TTS API error %d: %s", resp.StatusCode, string(body))
} Defensive patterns
Strategy: retry
Validate before calling
// validate inputs before the TTS call
if voiceID == "" {
return errors.New("voiceID is required")
}
if strings.TrimSpace(transcript) == "" {
return errors.New("transcript is required")
}
if cfg.APIKey == "" {
return errors.New("CARTESIA_API_KEY is not set")
}
// optionally confirm the voice exists:
// GET /voices/ and check voiceID is present before synthesizing Try / catch
path, err := video_gen.TextToSpeech(ctx, cfg, voiceID, transcript, filename)
if err != nil {
if strings.Contains(err.Error(), "cartesia TTS API error 429") ||
strings.Contains(err.Error(), "cartesia TTS API error 5") {
// retry with exponential backoff
}
if strings.Contains(err.Error(), "cartesia TTS API error 4") {
// 400/401/404: fix voiceID/key/format, do not retry
}
return err
} Prevention
- Cache and validate voiceIDs; handle deleted voices gracefully
- Verify the output format combo is supported by the chosen model_id
- Track Cartesia quota and alert before hitting 429
- Subscribe to Cartesia changelogs for model deprecations
When it happens
Trigger: Invalid or missing voice_id (404/400), invalid API key (401), unsupported model_id 'sonic-2' or output format combo (400), rate limits (429), Cartesia outage (5xx), or requesting a voice the account does not own.
Common situations: Stale voiceID from a deleted cloned voice, wrong sample_rate/encoding combination for the model, exhausted quota, deprecated model name after a Cartesia model update.
Related errors
- create TTS request: %w
- cartesia clone API error %d: %s
- unexpected API response: %d %s
- voice clone: %w
- create clone request: %w
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/f406939996a42284.
Report an issue: GitHub.