tinyhumansai/openhuman · error

HTTP error! status

Error message

HTTP error! status: ${response.status}

What it means

Thrown by apiClient's request helper when the backend responds with a non-2xx status AND the response's content-type is not application/json — so the structured ApiError path (which parses data.error/data.message) is unreachable. The generic message embeds only the numeric status. This shape typically means an HTML/plain-text error page from a proxy, gateway, or wrong server rather than a well-behaved API error.

Solutions

  1. Check the embedded status: 502/503 → backend behind a proxy is down or unreachable; 404 → wrong base URL/path; 401/403 HTML → proxy/auth wall, not the API.
  2. Open the same URL in a browser or curl -i to see the HTML/plain error — it usually names the gateway or proxy at fault.
  3. Fix backendUrl (config_resolve_api_url / BACKEND_API_URL) to the real API origin.
  4. If a proxy must sit in front, ensure it forwards the API routes and preserves JSON error content-type.

Example fix

// before
if (!contentType || !contentType.includes('application/json')) {
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return {} as T;
}

// after — include a body snippet to identify proxy/HTML error pages
if (!contentType || !contentType.includes('application/json')) {
  if (!response.ok) {
    const text = (await response.text().catch(() => '')).slice(0, 120);
    throw new Error(`HTTP ${response.status} (non-JSON${text ? `: ${text}` : ''}) — is backendUrl correct?`);
  }
  return {} as T;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const parsed = new URL(backendUrl);
if (!/^https?:$/.test(parsed.protocol) || !parsed.hostname.includes('.')) {
  throw new Error('backendUrl is not a valid API origin');
}

Type guard

function isApiError(e: unknown): e is { success: false; error: string; message?: string } {
  return Boolean(e) && typeof e === 'object' && (e as { success?: unknown }).success === false;
}

Try / catch

try {
  return await apiClient.request<T>(method, path, body);
} catch (e) {
  if (e instanceof Error && /^HTTP error! status: \d+$/.test(e.message)) {
    const status = Number(e.message.split(': ')[1]);
    if (status >= 500 || status === 502) notify('Service unavailable — try again later.');
    else if (status === 404) notify('API route not found — check backendUrl.');
    else throw e;
  } else if (isApiError(e)) {
    notify(e.error);
  } else throw e;
}

Prevention

When it happens

Trigger: backendUrl points at a domain served by nginx/a CDN returning an HTML 502/503/404 page; a reverse proxy blocking the route; requesting an API path from a host that serves a SPA (200 HTML would be returned as {} but error statuses hit this branch); auth portals (SSO/captive portal) answering HTML.

Common situations: Misconfigured BACKEND_API_URL (missing /api prefix, wrong protocol, trailing host without port); backend down behind a load balancer returning 502 HTML; corporate proxy/captive portal interception; WebSocket-only routes accidentally hit via this REST client.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/7d40963b244955c9. Report an issue: GitHub.

Appendix: source

Thrown at app/src/services/apiClient.ts:99

    }

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), timeout);

    const config: RequestInit = { method, headers, signal: controller.signal };

    if (body && method !== 'GET') {
      config.body = JSON.stringify(body);
    }

    try {
      const response = await fetch(url, config);

      // Handle non-JSON responses
      const contentType = response.headers.get('content-type');
      if (!contentType || !contentType.includes('application/json')) {
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        return {} as T;
      }

      const data = await response.json();

      // Handle error responses
      if (!response.ok) {
        const error: ApiError = data.error
          ? { success: false, error: data.error, message: data.message }
          : { success: false, error: `HTTP ${response.status}: ${response.statusText}` };
        throw error;
      }

      return data as T;
    } catch (error) {
      // Re-throw API errors as-is
      if (error && typeof error === 'object' && 'error' in error) {

View on GitHub (pinned to 7491200858)