Billionmail/BillionMail · error

cartesia clone API error %d: %s

Error message

cartesia clone API error %d: %s

What it means

CloneVoice in the video_gen package calls the Cartesia /voices/clone endpoint and requires an exact HTTP 200 response. When Cartesia returns any other status code (401, 400, 429, 5xx, etc.), the non-200 body is read and wrapped in this error, so the message contains the raw API error payload from Cartesia. It is a remote-API rejection of the clone request, not a local failure.

Source

Thrown at core/internal/service/video_gen/voice.go:156

		AudioURL:    audioURL,
		Language:    "en",
	}

	httpReq, err := BuildCloneRequest(cfg, req)
	if err != nil {
		return nil, err
	}
	httpReq = httpReq.WithContext(ctx)

	resp, err := cfg.doHTTP(httpReq)
	if err != nil {
		return nil, fmt.Errorf("cartesia clone API call: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("cartesia clone API error %d: %s", resp.StatusCode, string(body))
	}

	var result VoiceCloneResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("decode clone response: %w", err)
	}
	return &result, nil
}

// TextToSpeech generates audio from text using a Cartesia voice.
// Returns the path to the output WAV file.
func TextToSpeech(ctx context.Context, cfg VoiceConfig, voiceID, transcript, filename string) (string, error) {
	if err := os.MkdirAll(cfg.OutputDir, 0755); err != nil {
		return "", fmt.Errorf("create output dir: %w", err)
	}

	req := TTSRequest{
		VoiceID:      voiceID,

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Log the embedded Cartesia body — it names the exact reason (invalid key, bad clip, quota).
  2. Verify CARTESIA_API_KEY is set and valid for the account.
  3. Check the audioURL is publicly fetchable, uses a supported audio format, and is not expired.
  4. Check Cartesia status/quota; apply backoff and retry for 429/5xx.
  5. Confirm the Cartesia-Version header (2024-06-10) is still accepted by your account.

Example fix

// before
resp, err := cfg.doHTTP(httpReq)
if err != nil { ... }
if resp.StatusCode != http.StatusOK {
    return nil, fmt.Errorf("cartesia clone API error %d: %s", resp.StatusCode, string(body))
}
// after
if cfg.APIKey == "" {
    return nil, errors.New("cartesia API key missing")
}
if u, err := url.Parse(audioURL); err != nil || u.Scheme != "https" {
    return nil, fmt.Errorf("invalid audio URL %q", audioURL)
}
resp, err := cfg.doHTTP(httpReq)
if err != nil { ... }
switch {
case resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500:
    return nil, retryableError{fmt.Errorf("cartesia clone API error %d", resp.StatusCode)}
case resp.StatusCode != http.StatusOK:
    return nil, fmt.Errorf("cartesia clone API error %d: %s", resp.StatusCode, string(body))
}
Defensive patterns

Strategy: validation

Validate before calling

if cfg.APIKey == "" {
    return errors.New("CARTESIA_API_KEY is not set")
}
if audioURL == "" {
    return errors.New("audioURL is required for voice cloning")
}
u, err := url.Parse(audioURL)
if err != nil || (u.Scheme != "https" && u.Scheme != "http") {
    return fmt.Errorf("invalid audio URL: %q", audioURL)
}
resp, err := http.Head(audioURL)
if err == nil && resp.StatusCode != http.StatusOK {
    return fmt.Errorf("audio URL not fetchable: %d", resp.StatusCode)
}

Try / catch

resp, err := video_gen.CloneVoice(ctx, cfg, name, audioURL)
if err != nil {
    var apiErr *video_gen.APIError
    if strings.Contains(err.Error(), "cartesia clone API error 4") {
        // non-retryable: fix key/URL/input
        log.Fatalf("clone rejected: %v", err)
    }
    // 429/5xx: retry with backoff
}

Prevention

When it happens

Trigger: Calling CloneVoice when the X-API-Key is invalid/expired (401), the clip audio URL is unreachable or an invalid format (400), quota/rate limits are hit (429), or Cartesia has a server-side outage (5xx).

Common situations: Missing or misconfigured CARTESIA_API_KEY env var, audio clip URL behind auth or expired presigned URL, unsupported audio format/language, exceeding account voice-clone limits, Cartesia API version incompatibility.

Related errors


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