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

  1. Open Settings, find the OpenRouter provider, and paste the API key from openrouter.ai/keys.
  2. Or export OPENROUTER_API_KEY in the daemon's environment and restart.
  3. 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

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


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