nexu-io/open-design · error · Error
no OpenRouter API key — configure it in Settings or set OPEN
Error message
no OpenRouter API key — configure it in Settings or set OPENROUTER_API_KEY
What it means
Thrown by renderOpenRouterImage before any network call when credentials.apiKey is empty. OpenRouter routes image-capable models through its /api/v1/chat/completions endpoint and requires a bearer token; without one the request cannot authenticate. The message names both the Settings UI and the OPENROUTER_API_KEY env var as remediation paths.
Source
Thrown at apps/daemon/src/media/index.ts:1815
//
// ---------------------------------------------------------------------------
// OpenRouter image generation via Chat Completions API
// ---------------------------------------------------------------------------
// Unlike the dedicated /videos endpoint (async polling), image generation
// goes through /chat/completions with `modalities: ["image"]` (or
// `["image", "text"]` for multi-modal models like Gemini). The response
// embeds generated images as base64 data URLs in
// `choices[0].message.images[].image_url.url`.
//
// Model IDs follow the same `openrouter/`-prefix convention as video.
// ---------------------------------------------------------------------------
async function renderOpenRouterImage(
ctx: MediaContext,
credentials: ProviderConfig,
): Promise<RenderResult> {
if (!credentials.apiKey) {
throw new Error(
'no OpenRouter API key — configure it in Settings or set OPENROUTER_API_KEY',
);
}
const baseUrl = (credentials.baseUrl || 'https://openrouter.ai/api/v1').replace(/\/$/, '');
// Respect model-alias contract: credentials.model (from stored config)
// overrides ctx.wireModel (from OD_MEDIA_MODEL_ALIASES / resolveModelAlias).
// Then strip the `openrouter/` catalogue prefix so the wire model name
// matches OpenRouter's canonical slug.
const resolved = (credentials.model || ctx.wireModel).trim();
const wireModel = resolved.startsWith('openrouter/')
? resolved.slice('openrouter/'.length)
: resolved;
// Multi-modal models (Gemini variants) accept both image and text
// output; image-only models (Flux, Recraft, Sourceful) only accept
// ["image"]. We use a simple heuristic on the slug.
const modalities: string[] = wireModel.includes('gemini')View on GitHub (pinned to 5be4028344)
Solutions
- Open Settings, find the OpenRouter provider, and paste the API key from openrouter.ai/keys.
- Or export OPENROUTER_API_KEY in the daemon's environment and restart.
- If the key was entered but the error persists, verify the credentials loader is binding the key into credentials.apiKey for this provider id.
Example fix
// before
if (!credentials.apiKey) {
throw new Error('no OpenRouter API key — configure it in Settings or set OPENROUTER_API_KEY');
}
// after — env fallback for headless setups
const apiKey = credentials.apiKey || process.env.OPENROUTER_API_KEY;
if (!apiKey) {
throw new Error('no OpenRouter API key — configure it in Settings or set OPENROUTER_API_KEY');
} Defensive patterns
Strategy: validation
Validate before calling
function openRouterImageConfigured(creds: ProviderConfig, env = process.env): boolean {
return Boolean((creds.apiKey && creds.apiKey.trim()) || env.OPENROUTER_API_KEY);
}
if (ctx.providerId === 'openrouter' && ctx.kind === 'image' && !openRouterImageConfigured(credentials)) {
return { kind: 'config-error', message: 'Add your OpenRouter API key in Settings or set OPENROUTER_API_KEY.' };
} Type guard
function hasOpenRouterKey(creds: ProviderConfig): creds is ProviderConfig & { apiKey: string } {
return typeof creds.apiKey === 'string' && creds.apiKey.trim().length > 0;
} Try / catch
try {
await renderOpenRouterImage(ctx, credentials);
} catch (e) {
if (/no OpenRouter API key/.test(String(e))) {
return presentConfigPrompt('openrouter');
}
throw e;
} Prevention
- Run a credential pre-flight in the provider registry before dispatching.
- Allow OPENROUTER_API_KEY env fallback at the call site.
- Surface a Settings deep-link on missing-key errors.
- Re-validate credentials after config migrations.
When it happens
Trigger: User selects an OpenRouter image model in Settings but never pasted a key, OPENROUTER_API_KEY is unset in the daemon environment, or the credentials store was reset. The provider dispatch reaches renderOpenRouterImage and immediately fails this guard.
Common situations: Fresh install where the OpenRouter row in Settings has no key; CI/daemon run without the env var exported; key wiped by a config migration.
Related errors
- no Nano Banana API key — configure it in Settings or set OD_
- openrouter image ${resp.status}: ${truncate(text, 240)}
- nano-banana image ${resp.status}: ${truncate(text, 240)}
- openrouter image non-JSON response: ${truncate(text, 200)}
- openrouter image response contained no images for model ${wi
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/eb681ea1e6ea27ed.
Report an issue: GitHub.