Billionmail/BillionMail · warning

marshal TTS request: %w

Error message

marshal TTS request: %w

What it means

BuildTTSRequest marshals the TTSRequest struct to JSON before building the Cartesia text-to-speech HTTP request; a json.Marshal failure is wrapped as 'marshal TTS request: %w'. Like the clone-request counterpart, this is a defensive wrapper since marshaling a plain struct essentially never fails.

Source

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

	}

	httpReq, err := http.NewRequest("POST", cfg.voiceBaseURL()+cartesiaClonePath, bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("create clone 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
}

// 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),

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Review recent changes to TTSRequest for unsupported types or a broken custom MarshalJSON
  2. Keep transcript as a plain string/simple struct so json.Marshal cannot fail
  3. Extend TestBuildTTSRequest_Body/LongTranscript to cover any new fields after schema changes

Example fix

// before
type TTSRequest struct {
    Transcript string          `json:"transcript"`
    Meta       map[string]any  `json:"meta"`
}
// Meta containing a chan/func value at runtime makes json.Marshal fail
// after
type TTSRequest struct {
    Transcript string          `json:"transcript"`
    Meta       map[string]string `json:"meta"` // JSON-safe value type
}
Defensive patterns

Strategy: validation

Validate before calling

func validateTTSRequest(req TTSRequest) error {
    if req.Transcript == "" {
        return fmt.Errorf("TTS transcript must not be empty")
    }
    if _, err := json.Marshal(req); err != nil {
        return fmt.Errorf("TTS request not serializable: %w", err)
    }
    return nil
}

Try / catch

httpReq, err := video_gen.BuildTTSRequest(cfg, req)
if err != nil {
    var ume *json.UnsupportedTypeError
    if errors.As(err, &ume) {
        log.Errorf("unmarshalable field in TTSRequest: %v", ume)
    }
    return err
}

Prevention

When it happens

Trigger: json.Marshal(req) errors, only realistic if TTSRequest gains an unmarshalable field (chan/func/cyclic reference) or a field type with a broken custom MarshalJSON.

Common situations: Code changes that add unsupported field types to TTSRequest; a transcript modeled as a complex type with a faulty marshaller; ordinary runs with plain strings never reach this error.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


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