immich-app/immich · error · MalformedResponseError

Expected a JSON response

Error message

Expected a JSON response

What it means

The SDK's jsonOnly fetch wrapper expects every non-204 response to a JSON-requested endpoint to have a JSON content-type. If the server returns non-JSON (HTML login page, empty error body, wrong route), MalformedResponseError('Expected a JSON response') is thrown carrying the URL, status, and content-type so the caller can diagnose what the server actually returned.

Solutions

  1. Check the URL/status/content-type embedded in the MalformedResponseError to see what the server actually replied with.
  2. Verify the base URL points at your Immich instance's API (no trailing path mistakes) and there is no auth portal intercepting requests.
  3. Inspect the response outside the SDK (curl -i) to see the raw body.
  4. Upgrade SDK and server to matching versions if an endpoint's content type changed.

Example fix

// before
new ImmichApi({ baseUrl: 'https://my-proxy.example.com' }) // proxy serves HTML
// after
new ImmichApi({ baseUrl: 'https://immich.example.com/api' }) // direct JSON API
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch(`${baseUrl}/server-info/ping`);
const ct = res.headers.get('content-type') ?? '';
if (!ct.includes('json')) throw new Error(`baseUrl returns non-JSON (${ct}) - check URL/proxy`);

Try / catch

try {
  await api.someCall();
} catch (e) {
  if (e instanceof MalformedResponseError) {
    console.error(`non-JSON from ${e.url} (status ${e.status}, ct ${e.contentType}); check baseUrl/proxy`);
  } else throw e;
}

Prevention

When it happens

Trigger: Requesting an Immich endpoint whose response lacks 'json' in content-type: hitting an HTML 404/402 page, a proxy error page, or a route returning text/plain; also wrong base URL pointing at a non-Immich server.

Common situations: Base URL typo or reverse proxy intercepting the request (auth portal, Cloudflare challenge), server returning an HTML error for a missing route, or an old SDK talking to an endpoint that changed its response type.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/21eb78e5c2e8b7af. Report an issue: GitHub.

Appendix: source

Thrown at packages/sdk/src/index.ts:66

    throw new Error('The API key header can only be set using setApiKey().');
  }
};

export const jsonOnly =
  (impl?: typeof fetch): typeof fetch =>
  async (input, options) => {
    const response = await (impl ?? fetch)(input, options);

    const expectsJson = new Headers(options?.headers)
      .get('accept')
      ?.includes('json');
    if (!expectsJson || response.status === 204) {
      return response;
    }

    const contentType = response.headers.get('content-type');
    if (!contentType?.includes('json')) {
      throw new MalformedResponseError(
        'Expected a JSON response',
        response.url,
        response.status,
        contentType,
      );
    }

    return response;
  };

export const setFetch = (impl: typeof fetch) => {
  defaults.fetch = jsonOnly(impl);
};

defaults.fetch = jsonOnly();

export const getAssetOriginalPath = (id: string) => `/assets/${id}/original`;

View on GitHub (pinned to e55ac299a4)