{"record":{"id":"7b97472c4ae3edc5","repo":"decolua/9router","slug":"elevenlabs-api-key-required","errorCode":null,"errorMessage":"ElevenLabs API key required","messagePattern":"ElevenLabs API key required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"open-sse/handlers/ttsProviders/elevenlabs.js","lineNumber":8,"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\");","sourceCodeStart":1,"sourceCodeEnd":26,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/ttsProviders/elevenlabs.js#L1-L26","documentation":"fetchElevenLabsVoices requires an ElevenLabs API key (sent as the `xi-api-key` header) and throws this error immediately when called with a falsy key. It is a guard against making an authenticated list-voices call without credentials, since ElevenLabs will only reject the request later with a 401.","triggerScenarios":"Calling fetchElevenLabsVoices() (via the `voices` handler) with the ElevenLabs provider selected while no API key is configured — credentials missing, credentials present but the key field named incorrectly (e.g. `token` instead of `apiKey`), or an empty-string key stored in the dashboard.","commonSituations":"User enabled the ElevenLabs TTS provider in 9Router but never entered an API key; key stored under the wrong credential field after migration; key was deleted/blanked in the credentials UI; code path invoked programmatically without passing credentials.","solutions":["Enter a valid ElevenLabs API key in the 9Router dashboard credentials for the ElevenLabs provider.","Verify the credential object shape: the code reads `credentials.apiKey` — ensure your key is stored under `apiKey`, not another field name.","Test the key directly: `curl -H \"xi-api-key: $KEY\" https://api.elevenlabs.io/v1/voices` should return 200.","If calling fetchElevenLabsVoices programmatically, pass the key explicitly: fetchElevenLabsVoices(process.env.ELEVENLABS_API_KEY)."],"exampleFix":"// before\nconst voices = await fetchElevenLabsVoices(creds.elevenKey); // undefined field\n// after\nconst key = creds?.apiKey || process.env.ELEVENLABS_API_KEY;\nif (!key) throw new Error(\"Set an ElevenLabs API key before listing voices\");\nconst voices = await fetchElevenLabsVoices(key);","handlingStrategy":"validation","validationCode":"function hasElevenLabsCredentials(creds) {\n  return typeof creds?.apiKey === \"string\" && creds.apiKey.trim().length > 0;\n}\nif (!hasElevenLabsCredentials(creds)) throw new Error(\"Configure an ElevenLabs API key first\");","typeGuard":"function hasApiKey(creds) {\n  return typeof creds === \"object\" && creds !== null &&\n    typeof creds.apiKey === \"string\" && creds.apiKey.length > 0;\n}","tryCatchPattern":"try {\n  const voices = await fetchElevenLabsVoices(apiKey);\n} catch (e) {\n  if (e.message === \"ElevenLabs API key required\") {\n    // surface a config error to the user, not a 500\n    return res.status(400).json({ error: \"ElevenLabs provider is not configured — add an API key in the dashboard\" });\n  }\n  throw e;\n}","preventionTips":["Configure the ElevenLabs API key immediately after enabling the provider.","Always store keys under the `apiKey` credential field.","Trim keys when pasting to avoid blank/whitespace-only values.","Add a startup health check that lists voices once to verify the key early."],"tags":["authentication","missing-credentials","tts","api-key"],"backgroundTag":"missing-api-key","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}