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

  1. Read the embedded Cartesia body — it identifies invalid voice, model, or auth.
  2. Verify voiceID exists and belongs to the account (list voices or re-clone).
  3. Validate the TTSOutputFormat combo (wav/44100/pcm_f32le) is supported by sonic-2.
  4. Handle 429 with backoff; check quota in the Cartesia dashboard.
  5. 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

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


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/f406939996a42284. Report an issue: GitHub.