{"record":{"id":"0b0368ceb3cef34a","repo":"decolua/9router","slug":"elevenlabs-tts-failed-res-status","errorCode":null,"errorMessage":"ElevenLabs TTS failed: ${res.status}","messagePattern":"ElevenLabs TTS failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"open-sse/handlers/ttsProviders/elevenlabs.js","lineNumber":42,"sourceCode":"export default {\n  async synthesize(text, model, credentials) {\n    if (!credentials?.apiKey) throw new Error(\"ElevenLabs API key required\");\n    let modelId = \"eleven_flash_v2_5\";\n    let voiceId = model;\n    if (model && model.includes(\"/\")) [modelId, voiceId] = model.split(\"/\");\n\n    const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {\n      method: \"POST\",\n      headers: { \"xi-api-key\": credentials.apiKey, \"Content-Type\": \"application/json\" },\n      body: JSON.stringify({\n        text,\n        model_id: modelId,\n        voice_settings: { stability: 0.5, similarity_boost: 0.75 },\n      }),\n    });\n    if (!res.ok) {\n      const err = await res.json().catch(() => ({}));\n      throw new Error(err?.detail?.message || `ElevenLabs TTS failed: ${res.status}`);\n    }\n    const buf = await res.arrayBuffer();\n    if (buf.byteLength < 1024) throw new Error(\"ElevenLabs TTS returned empty audio\");\n    return { base64: Buffer.from(buf).toString(\"base64\"), format: \"mp3\" };\n  },\n};\n","sourceCodeStart":24,"sourceCodeEnd":49,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/ttsProviders/elevenlabs.js#L24-L49","documentation":"The ElevenLabs synthesize call POSTs to /v1/text-to-speech/{voiceId}; on a non-OK response it first tries to parse the JSON body and surface ElevenLabs' own `detail.message`, falling back to this generic `ElevenLabs TTS failed: <status>` when the body is not parseable or lacks a message. It means ElevenLabs rejected the synthesis request or the request failed upstream.","triggerScenarios":"Non-OK response from POST https://api.elevenlabs.io/v1/text-to-speech/{voiceId}: unknown/removed voice_id (404? actually 400/422), invalid key (401), quota exceeded or free-tier limit (401/429), model_id not available on the account's plan (e.g. eleven_flash_v2_5 unavailable), malformed body, or a non-JSON error body (proxy/CDN error page) so the detail parse fails.","commonSituations":"Voice id deleted or renamed in the ElevenLabs account; using a premium model id on a free plan; running out of character quota; sending text with invalid characters; a reverse proxy returning an HTML 502 that defeats the JSON detail extraction, leaving only the status code.","solutions":["If the message shown is ElevenLabs' own detail.message, follow it; if you only got the status, re-run with the raw response logged to capture the body.","401 → regenerate/update the API key; 429/quota → wait or upgrade the ElevenLabs subscription.","Verify the voice id exists: GET /v1/voices with the same key and use one of its voice_ids (model string is `modelId/voiceId`).","Confirm the account has access to the requested model_id; switch to `eleven_multilingual_v2` or `eleven_flash_v2_5` per your plan.","If the response body is HTML (proxy error), check your network path/proxy rather than ElevenLabs itself."],"exampleFix":"// before\nawait synthesize(text, \"eleven_flash_v2_5/old-voice-id\", { apiKey });\n// after: verify the voice exists first\nconst list = await fetchElevenLabsVoices(apiKey);\nconst voice = list.find(v => v.voice_id === voiceId || v.name === voiceId);\nif (!voice) throw new Error(`Unknown ElevenLabs voice: ${voiceId}`);\nawait synthesize(text, `eleven_flash_v2_5/${voice.voice_id}`, { apiKey });","handlingStrategy":"retry","validationCode":"const voices = await fetchElevenLabsVoices(apiKey); // throws early on bad key\nconst voice = voices.find(v => v.voice_id === voiceId || v.name === voiceId);\nif (!voice) throw new Error(`Unknown ElevenLabs voice: ${voiceId}`);\nif (!text?.trim()) throw new Error(\"TTS text is empty\");","typeGuard":null,"tryCatchPattern":"try {\n  const audio = await elevenlabs.synthesize(text, `${modelId}/${voiceId}`, { apiKey });\n} catch (e) {\n  if (/failed: 429/.test(e.message)) {\n    await sleep(5000);\n    return elevenlabs.synthesize(text, `${modelId}/${voiceId}`, { apiKey });\n  }\n  if (/failed: 40[13]/.test(e.message)) throw new Error(\"Check ElevenLabs key/plan access for this model\");\n  throw e;\n}","preventionTips":["Resolve voice ids from GET /v1/voices instead of hardcoding them.","Confirm your ElevenLabs plan supports the chosen model_id.","Track character quota to avoid surprise 429/401s mid-month.","Log response bodies on this path to see ElevenLabs' detail.message when the fallback message fires."],"tags":["network","tts","upstream-provider","http-status","quota"],"backgroundTag":"upstream-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}