{"record":{"id":"502f1c36ca9b5434","repo":"SubtitleEdit/subtitleedit","slug":"elevenlabs-voice-list-request-failed-http-int-r","errorCode":null,"errorMessage":"ElevenLabs voice list request failed: HTTP {(int)result.StatusCode} {result.StatusCode}","messagePattern":"ElevenLabs voice list request failed: HTTP (.+?) (.+?)","errorType":"exception","errorClass":"HttpRequestException","httpStatus":null,"severity":"error","filePath":"src/ui/Logic/Download/TtsDownloadService.cs","lineNumber":230,"sourceCode":"        requestMessage.Headers.TryAddWithoutValidation(\"Content-Type\", \"application/json\");\n        requestMessage.Headers.TryAddWithoutValidation(\"Accept\", \"application/json\");\n\n        if (!string.IsNullOrEmpty(Se.Settings.Video.TextToSpeech.ElevenLabsApiKey))\n        {\n            requestMessage.Headers.TryAddWithoutValidation(\"xi-api-key\", Se.Settings.Video.TextToSpeech.ElevenLabsApiKey);\n        }\n\n        var result = await _httpClient.SendAsync(requestMessage, cancellationToken);\n\n        // Throw on failure instead of copying the error body into the stream: the caller writes\n        // the stream over its cached voice-list JSON, and since the bundled fallback only kicks\n        // in when the file is *missing*, one failed refresh (expired key, network error) used to\n        // permanently empty the voice list.\n        if (!result.IsSuccessStatusCode)\n        {\n            var error = (await result.Content.ReadAsStringAsync(cancellationToken)).Trim();\n            SeLogger.Error($\"ElevenLabs TTS failed calling API address {url} : Status code={result.StatusCode} {TruncateForLog(error)}\");\n            throw new HttpRequestException($\"ElevenLabs voice list request failed: HTTP {(int)result.StatusCode} {result.StatusCode}\");\n        }\n\n        await result.Content.CopyToAsync(ms, cancellationToken);\n    }\n\n    public async Task DownloadMurfVoiceList(MemoryStream ms, IProgress<float>? progress, CancellationToken cancellationToken)\n    {\n        var url = \"https://api.murf.ai/v1/speech/voices\";\n\n        using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);\n        requestMessage.Headers.TryAddWithoutValidation(\"Content-Type\", \"application/json\");\n        requestMessage.Headers.TryAddWithoutValidation(\"Accept\", \"application/json\");\n        requestMessage.Headers.TryAddWithoutValidation(\"api-key\", Se.Settings.Video.TextToSpeech.MurfApiKey);\n\n        var result = await _httpClient.SendAsync(requestMessage, cancellationToken);\n\n        // See DownloadElevenLabsVoiceList: an error body must not reach the cached voice list.\n        if (!result.IsSuccessStatusCode)","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Logic/Download/TtsDownloadService.cs#L212-L248","documentation":"HttpRequestException from DownloadElevenLabsVoiceList when the voice-list GET returns non-2xx. Per the comment, this throw-on-failure is intentional: copying an error body into the cached voice-list JSON used to permanently empty the list because the bundled fallback only restores when the file is missing.","triggerScenarios":"_httpClient.SendAsync for the ElevenLabs voice list returns !IsSuccessStatusCode. The trimmed error body is logged, and the status code is surfaced in the exception message.","commonSituations":"Expired or invalid ElevenLabs API key (401), quota exhausted (429), wrong endpoint, or transient server error (5xx).","solutions":["Verify the ElevenLabs API key is valid and not expired.","Check the account quota / billing status (429 means throttle).","Inspect the SeLogger line for the trimmed server error body.","Retry after a short backoff for transient 5xx responses."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (string.IsNullOrWhiteSpace(Se.Settings.Video.TextToSpeech.ElevenLabsApiKey))\n{\n    throw new InvalidOperationException(\"ElevenLabs API key is not set.\");\n}","typeGuard":null,"tryCatchPattern":"try { await service.DownloadElevenLabsVoiceList(ms, progress, ct); }\ncatch (HttpRequestException ex) when (ex.Message.Contains(\"ElevenLabs voice list\"))\n{\n    // keep the existing cached voice list; surface the status code to the user\n}","preventionTips":["Do NOT overwrite the cached voice-list JSON on a failed refresh (the throw design enforces this).","Verify the API key and quota before refreshing.","Retry transient 5xx with backoff; treat 401/429 as config/quota issues."],"tags":["network","http","tts","elevenlabs","voice-list","csharp"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}