nexu-io/open-design · error · Error
no ElevenLabs API key - configure it in Settings or set OD_E
Error message
no ElevenLabs API key - configure it in Settings or set OD_ELEVENLABS_API_KEY
What it means
Thrown by listElevenLabsVoiceOptions() when resolveProviderConfig(projectRoot, 'elevenlabs') returns credentials with no apiKey. The function builds the voice-picker catalogue from the ElevenLabs /v2/voices endpoint, which requires a valid xi-api-key header, so a missing key is a hard precondition.
Source
Thrown at apps/daemon/src/integrations/elevenlabs-voices.ts:105
}
function cloneVoiceOptions(voices: ElevenLabsVoiceOption[]): ElevenLabsVoiceOption[] {
return voices.map((voice) => ({
...voice,
...(voice.labels ? { labels: { ...voice.labels } } : {}),
}));
}
export async function listElevenLabsVoiceOptions(
projectRoot: string,
options: {
limit?: number;
requestInit?: Pick<RequestInit, 'dispatcher'>;
} = {},
): Promise<ElevenLabsVoiceOption[]> {
const credentials = await resolveProviderConfig(projectRoot, 'elevenlabs');
if (!credentials.apiKey) {
throw new Error(
'no ElevenLabs API key - configure it in Settings or set OD_ELEVENLABS_API_KEY',
);
}
const baseUrl = (credentials.baseUrl || ELEVENLABS_DEFAULT_BASE_URL).replace(
/\/$/,
'',
);
const pageSize = clampLimit(options.limit);
const cacheKey = voiceCacheKey({
projectRoot,
baseUrl,
apiKey: credentials.apiKey,
pageSize,
});
const cached = voiceOptionsCache.get(cacheKey);
const now = Date.now();
if (cached && cached.expiresAt > now) {View on GitHub (pinned to 5be4028344)
Solutions
- Configure ElevenLabs in Settings (writes the per-project media config), or set OD_ELEVENLABS_API_KEY in the daemon env.
- At the call site, resolve the config first and skip / show a setup CTA when credentials.apiKey is empty instead of calling the listing.
- If running in a different deployment, set a per-project media-config.json with elevenlabs.apiKey and baseUrl.
Example fix
// before
const voices = await listElevenLabsVoiceOptions(projectRoot);
// after
const credentials = await resolveProviderConfig(projectRoot, 'elevenlabs');
if (!credentials.apiKey) {
return { voices: [], requiresSetup: true };
}
const voices = await listElevenLabsVoiceOptions(projectRoot); Defensive patterns
Strategy: validation
Validate before calling
import { resolveProviderConfig } from '../media/config.js';
async function hasElevenLabsKey(projectRoot: string): Promise<boolean> {
const credentials = await resolveProviderConfig(projectRoot, 'elevenlabs');
return Boolean(credentials.apiKey && credentials.apiKey.trim());
}
// usage
if (!(await hasElevenLabsKey(projectRoot))) {
return { voices: [], requiresSetup: true };
} Type guard
function hasElevenLabsApiKey(c: { apiKey?: string }): c is { apiKey: string } {
return typeof c.apiKey === 'string' && c.apiKey.trim().length > 0;
} Try / catch
try {
return await listElevenLabsVoiceOptions(projectRoot);
} catch (err) {
if (err instanceof Error && /no ElevenLabs API key/.test(err.message)) {
return { voices: [], requiresSetup: true };
}
throw err;
} Prevention
- Resolve credentials once and short-circuit the picker when apiKey is empty.
- Use the OD_ELEVENLABS_API_KEY env var name exactly (with the OD_ prefix).
- In Settings, write the per-project media-config so other code paths see the key.
When it happens
Trigger: Calling listElevenLabsVoiceOptions() for a project whose media config has no elevenlabs.apiKey, and OD_ELEVENLABS_API_KEY is unset for that project root. The Settings UI voice-picker and any daemon route listing voices both hit this.
Common situations: User opened the voice picker before configuring ElevenLabs in Settings; per-project media-config.json missing the elevenlabs.apiKey field; env var spelled differently (e.g. ELEVENLABS_API_KEY without the OD_ prefix); key field present but empty string.
Related errors
- Cloudflare API token is required.
- collab cloud is not configured (OD_COLLAB_CLOUD_URL is unset
- elevenlabs voices ${resp.status}: ${errText.slice(0, 240)}
- vela binary not found; install vela or configure VELA_BIN
- no Nano Banana API key — configure it in Settings or set OD_
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/d1808170b3d2f99d.
Report an issue: GitHub.