{"record":{"id":"61c1113e46bf4a36","repo":"decolua/9router","slug":"openrouter-tts-failed-res-status","errorCode":null,"errorMessage":"OpenRouter TTS failed: ${res.status}","messagePattern":"OpenRouter TTS failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"open-sse/handlers/ttsProviders/openrouter.js","lineNumber":45,"sourceCode":"    const res = await fetch(TTS_CFG.baseUrl, {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        \"Authorization\": `Bearer ${credentials.apiKey}`,\n        ...(TTS_CFG.headers || {}),\n      },\n      body: JSON.stringify({\n        model: ttsModel,\n        modalities: [\"text\", \"audio\"],\n        audio: { voice, format: \"wav\" },\n        stream: true,\n        messages: [{ role: \"user\", content: text }],\n      }),\n    });\n\n    if (!res.ok) {\n      const err = await res.json().catch(() => ({}));\n      throw new Error(err?.error?.message || `OpenRouter TTS failed: ${res.status}`);\n    }\n\n    // Parse SSE stream, accumulate base64 audio chunks\n    const chunks = [];\n    const reader = res.body.getReader();\n    const decoder = new TextDecoder();\n    let buffer = \"\";\n\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      buffer += decoder.decode(value, { stream: true });\n      const lines = buffer.split(\"\\n\");\n      buffer = lines.pop();\n      for (const line of lines) {\n        if (!line.startsWith(\"data: \") || line === \"data: [DONE]\") continue;\n        try {\n          const json = JSON.parse(line.slice(6));","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/ttsProviders/openrouter.js#L27-L63","documentation":"Thrown when OpenRouter's TTS (chat-completions-with-audio) endpoint returns a non-2xx HTTP status. Like the OpenAI variant it prefers the JSON error.message from the body and falls back to a status-code template. Any upstream rejection (auth, model access, moderation, quota) surfaces here.","triggerScenarios":"Non-OK response from OpenRouter chat completions with audio modality: 401 bad key, 404 model not found/not permitted, 402 insufficient credits, 429 rate limited, 5xx from the upstream provider OpenRouter proxies to.","commonSituations":"Free-tier OpenRouter account lacking access to the TTS model, credits exhausted, model slug misspelled (e.g. openai/gpt-4o-mini-tts unavailable), or OpenRouter returning HTML error pages that fail JSON parsing.","solutions":["Read the status code: 401 -> fix key, 402 -> add OpenRouter credits, 404/400 -> check model slug supports audio output","Verify the model ID in the TTS config exists on OpenRouter and your account can use it","Check openrouter.ai activity page for rejected-request details","Retry with backoff if 429 or 5xx","Confirm the request body shape still matches OpenRouter's audio-modality API if versions changed"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if (!credentials?.apiKey) throw new Error(\"OpenRouter TTS: missing API key\");\nif (typeof text !== \"string\" || text.length === 0) throw new Error(\"OpenRouter TTS: text required\");","typeGuard":"function isOpenRouterHttpError(e) {\n  return e instanceof Error && /^OpenRouter TTS failed: \\d{3}$/.test(e.message);\n}","tryCatchPattern":"try {\n  return await openrouterTts.synthesize(text, model, creds);\n} catch (e) {\n  const m = e.message.match(/OpenRouter TTS failed: (\\d{3})/);\n  if (m && [\"429\", \"500\", \"502\", \"503\", \"504\"].includes(m[1])) return retryWithBackoff(() => openrouterTts.synthesize(text, model, creds));\n  throw e;\n}","preventionTips":["Verify the model slug supports audio modality before routing","Keep OpenRouter credits funded; alert on 402s","Back off on 429/5xx with retry-after headers when available","Log full response bodies when JSON parsing fails to catch HTML error pages"],"tags":["tts","openrouter","http-error","upstream"],"backgroundTag":"upstream-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}