{"record":{"id":"029438e0251f1d51","repo":"SubtitleEdit/subtitleedit","slug":"openrouter-stt-request-failed-with-status-statusc","errorCode":null,"errorMessage":"OpenRouter STT request failed with status {statusCode} ({response.StatusCode}). Response: {errorContent}","messagePattern":"OpenRouter STT request failed with status (.+?) \\((.+?)\\)\\. Response: (.+?)","errorType":"exception","errorClass":"HttpRequestException","httpStatus":null,"severity":"error","filePath":"src/ui/Features/Video/SpeechToText/OpenRouter/OpenRouterSttService.cs","lineNumber":119,"sourceCode":"        {\n            request.Headers.Authorization = new AuthenticationHeaderValue(\"Bearer\", _settings.ApiKey);\n        }\n\n        // OpenRouter uses these to attribute traffic; harmless if the server ignores them.\n        request.Headers.TryAddWithoutValidation(\"HTTP-Referer\", \"https://www.nikse.dk/subtitleedit\");\n        request.Headers.TryAddWithoutValidation(\"X-Title\", \"Subtitle Edit\");\n\n        using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);\n        if (!response.IsSuccessStatusCode)\n        {\n            var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);\n            var statusCode = (int)response.StatusCode;\n            _settings.Logger?.Invoke(\n                $\"OpenRouter STT failed: POST {_settings.EndpointUrl}{Environment.NewLine}\" +\n                $\"Status: {statusCode} {response.StatusCode}{Environment.NewLine}\" +\n                $\"RequestParams: model={_settings.Model}, language={language ?? _settings.Language}, format={format}, bytes={audioBytes.Length}{Environment.NewLine}\" +\n                $\"ResponseBody: {errorContent}\");\n            throw new HttpRequestException(\n                $\"OpenRouter STT request failed with status {statusCode} ({response.StatusCode}). Response: {errorContent}\");\n        }\n\n        var json = await response.Content.ReadAsStringAsync(cancellationToken);\n        return ParseResponse(json);\n    }\n\n    /// <summary>\n    /// Serialize the OpenRouter transcription request body. The audio is\n    /// base64-encoded (raw, not a data URI) under <c>input_audio</c>, and\n    /// <c>verbose_json</c> + <c>timestamp_granularities[]</c> ask for segment and\n    /// word timings when the underlying model supports them.\n    /// </summary>\n    internal static string BuildRequestBody(OpenRouterSttSettings settings, byte[] audioBytes, string format, string? language)\n    {\n        using var stream = new MemoryStream();\n        using (var writer = new Utf8JsonWriter(stream))\n        {","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Features/Video/SpeechToText/OpenRouter/OpenRouterSttService.cs#L101-L137","documentation":"OpenRouter STT endpoint returned non-2xx; the status code and response body are embedded in the message. Note the log line writes _settings.EndpointUrl RAW (unlike the OpenAI path which sanitizes), and the thrown message itself does not include the endpoint.","triggerScenarios":"Non-success status from POST _settings.EndpointUrl: 400 bad model/format, 401 bad API key, 404 wrong endpoint, 429 rate limit, 5xx from OpenRouter or the routed upstream model.","commonSituations":"Wrong _settings.Model name; invalid/expired OpenRouter API key; endpoint URL misconfigured; rate limit on the account; the routed upstream provider returned an error OpenRouter forwards.","solutions":["Read statusCode and errorContent in the message to see the upstream reason.","Confirm _settings.Model is a valid OpenRouter model id that supports audio.","Validate/rotate the API key.","Retry with backoff on 429."],"exampleFix":"// before: model id OpenRouter rejects\nModel = \"whisper-1\";\n// after: a model id OpenRouter actually routes for audio\nModel = \"openai/whisper-large-v3\";","handlingStrategy":"try-catch","validationCode":"if (string.IsNullOrWhiteSpace(_settings.Model) || !_settings.Model.Contains('/'))\n    return Invalid(\"OpenRouter Model must be a provider/model id\");","typeGuard":null,"tryCatchPattern":"try { return await openRouter.TranscribeAsync(...); }\ncatch (HttpRequestException ex) when (ex.Message.Contains(\"OpenRouter STT request failed\"))\n{ /* parse statusCode from message; 401 -> key, 404 -> model/endpoint, 429 -> backoff */ }","preventionTips":["Use a valid OpenRouter provider/model id for audio.","Note this engine logs EndpointUrl raw — do not put secrets in the URL.","Branch on status code rather than substring-matching the message."],"tags":["openrouter","http","network","speech-to-text"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}