{"record":{"id":"383eead0ed5150e3","repo":"decolua/9router","slug":"provider-api-key-required","errorCode":null,"errorMessage":"${provider} API key required","messagePattern":"(.+?) API key required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"open-sse/handlers/ttsProviders/index.js","lineNumber":39,"sourceCode":"  openrouter,\n  gemini,\n  \"xiaomi-mimo\": xiaomiMimo,\n  \"selfhosted-tts\": selfhostedTts,\n};\n\nexport function getTtsAdapter(provider) {\n  return SPECIAL_ADAPTERS[provider] || null;\n}\n\n// Generic config-driven dispatcher (uses ttsConfig.format)\nexport async function synthesizeViaConfig(provider, text, model, credentials) {\n  const { AI_PROVIDERS } = await import(\"@/shared/constants/providers\");\n  const cfg = AI_PROVIDERS[provider]?.ttsConfig;\n  if (!cfg) return null;\n  const handler = FORMAT_HANDLERS[cfg.format];\n  if (!handler) return null;\n  const apiKey = credentials?.apiKey;\n  if (cfg.authType !== \"none\" && !apiKey) throw new Error(`${provider} API key required`);\n  const { PROVIDER_MODELS } = await import(\"open-sse/config/providerModels.js\");\n  const ttsModels = (PROVIDER_MODELS[provider] || []).filter(m => (m.kind || m.type) === \"tts\");\n  const defaultModel = ttsModels[0]?.id || \"\";\n  const { modelId, voiceId } = parseModelVoice(model, defaultModel, \"\", ttsModels);\n  return handler({ baseUrl: cfg.baseUrl, apiKey, text, modelId, voiceId });\n}\n\n// Voice fetchers (used by /api/media-providers/tts/voices route)\nexport const VOICE_FETCHERS = {\n  \"edge-tts\": fetchEdgeTtsVoices,\n  \"local-device\": fetchLocalDeviceVoices,\n  elevenlabs: fetchElevenLabsVoices,\n  gemini: fetchGeminiVoices,\n};\n\n// Re-export for backward compat\nexport { fetchEdgeTtsVoices, fetchLocalDeviceVoices, fetchElevenLabsVoices, fetchGeminiVoices };\n","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/open-sse/handlers/ttsProviders/index.js#L21-L57","documentation":"`synthesizeViaConfig` resolves the provider's TTS config from AI_PROVIDERS and its credentials. For providers whose `ttsConfig.authType` is not `none`, an API key is mandatory; missing credentials throw `${provider} API key required`. This is a client-side configuration gate before any upstream call.","triggerScenarios":"Calling synthesize for a TTS provider (e.g. elevenlabs, cartesia, inworld) whose stored credentials have no `apiKey`, while that provider's ttsConfig.authType is anything other than `none`.","commonSituations":"Fresh install with no credentials configured; credentials saved for chat completions but the TTS-specific key field empty; using a provider like the free Google translate path whose authType is `none` vs a keyed provider confused in config; env var not imported into the credential store.","solutions":["Configure the provider's API key in the dashboard credential store (or the credentials object passed to synthesize)","Verify AI_PROVIDERS[provider].ttsConfig.authType — only `none` providers (like the Google scrape path) work keyless","Check the key is stored under the field the credentials resolver reads (`apiKey`), not a differently named field","If you intended a free/local TTS, switch the model/provider to one with authType `none`"],"exampleFix":"// before\nawait synthesizeViaConfig({ provider: 'elevenlabs', text: 'hi', credentials: {} });\n// after\nawait synthesizeViaConfig({ provider: 'elevenlabs', text: 'hi', credentials: { apiKey: process.env.ELEVENLABS_API_KEY } });","handlingStrategy":"validation","validationCode":"const cfg = AI_PROVIDERS[provider]?.ttsConfig;\nif (cfg && cfg.authType !== 'none' && !credentials?.apiKey) {\n  throw new Error(`${provider} API key must be configured before TTS calls`);\n}","typeGuard":"const hasApiKey = (c) => c != null && typeof c.apiKey === 'string' && c.apiKey.length > 0;","tryCatchPattern":"try { return await synthesizeViaConfig(opts); } catch (e) { if (e.message.endsWith('API key required')) { throw new ConfigError(`Set an API key for ${provider} in the dashboard`); } throw e; }","preventionTips":["Configure provider credentials during setup and verify with a health check","Check ttsConfig.authType before selecting a provider for keyless use","Surface the key requirement in UI before the user invokes TTS"],"tags":["validation","config","api-key","tts","credentials"],"backgroundTag":"missing-api-key","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}