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

  1. Set OPENAI_API_KEY in the daemon environment, or add the key under Settings -> Media Providers -> OpenAI.
  2. For Azure TTS, populate the same apiKey field with the Azure deployment key and set baseUrl to the deployment URL.
  3. Restart the daemon after setting the env var.
  4. 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

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


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/21778811fdd6f3e7. Report an issue: GitHub.