Billionmail/BillionMail · error

create TTS request: %w

Error message

create TTS request: %w

What it means

BuildTTSRequest then calls http.NewRequest("POST", cfg.voiceBaseURL()+cartesiaTTSPath, ...); if the TTS endpoint URL cannot be parsed, NewRequest returns a *url.Error wrapped as 'create TTS request: %w'. The root cause is an invalid Cartesia base URL in VoiceConfig, not the transcript content.

Source

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

	}

	httpReq.Header.Set("X-API-Key", cfg.APIKey)
	httpReq.Header.Set("Cartesia-Version", cartesiaAPIVersion)
	httpReq.Header.Set("Content-Type", "application/json")
	return httpReq, nil
}

// BuildTTSRequest constructs the HTTP request for text-to-speech.
// Exported for testing without making API calls.
func BuildTTSRequest(cfg VoiceConfig, req TTSRequest) (*http.Request, error) {
	body, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("marshal TTS request: %w", err)
	}

	httpReq, err := http.NewRequest("POST", cfg.voiceBaseURL()+cartesiaTTSPath, bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("create TTS request: %w", err)
	}

	httpReq.Header.Set("X-API-Key", cfg.APIKey)
	httpReq.Header.Set("Cartesia-Version", cartesiaAPIVersion)
	httpReq.Header.Set("Content-Type", "application/json")
	return httpReq, nil
}

// CloneVoice creates a cloned voice from an audio sample URL via Cartesia API.
func CloneVoice(ctx context.Context, cfg VoiceConfig, name, audioURL string) (*VoiceCloneResponse, error) {
	req := VoiceCloneRequest{
		Name:        name,
		Description: fmt.Sprintf("Cloned voice for %s", name),
		Mode:        "url",
		AudioURL:    audioURL,
		Language:    "en",
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Print and url.Parse cfg.voiceBaseURL()+cartesiaTTSPath to see the parse error
  2. Correct the Cartesia base URL (default https://api.cartesia.ai) in VoiceConfig or its environment variable, trimming whitespace
  3. Validate scheme and host at config-load time and fail fast with a clear message
  4. Reuse one validated base-URL helper for both clone and TTS paths so a fix covers both

Example fix

// before
httpReq, err := http.NewRequest("POST", cfg.voiceBaseURL()+cartesiaTTSPath, bytes.NewReader(body))
if err != nil {
    return nil, fmt.Errorf("create TTS request: %w", err)
}
// after
u, uerr := url.Parse(cfg.voiceBaseURL() + cartesiaTTSPath)
if uerr != nil || u.Scheme == "" || u.Host == "" {
    return nil, fmt.Errorf("invalid TTS endpoint URL %q: %w", cfg.voiceBaseURL()+cartesiaTTSPath, uerr)
}
httpReq, err := http.NewRequest("POST", u.String(), bytes.NewReader(body))
Defensive patterns

Strategy: try-catch

Validate before calling

func validateTTSEndpoint(cfg VoiceConfig) error {
    u, err := url.Parse(cfg.voiceBaseURL() + cartesiaTTSPath)
    if err != nil || u.Scheme == "" || u.Host == "" {
        return fmt.Errorf("invalid TTS endpoint: %q", cfg.voiceBaseURL()+cartesiaTTSPath)
    }
    return nil
}

Try / catch

httpReq, err := video_gen.BuildTTSRequest(cfg, req)
if err != nil {
    var urlErr *url.Error
    if errors.As(err, &urlErr) {
        log.Errorf("bad TTS endpoint URL: %v", urlErr)
        // fix VoiceConfig base URL before retrying
    }
    return err
}

Prevention

When it happens

Trigger: http.NewRequest fails on cfg.voiceBaseURL()+cartesiaTTSPath: empty base URL, whitespace/control characters, or invalid scheme in the configured voice base URL.

Common situations: Unset or mis-typed TTS/API base URL env var; trailing newline from file-based config; scheme typos; hand-edited config pointing at an internal proxy with a bad URL.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


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