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
- 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.
- 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.
- Fix backendUrl (config_resolve_api_url / BACKEND_API_URL) to the real API origin.
- 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
- Validate backendUrl shape before the first request.
- Non-JSON error bodies usually mean a proxy/gateway — inspect with curl -i when it appears.
- Distinguish the ApiError object path from this generic string path when handling.
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)