{"record":{"id":"d0bafb16137d9bc1","repo":"Billionmail/BillionMail","slug":"create-tts-request-w","errorCode":null,"errorMessage":"create TTS request: %w","messagePattern":"create TTS request: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/video_gen/voice.go","lineNumber":123,"sourceCode":"\t}\n\n\thttpReq.Header.Set(\"X-API-Key\", cfg.APIKey)\n\thttpReq.Header.Set(\"Cartesia-Version\", cartesiaAPIVersion)\n\thttpReq.Header.Set(\"Content-Type\", \"application/json\")\n\treturn httpReq, nil\n}\n\n// BuildTTSRequest constructs the HTTP request for text-to-speech.\n// Exported for testing without making API calls.\nfunc BuildTTSRequest(cfg VoiceConfig, req TTSRequest) (*http.Request, error) {\n\tbody, err := json.Marshal(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshal TTS request: %w\", err)\n\t}\n\n\thttpReq, err := http.NewRequest(\"POST\", cfg.voiceBaseURL()+cartesiaTTSPath, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create TTS request: %w\", err)\n\t}\n\n\thttpReq.Header.Set(\"X-API-Key\", cfg.APIKey)\n\thttpReq.Header.Set(\"Cartesia-Version\", cartesiaAPIVersion)\n\thttpReq.Header.Set(\"Content-Type\", \"application/json\")\n\treturn httpReq, nil\n}\n\n// CloneVoice creates a cloned voice from an audio sample URL via Cartesia API.\nfunc CloneVoice(ctx context.Context, cfg VoiceConfig, name, audioURL string) (*VoiceCloneResponse, error) {\n\treq := VoiceCloneRequest{\n\t\tName:        name,\n\t\tDescription: fmt.Sprintf(\"Cloned voice for %s\", name),\n\t\tMode:        \"url\",\n\t\tAudioURL:    audioURL,\n\t\tLanguage:    \"en\",\n\t}\n","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/video_gen/voice.go#L105-L141","documentation":"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.","triggerScenarios":"http.NewRequest fails on cfg.voiceBaseURL()+cartesiaTTSPath: empty base URL, whitespace/control characters, or invalid scheme in the configured voice base URL.","commonSituations":"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.","solutions":["Print and url.Parse cfg.voiceBaseURL()+cartesiaTTSPath to see the parse error","Correct the Cartesia base URL (default https://api.cartesia.ai) in VoiceConfig or its environment variable, trimming whitespace","Validate scheme and host at config-load time and fail fast with a clear message","Reuse one validated base-URL helper for both clone and TTS paths so a fix covers both"],"exampleFix":"// before\nhttpReq, err := http.NewRequest(\"POST\", cfg.voiceBaseURL()+cartesiaTTSPath, bytes.NewReader(body))\nif err != nil {\n    return nil, fmt.Errorf(\"create TTS request: %w\", err)\n}\n// after\nu, uerr := url.Parse(cfg.voiceBaseURL() + cartesiaTTSPath)\nif uerr != nil || u.Scheme == \"\" || u.Host == \"\" {\n    return nil, fmt.Errorf(\"invalid TTS endpoint URL %q: %w\", cfg.voiceBaseURL()+cartesiaTTSPath, uerr)\n}\nhttpReq, err := http.NewRequest(\"POST\", u.String(), bytes.NewReader(body))","handlingStrategy":"try-catch","validationCode":"func validateTTSEndpoint(cfg VoiceConfig) error {\n    u, err := url.Parse(cfg.voiceBaseURL() + cartesiaTTSPath)\n    if err != nil || u.Scheme == \"\" || u.Host == \"\" {\n        return fmt.Errorf(\"invalid TTS endpoint: %q\", cfg.voiceBaseURL()+cartesiaTTSPath)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"httpReq, err := video_gen.BuildTTSRequest(cfg, req)\nif err != nil {\n    var urlErr *url.Error\n    if errors.As(err, &urlErr) {\n        log.Errorf(\"bad TTS endpoint URL: %v\", urlErr)\n        // fix VoiceConfig base URL before retrying\n    }\n    return err\n}","preventionTips":["Validate the base URL once at startup and reuse it for all Cartesia calls","Trim whitespace from config/env-derived URLs","Pin the Cartesia base URL in deployment config rather than ad-hoc strings","Cover the empty-base-URL case in TestBuildTTSRequest_Headers-style unit tests"],"tags":["http","url","cartesia","tts","config"],"backgroundTag":"invalid-url","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}