{"record":{"id":"21778811fdd6f3e7","repo":"nexu-io/open-design","slug":"no-openai-credential-configure-an-api-key-in-set-217788","errorCode":null,"errorMessage":"no OpenAI credential — configure an API key in Settings or set OPENAI_API_KEY","messagePattern":"no OpenAI credential — configure an API key in Settings or set OPENAI_API_KEY","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/media/index.ts","lineNumber":1319,"sourceCode":"  parsed.pathname = parsed.pathname.replace(/\\/+$/, '') + '/audio/speech';\n  if (isAzure && !parsed.searchParams.has('api-version')) {\n    parsed.searchParams.set('api-version', AZURE_DEFAULT_API_VERSION);\n  }\n  return parsed.toString();\n}\n\nfunction openaiSpeechFormatFor(fileName: string): string {\n  const ext = path.extname(fileName).toLowerCase();\n  if (ext === '.wav') return 'wav';\n  if (ext === '.flac') return 'flac';\n  if (ext === '.aac') return 'aac';\n  if (ext === '.opus' || ext === '.ogg' || ext === '.oga') return 'opus';\n  return 'mp3';\n}\n\nasync function renderOpenAISpeech(ctx: MediaContext, credentials: ProviderConfig, fileName: string): Promise<RenderResult> {\n  if (!credentials.apiKey) {\n    throw new Error('no OpenAI credential — configure an API key in Settings or set OPENAI_API_KEY');\n  }\n  const rawBase = credentials.baseUrl || 'https://api.openai.com/v1';\n  const azure = detectAzureEndpoint(rawBase);\n  const url = buildOpenAISpeechUrl(rawBase, azure);\n  const format = openaiSpeechFormatFor(fileName);\n  const text = (ctx.prompt && ctx.prompt.trim()) || 'This is a test.';\n\n  let voiceId = 'alloy';\n  let instructions = '';\n  const requestedVoice = (ctx.voice && ctx.voice.trim()) || '';\n  if (requestedVoice) {\n    if (OPENAI_TTS_VOICES.has(requestedVoice)) {\n      voiceId = requestedVoice;\n    } else {\n      // gpt-4o-mini-tts accepts free-form speaking style instructions.\n      // If the UI metadata carries prose rather than a concrete voice id,\n      // preserve it here instead of surfacing a provider error.\n      instructions = requestedVoice;","sourceCodeStart":1301,"sourceCodeEnd":1337,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/media/index.ts#L1301-L1337","documentation":"Thrown at the top of renderOpenAISpeech when credentials.apiKey is falsy. The speech renderer calls POST {baseUrl}/v1/audio/speech (or the Azure deployment equivalent) and needs a Bearer key; without one it refuses before any network call. Same credential contract as the image path but for text-to-speech.","triggerScenarios":"Calling --surface audio with an OpenAI TTS model (gpt-4o-mini-tts and similar) with no OpenAI credential stored and no OPENAI_API_KEY env var; key cleared from Settings but TTS model still selectable.","commonSituations":"Fresh install; user moved OPENAI_API_KEY out of the shell rc; BYOK daemon process not inheriting the user's env; key revoked.","solutions":["Set OPENAI_API_KEY in the daemon environment, or add the key under Settings -> Media Providers -> OpenAI.","For Azure TTS, populate the same apiKey field with the Azure deployment key and set baseUrl to the deployment URL.","Restart the daemon after setting the env var.","Verify with od media providers that the OpenAI row shows a configured key."],"exampleFix":"// before\nod media generate --surface audio --audio-kind speech --model gpt-4o-mini-tts --prompt \"...\"\n// after\nexport OPENAI_API_KEY=sk-...\nod media generate --surface audio --audio-kind speech --model gpt-4o-mini-tts --prompt \"...\"","handlingStrategy":"validation","validationCode":"function ensureOpenAICredential(creds: {apiKey?: string} | null): asserts creds is { apiKey: string } {\n  if (!creds?.apiKey) {\n    throw new Error('no OpenAI credential — configure an API key in Settings or set OPENAI_API_KEY');\n  }\n}\nensureOpenAICredential(credentials);","typeGuard":"function hasOpenAIKey(c: {apiKey?:string} | null): c is { apiKey: string } {\n  return Boolean(c && typeof c.apiKey === 'string' && c.apiKey.length > 0);\n}","tryCatchPattern":null,"preventionTips":["Pre-flight the OpenAI credential before offering TTS models in the picker.","Document that the daemon process must have OPENAI_API_KEY exported (BYOK runs do not inherit login shell env).","For Azure TTS, ensure both baseUrl (deployment URL) and apiKey are set."],"tags":["media","provider-config","openai","credentials","audio","tts"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}