nexu-io/open-design · error · Error

openrouter image ${resp.status}: ${truncate(text, 240)}

Error message

openrouter image ${resp.status}: ${truncate(text, 240)}

What it means

Thrown by renderOpenRouterImage when POST /api/v1/chat/completions returns non-2xx. The request carries authorization bearer, HTTP-Referer, X-Title attribution headers, and an AbortSignal.timeout capped by max(OPENAI_IMAGE_HEADERS_TIMEOUT_MS, OPENAI_IMAGE_BODY_TIMEOUT_MS). The error embeds status plus the first 240 chars of the body.

Source

Thrown at apps/daemon/src/media/index.ts:1870

    aspect_ratio: aspectRatio,
    image_size: '1K',
  };
  body.image_config = imageConfig;

  const resp = await fetch(`${baseUrl}/chat/completions`, withMediaRequestInit(ctx, {
    method: 'POST',
    headers: {
      'authorization': `Bearer ${credentials.apiKey}`,
      'content-type': 'application/json',
      'HTTP-Referer': 'https://opendesign.dev',
      'X-Title': 'Open Design',
    },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(Math.max(OPENAI_IMAGE_HEADERS_TIMEOUT_MS, OPENAI_IMAGE_BODY_TIMEOUT_MS)),
  }));
  const text = await resp.text();
  if (!resp.ok) {
    throw new Error(`openrouter image ${resp.status}: ${truncate(text, 240)}`);
  }

  let data: any;
  try {
    data = JSON.parse(text);
  } catch {
    throw new Error(`openrouter image non-JSON response: ${truncate(text, 200)}`);
  }

  // Extract the first generated image from the response.
  const images: any[] | undefined =
    data?.choices?.[0]?.message?.images;
  if (!images || images.length === 0) {
    throw new Error(
      `openrouter image response contained no images for model ${wireModel}: `
      + truncate(text, 200),
    );
  }

View on GitHub (pinned to 5be4028344)

Solutions

  1. Read the embedded status+body: 401→fix key, 402→add credits, 404→check the model slug, 429→slow down, 400→check modality support.
  2. Confirm the resolved wireModel (after stripping the openrouter/ prefix) matches a slug listed at openrouter.ai/models with image output.
  3. Verify the OpenRouter account has credits for the chosen model tier.
  4. If 5xx, retry with backoff or pick an alternate image model.

Example fix

// before
if (!resp.ok) {
  throw new Error(`openrouter image ${resp.status}: ${truncate(text, 240)}`);
}

// after — actionable hints by status
if (!resp.ok) {
  const hint = resp.status === 401 ? ' (bad key)'
    : resp.status === 402 ? ' (no credits)'
    : resp.status === 404 ? ' (unknown model)'
    : resp.status === 429 ? ' (rate limited)'
    : '';
  throw new Error(`openrouter image ${resp.status}${hint}: ${truncate(text, 240)}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

function describeOpenRouterImageError(status: number, body: string): string {
  const hint = status === 401 ? 'bad key'
    : status === 402 ? 'no credits'
    : status === 404 ? 'unknown model'
    : status === 429 ? 'rate limited'
    : status >= 500 ? 'upstream provider outage — retry'
    : 'see body';
  return `openrouter image ${status} (${hint}): ${truncate(body, 240)}`;
}

function isRetryableOpenRouterStatus(status: number): boolean {
  return status === 429 || status >= 500;
}

Type guard

function isOpenRouterModelCapable(model: string): boolean {
  // Caller should consult the OpenRouter catalogue; this is a last-mile sanity check
  return /image|vision|gemini|dall-e|flux|seedream/i.test(model);
}

Try / catch

for (let attempt = 0; attempt < 2; attempt++) {
  const resp = await fetch(/* ... */);
  if (resp.ok) { /* parse and return */ }
  if (attempt === 0 && isRetryableOpenRouterStatus(resp.status)) {
    await sleep(1500 * (attempt + 1));
    continue;
  }
  throw new Error(describeOpenRouterImageError(resp.status, await resp.text()));
}

Prevention

When it happens

Trigger: 401 (bad/missing key), 402 (insufficient credits on the OpenRouter account), 404 (model slug not in the OpenRouter catalogue or not enabled for the key), 429 (rate limit), 400 (model does not support the requested modalities or the prompt exceeds limits), or upstream 5xx from the underlying provider routed through OpenRouter.

Common situations: Free OpenRouter key with no credits hitting a paid image model, model id with the wrong prefix, regional rate limits, or the underlying provider (e.g. Gemini-via-OpenRouter) incident surfacing as a 5xx.

Related errors


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