SubtitleEdit/subtitleedit · error · HttpRequestException

Mistral voice list request failed: HTTP

Error message

Mistral voice list request failed: HTTP {(int)result.StatusCode} {result.StatusCode}

What it means

HttpRequestException from DownloadMistralVoiceList on a non-2xx response. Per the comment, throwing (rather than returning an empty stream) prevents a silently failed refresh from permanently emptying the cached voice list, since the bundled fallback only restores when the file is missing.

Solutions

  1. Verify MistralApiKey is valid and not expired.
  2. Check Mistral quota/plan limits (429).
  3. Inspect the SeLogger status line for the server reason.
  4. Retry transient 5xx after backoff.
Defensive patterns

Strategy: try-catch

Validate before calling

if (string.IsNullOrWhiteSpace(Se.Settings.Video.TextToSpeech.MistralApiKey))
{
    throw new InvalidOperationException("Mistral API key is not set.");
}

Try / catch

try { await service.DownloadMistralVoiceList(ms, progress, ct); }
catch (HttpRequestException ex) when (ex.Message.Contains("Mistral voice list"))
{
    // keep cached voice list; show the status code to the user
}

Prevention

When it happens

Trigger: _httpClient.SendAsync with Bearer MistralApiKey returns !IsSuccessStatusCode. Status logged; status code surfaced in the thrown message.

Common situations: Invalid/expired MistralApiKey (401), quota/throttle (429), wrong endpoint, or Mistral-side outage (5xx).

Related errors


AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13). Data as JSON: /api/errors/773faf14f8a51601. Report an issue: GitHub.

Appendix: source

Thrown at src/ui/Logic/Download/TtsDownloadService.cs:776

    public async Task DownloadMistralSpeechVoiceList(MemoryStream ms, IProgress<float>? progress, CancellationToken cancellationToken)
    {
        var url = "https://api.mistral.ai/v1/audio/voices?limit=10000";

        using var requestMessage = new HttpRequestMessage(HttpMethod.Get, url);
        requestMessage.Headers.TryAddWithoutValidation("Content-Type", "application/json");
        requestMessage.Headers.TryAddWithoutValidation("Accept", "application/json");
        requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Se.Settings.Video.TextToSpeech.MistralApiKey);

        var result = await _httpClient.SendAsync(requestMessage, cancellationToken);

        // Throw instead of returning with an empty stream: the caller writes the stream over its
        // cached voice-list JSON, so a silently failed refresh permanently emptied the voice list
        // (the bundled fallback only restores when the file is missing).
        if (!result.IsSuccessStatusCode)
        {
            SeLogger.Error($"Mistral TTS failed calling API address {url} : Status code={result.StatusCode}");
            throw new HttpRequestException($"Mistral voice list request failed: HTTP {(int)result.StatusCode} {result.StatusCode}");
        }

        await result.Content.CopyToAsync(ms, cancellationToken);
    }

    public async Task<bool> DownloadMistralSpeechSpeak(
        string inputText,
        MistralVoice voice,
        string model,
        string apiKey,
        MemoryStream stream,
        IProgress<float>? progress,
        CancellationToken cancellationToken)
    {
        var url = "https://api.mistral.ai/v1/audio/speech";
        var text = Utilities.UnbreakLine(inputText);

        var data = "{ \"model\": \"" + Json.EncodeJsonText(model) +

View on GitHub (pinned to 17a9f07487)