nexu-io/open-design · error · Error
openrouter image non-JSON response: ${truncate(text, 200)}
Error message
openrouter image non-JSON response: ${truncate(text, 200)} What it means
Thrown by renderOpenRouterImage when HTTP returned 2xx but JSON.parse fails on the body. OpenRouter normally returns JSON for chat completions; a non-JSON 2xx typically means an interceptor (proxy, captive portal) rewrote the response, or a misconfigured baseUrl pointed at a host returning HTML.
Source
Thrown at apps/daemon/src/media/index.ts:1877
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),
);
}
const dataUrl: string | undefined = images[0]?.image_url?.url;
if (!dataUrl) {
throw new Error(
`openrouter image response missing image_url.url: ${truncate(text, 200)}`,
);
}View on GitHub (pinned to 5be4028344)
Solutions
- Inspect the truncated body in the error — HTML tags confirm interception/misroute.
- Reset credentials.baseUrl to the default (https://openrouter.ai/api/v1) or remove it.
- Bypass any HTTP proxy for openrouter.ai, or whitelist the host.
- Retry once for transient interstitials.
Example fix
// before
try { data = JSON.parse(text); }
catch { throw new Error(`openrouter image non-JSON response: ${truncate(text, 200)}`); }
// after — flag HTML interception
try { data = JSON.parse(text); }
catch {
const html = /<html|<!doctype/i.test(text);
throw new Error(
`openrouter image ${html ? 'returned HTML (interception?)' : 'non-JSON response'}: ${truncate(text, 200)}`,
);
} Defensive patterns
Strategy: validation
Validate before calling
function looksLikeHtmlBody(text: string): boolean {
return /<html|<!doctype|<title>/i.test(text.slice(0, 200));
}
if (looksLikeHtmlBody(text)) {
throw new Error(`openrouter image returned HTML (proxy/interception or wrong baseUrl): ${truncate(text, 200)}`);
} Type guard
function isJsonContentType(resp: Response): boolean {
const ct = resp.headers.get('content-type') || '';
return ct.includes('application/json') || ct.includes('+json');
} Try / catch
try {
data = JSON.parse(text);
} catch {
if (looksLikeHtmlBody(text)) {
throw new Error(`openrouter image returned HTML (interception?): ${truncate(text, 200)}`);
}
throw new Error(`openrouter image non-JSON response: ${truncate(text, 200)}`);
} Prevention
- Validate content-type and body prefix before parsing.
- Reset baseUrl to the default OpenRouter endpoint when interception is suspected.
- Whitelist openrouter.ai through corporate proxies.
- Log the first 200 chars of non-JSON bodies for diagnosis.
When it happens
Trigger: Reverse proxy returns an HTML ' upstream' page with 200, baseUrl points at a non-OpenRouter host, or a CDN served a cached error page. The guard prevents the subsequent choices[0].message.images extraction from running on garbage.
Common situations: Corporate proxy intercepting openrouter.ai, a typo'd baseUrl hitting a parked domain, or a transient CDN interstitial during a regional outage.
Related errors
- nano-banana image non-JSON: ${truncate(text, 200)}
- openrouter image response contained no images for model ${wi
- openrouter image response missing image_url.url: ${truncate(
- openrouter image download ${imgResp.status}
- openrouter video non-JSON: ${truncate(submitText, 200)}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/a160f7738fa26cc5.
Report an issue: GitHub.