{"record":{"id":"241148805349563c","repo":"decolua/9router","slug":"elevenlabs-voices-fetch-failed-res-status","errorCode":null,"errorMessage":"ElevenLabs voices fetch failed: ${res.status}","messagePattern":"ElevenLabs voices fetch failed: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"open-sse/handlers/ttsProviders/elevenlabs.js","lineNumber":16,"sourceCode":"// ElevenLabs TTS — voice id with optional model_id prefix\nimport { Buffer } from \"node:buffer\";\n\nconst VOICES_TTL = 24 * 60 * 60 * 1000;\nconst _voicesCache = new Map(); // by API key\n\nexport async function fetchElevenLabsVoices(apiKey) {\n  if (!apiKey) throw new Error(\"ElevenLabs API key required\");\n  const now = Date.now();\n  const cached = _voicesCache.get(apiKey);\n  if (cached && now - cached.time < VOICES_TTL) return cached.voices;\n\n  const res = await fetch(\"https://api.elevenlabs.io/v1/voices\", {\n    headers: { \"xi-api-key\": apiKey, \"Content-Type\": \"application/json\" },\n  });\n  if (!res.ok) throw new Error(`ElevenLabs voices fetch failed: ${res.status}`);\n  const data = await res.json();\n  // Normalize: derive lang from labels for grouping\n  const voices = (data.voices || []).map((v) => ({ ...v, lang: v.labels?.language || \"en\" }));\n  _voicesCache.set(apiKey, { voices, time: now });\n  return voices;\n}\n\nexport 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({","sourceCodeStart":1,"sourceCodeEnd":34,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/ttsProviders/elevenlabs.js#L1-L34","documentation":"When the GET to https://api.elevenlabs.io/v1/voices returns a non-OK status, fetchElevenLabsVoices throws `ElevenLabs voices fetch failed: <status>` (only the numeric status is included). It signals that the authenticated voices-list request was rejected or failed upstream; the numeric code distinguishes auth problems (401) from quota/plan issues (401/429) and outages (5xx).","triggerScenarios":"Any non-OK response from the ElevenLabs /v1/voices endpoint: invalid or revoked API key (401), free-tier/quota restrictions (401/429), ElevenLabs outage or maintenance (500/503), or network-layer responses like 451 in restricted regions.","commonSituations":"API key rotated or deleted in the ElevenLabs dashboard while still configured in 9Router; free-tier account hitting rate limits; typo'd key (extra whitespace/newline when pasting); ElevenLabs regional availability changes; temporary upstream outage.","solutions":["Match the status to ElevenLabs docs: 401 → re-generate the API key and update it in the 9Router dashboard; 429 → wait and retry, or upgrade the ElevenLabs plan.","Trim the key when pasting — a trailing newline or space breaks the xi-api-key header.","Validate the key directly: `curl -H \"xi-api-key: $KEY\" https://api.elevenlabs.io/v1/voices` — if it fails outside 9Router, the problem is the key/account, not this code.","Check https://status.elevenlabs.io for outages if the status is 5xx.","Voice results are cached per key for 24h; if the key was fixed after a failure, no stale cache is involved — simply retry."],"exampleFix":"// before\nconst res = await fetch(\"https://api.elevenlabs.io/v1/voices\", { headers: { \"xi-api-key\": rawKey } });\n// after: sanitize the key and surface the body\nconst key = String(rawKey || \"\").trim();\nconst res = await fetch(\"https://api.elevenlabs.io/v1/voices\", { headers: { \"xi-api-key\": key } });\nif (!res.ok) {\n  const detail = await res.text().catch(() => \"\");\n  throw new Error(`ElevenLabs voices fetch failed: ${res.status} ${detail}`);\n}","handlingStrategy":"try-catch","validationCode":"if (!apiKey?.trim()) throw new Error(\"ElevenLabs API key missing\");\n// optional pre-check of key validity\ncurl -fsS -H \"xi-api-key: $KEY\" https://api.elevenlabs.io/v1/voices > /dev/null","typeGuard":null,"tryCatchPattern":"try {\n  const voices = await fetchElevenLabsVoices(apiKey);\n} catch (e) {\n  const m = e.message.match(/failed: (\\d+)/);\n  if (m?.[1] === \"401\") throw new Error(\"ElevenLabs key invalid/revoked — regenerate it\");\n  if (m?.[1] === \"429\") throw new Error(\"ElevenLabs rate limited — retry later\");\n  if (m && +m[1] >= 500) throw new Error(\"ElevenLabs outage — try again shortly\");\n  throw e;\n}","preventionTips":["Trim API keys on input (stray whitespace breaks the header).","Rotate keys deliberately: update 9Router in the same change as the ElevenLabs dashboard.","Watch your ElevenLabs quota/plan to anticipate 429s.","Subscribe to ElevenLabs status page for outage awareness."],"tags":["authentication","network","tts","upstream-provider","http-status"],"backgroundTag":"upstream-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}