nexu-io/open-design · error · Error
no OpenAI credential — configure an API key in Settings or s
Error message
no OpenAI credential — configure an API key in Settings or set OPENAI_API_KEY
What it means
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.
Source
Thrown at apps/daemon/src/media/index.ts:1319
parsed.pathname = parsed.pathname.replace(/\/+$/, '') + '/audio/speech';
if (isAzure && !parsed.searchParams.has('api-version')) {
parsed.searchParams.set('api-version', AZURE_DEFAULT_API_VERSION);
}
return parsed.toString();
}
function openaiSpeechFormatFor(fileName: string): string {
const ext = path.extname(fileName).toLowerCase();
if (ext === '.wav') return 'wav';
if (ext === '.flac') return 'flac';
if (ext === '.aac') return 'aac';
if (ext === '.opus' || ext === '.ogg' || ext === '.oga') return 'opus';
return 'mp3';
}
async function renderOpenAISpeech(ctx: MediaContext, credentials: ProviderConfig, fileName: string): Promise<RenderResult> {
if (!credentials.apiKey) {
throw new Error('no OpenAI credential — configure an API key in Settings or set OPENAI_API_KEY');
}
const rawBase = credentials.baseUrl || 'https://api.openai.com/v1';
const azure = detectAzureEndpoint(rawBase);
const url = buildOpenAISpeechUrl(rawBase, azure);
const format = openaiSpeechFormatFor(fileName);
const text = (ctx.prompt && ctx.prompt.trim()) || 'This is a test.';
let voiceId = 'alloy';
let instructions = '';
const requestedVoice = (ctx.voice && ctx.voice.trim()) || '';
if (requestedVoice) {
if (OPENAI_TTS_VOICES.has(requestedVoice)) {
voiceId = requestedVoice;
} else {
// gpt-4o-mini-tts accepts free-form speaking style instructions.
// If the UI metadata carries prose rather than a concrete voice id,
// preserve it here instead of surfacing a provider error.
instructions = requestedVoice;View on GitHub (pinned to 5be4028344)
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.
Example fix
// before od media generate --surface audio --audio-kind speech --model gpt-4o-mini-tts --prompt "..." // after export OPENAI_API_KEY=sk-... od media generate --surface audio --audio-kind speech --model gpt-4o-mini-tts --prompt "..."
Defensive patterns
Strategy: validation
Validate before calling
function ensureOpenAICredential(creds: {apiKey?: string} | null): asserts creds is { apiKey: string } {
if (!creds?.apiKey) {
throw new Error('no OpenAI credential — configure an API key in Settings or set OPENAI_API_KEY');
}
}
ensureOpenAICredential(credentials); Type guard
function hasOpenAIKey(c: {apiKey?:string} | null): c is { apiKey: string } {
return Boolean(c && typeof c.apiKey === 'string' && c.apiKey.length > 0);
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Fresh install; user moved OPENAI_API_KEY out of the shell rc; BYOK daemon process not inheriting the user's env; key revoked.
Related errors
- no OpenAI credential - configure an API key in Settings or s
- no ImageRouter API key — configure it in Settings or set OD_
- ${tag} speech ${resp.status}: ${truncate(text, 240)}
- unsupported audioKind: ${audioKind}. Allowed: music | speech
- ${tag} ${resp.status}: ${truncate(text, 240)}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/21778811fdd6f3e7.
Report an issue: GitHub.