nexu-io/open-design · error · Error

openai non-JSON response

Error message

openai non-JSON response: ${truncate(text, 200)}

What it means

Thrown by renderOpenAIImage when resp.ok is true but JSON.parse(text) throws. The dispatcher assumes a JSON images/generations response; a non-JSON 200 usually means a man-in-the-middle (corporate proxy, captive portal, service mesh) returned an HTML page with a 200 status, or the baseUrl was pointed at an endpoint that does not speak the OpenAI schema.

Solutions

  1. Inspect the truncated body in the message to identify what returned the non-JSON response (proxy page, gateway error, etc.).
  2. Verify credentials.baseUrl resolves to a real OpenAI/Azure endpoint and not a UI or docs host.
  3. Bypass or reconfigure the intercepting proxy for the OpenAI host, or point baseUrl at the proxy's true API path.
  4. If running a custom OpenAI-compatible server, ensure it returns application/json with the {data:[...]} schema on success.

Example fix

// before
OD_OPENAI_BASE_URL=https://gateway.internal/
// after (point at the API path the gateway actually serves)
OD_OPENAI_BASE_URL=https://gateway.internal/v1
Defensive patterns

Strategy: try-catch

Validate before calling

function isOpenAIJsonEndpoint(baseUrl: string): boolean {
  // crude guard: must look like an API host, not a UI/docs host
  return /^https:\/\/[\w.-]+\.openai\.com(?:\/.*)?$/.test(baseUrl)
    || /\/v1$/.test(baseUrl)
    || /openai\.azure\.com/.test(baseUrl);
}

Try / catch

try {
  return await renderOpenAIImage(ctx, credentials);
} catch (err) {
  const m = err instanceof Error ? err.message : '';
  if (m.startsWith('openai non-JSON response:')) {
    // surface the body snippet to the user — almost always a proxy/gateway issue
    throw new ConfigError(`OpenAI returned HTML/text, not JSON. Check baseUrl and proxies. Body: ${m}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Custom baseUrl pointed at a browser-facing gateway that returns HTML; corporate TLS-intercepting proxy injecting a block page; misconfigured reverse proxy returning a health-check page; baseUrl pointing at /v1 instead of the host root.

Common situations: Self-hosted gateway in front of OpenAI that 200s with HTML on unknown paths; Azure app-gateway rewrite rule stripping the body; local mock server returning text/plain.

Related errors


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

Appendix: source

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

  const resp = await fetch(url, withMediaRequestInit(ctx, {
    method: 'POST',
    headers,
    body: JSON.stringify(body),
    dispatcher: ctx.requestInit.dispatcher
      ?? openAIImageDispatcher as unknown as NonNullable<RequestInit['dispatcher']>,
    signal: AbortSignal.timeout(Math.max(OPENAI_IMAGE_HEADERS_TIMEOUT_MS, OPENAI_IMAGE_BODY_TIMEOUT_MS)),
  }));
  const text = await resp.text();
  if (!resp.ok) {
    const tag = azure ? 'azure-openai' : 'openai';
    throw new Error(`${tag} ${resp.status}: ${truncate(text, 240)}`);
  }
  let data: any;
  try {
    data = JSON.parse(text);
  } catch {
    throw new Error(`openai non-JSON response: ${truncate(text, 200)}`);
  }
  const entry = data && Array.isArray(data.data) ? data.data[0] : null;
  if (!entry) throw new Error('openai response had no data[0]');
  let bytes;
  if (entry.b64_json) {
    bytes = Buffer.from(entry.b64_json, 'base64');
  } else if (entry.url) {
    const imgResp = await fetch(entry.url, withMediaRequestInit(ctx));
    if (!imgResp.ok) throw new Error(`openai image fetch ${imgResp.status}`);
    const arr = await imgResp.arrayBuffer();
    bytes = Buffer.from(arr);
  } else {
    throw new Error('openai response had neither b64_json nor url');
  }

  const tag = azure ? 'azure-openai' : 'openai';
  return {
    bytes,

View on GitHub (pinned to 5be4028344)