{"record":{"id":"21eb78e5c2e8b7af","repo":"immich-app/immich","slug":"expected-a-json-response","errorCode":null,"errorMessage":"Expected a JSON response","messagePattern":"Expected a JSON response","errorType":"exception","errorClass":"MalformedResponseError","httpStatus":null,"severity":"error","filePath":"packages/sdk/src/index.ts","lineNumber":66,"sourceCode":"    throw new Error('The API key header can only be set using setApiKey().');\n  }\n};\n\nexport const jsonOnly =\n  (impl?: typeof fetch): typeof fetch =>\n  async (input, options) => {\n    const response = await (impl ?? fetch)(input, options);\n\n    const expectsJson = new Headers(options?.headers)\n      .get('accept')\n      ?.includes('json');\n    if (!expectsJson || response.status === 204) {\n      return response;\n    }\n\n    const contentType = response.headers.get('content-type');\n    if (!contentType?.includes('json')) {\n      throw new MalformedResponseError(\n        'Expected a JSON response',\n        response.url,\n        response.status,\n        contentType,\n      );\n    }\n\n    return response;\n  };\n\nexport const setFetch = (impl: typeof fetch) => {\n  defaults.fetch = jsonOnly(impl);\n};\n\ndefaults.fetch = jsonOnly();\n\nexport const getAssetOriginalPath = (id: string) => `/assets/${id}/original`;\n","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/immich-app/immich/blob/5666d57f15a66bd5518119c5d9f4d2b62f3a86c1/packages/sdk/src/index.ts#L48-L84","documentation":"The SDK wraps its fetch implementation with `jsonOnly`, a fetch middleware that verifies a response actually is JSON whenever the request's Accept header asks for JSON (and the status isn't 204). If the response's Content-Type header is missing or does not include 'json', a MalformedResponseError('Expected a JSON response') is thrown, carrying the URL, status, and content type. This guards callers against receiving HTML error pages, empty bodies, or binary data when a typed JSON response is expected.","triggerScenarios":"Any SDK call that sends Accept: application/json but whose server response Content-Type is not JSON — e.g. the server returns an HTML error/interstitial page (reverse proxy, login portal, Cloudflare block page), returns an empty body with text/plain, redirects to a non-JSON endpoint, or a custom fetch impl (setFetch) that mislabels or strips Content-Type.","commonSituations":"Wrong baseUrl pointing at a web UI instead of the API; a proxy or WAF intercepting requests and returning HTML 403/502 pages; misconfigured server behind auth that responds with a redirect to a login page; custom fetch implementations (via setFetch) that swallow or rewrite headers.","solutions":["Check response.url/status/contentType in the MalformedResponseError to see what actually came back; usually the baseUrl is wrong or a proxy returned an HTML page.","Verify baseUrl points at the API server root (not the web app) and is reachable without browser redirects.","Check proxy/WAF/anti-bot layers (nginx, Cloudflare) and either fix them or add an allowlist for the API path.","If you intentionally call a non-JSON endpoint (e.g. binary asset download), omit the Accept: application/json header from the request so jsonOnly skips the check.","If using a custom fetch via setFetch, ensure it forwards the upstream Content-Type header unchanged."],"exampleFix":"// before\ninit({ baseUrl: 'https://my-immich.example.com' }); // hits web app, gets HTML\n// after\ninit({ baseUrl: 'https://my-immich.example.com/api' }); // hits JSON API","handlingStrategy":"try-catch","validationCode":"// check what the server actually returns before trusting JSON\nconst res = await fetch(url, { headers: { accept: 'application/json' } });\nconst ct = res.headers.get('content-type') ?? '';\nif (!ct.includes('json')) console.warn(`non-JSON from ${res.url}: ${ct}, status ${res.status}`);","typeGuard":"function isMalformedResponseError(e: unknown): e is MalformedResponseError {\n  return e instanceof MalformedResponseError;\n}","tryCatchPattern":"try {\n  const res = await sdk.someJsonCall();\n} catch (e) {\n  if (isMalformedResponseError(e)) {\n    console.error(`Non-JSON response from ${e.url} (status ${e.status}, content-type ${e.contentType})`);\n  } else throw e;\n}","preventionTips":["Point baseUrl at the JSON API root, not the web UI.","Keep Accept: application/json only on endpoints that truly return JSON; drop it for binary asset downloads.","Check proxy/WAF rules that replace API responses with HTML error pages.","When wrapping fetch with setFetch, never strip or rewrite the Content-Type header.","Inspect e.url/e.status/e.contentType on the error before retrying blindly."],"tags":["http","content-type","fetch","network","sdk"],"backgroundTag":"non-json-response-received","analyzedSha":"5666d57f15a66bd5518119c5d9f4d2b62f3a86c1","analyzedAt":"2026-09-01T05:20:49.208Z","contentChangedAt":"2026-09-01T05:20:49.208Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}