MHSanaei/3x-ui · error · HttpError
Request failed with status ${status}
Error message
Request failed with status ${status} What it means
HttpError is thrown by the shared httpRequest wrapper in frontend/src/api/http-init.ts after any response where res.ok is false (status outside 200-299) that was not already handled as 401 (session-expiry redirect) or retried as 403 (CSRF refresh). The message template 'Request failed with status ${status}' is the generic HttpError message; the thrown value carries status, statusText, and the parsed body so callers can branch on it. It is the single choke point for all panel REST failures, so server-side error messages arrive as err.data (often {msg: ...}).
Source
Thrown at frontend/src/api/http-init.ts:193
if (res.status === 403 && !SAFE_METHODS.has(method.toUpperCase())) {
csrfToken = null;
const fresh = await fetchCsrfToken();
if (fresh) {
csrfToken = fresh;
res = await performFetch(method, url, data, options, fresh);
}
}
if (res.status === 401) {
if (!sessionExpired) {
sessionExpired = true;
window.location.replace(window.X_UI_BASE_PATH || basePathPrefix || '/');
}
return new Promise<HttpResponse>(() => {});
}
const parsed = await parseBody(res);
if (!res.ok) throw new HttpError(res.status, res.statusText, parsed);
return { ok: true, status: res.status, statusText: res.statusText, data: parsed };
}
export function setupHttp(): void {
let basePath: string | null | undefined = window.X_UI_BASE_PATH;
if (!basePath) {
const metaTag = document.querySelector('meta[name="base-path"]');
basePath = metaTag ? metaTag.getAttribute('content') : null;
}
basePathPrefix =
typeof basePath === 'string' && basePath !== '' && basePath !== '/'
? basePath.replace(/\/$/, '')
: '';
csrfToken = readMetaToken();
}
View on GitHub (pinned to ad32144c42)
Solutions
- Inspect the thrown HttpError: read err.status and err.data.msg — the Go side almost always puts the human-readable reason in data.msg
- If status is 403 repeatedly, confirm the CSRF meta tag / X-CSRF-Token flow is intact (setupHttp ran before requests)
- If 401s leak through on safe methods, verify window.X_UI_BASE_PATH matches the serving base path so the redirect lands on the login page
- For 5xx, check the Go panel logs for the matching request error
Example fix
// before
const res = await httpRequest('POST', '/panel/api/inbounds/add', payload);
// after
try {
const res = await httpRequest('POST', '/panel/api/inbounds/add', payload);
} catch (e) {
if (e instanceof HttpError) {
message.error(String((e.data as { msg?: string })?.msg ?? e.statusText));
return;
}
throw e;
} Defensive patterns
Strategy: try-catch
Type guard
export function isHttpError(e: unknown): e is HttpError {
return e instanceof HttpError && typeof e.status === 'number';
} Try / catch
try {
await httpRequest('POST', url, payload);
} catch (e) {
if (isHttpError(e)) {
if (e.status === 401) return; // redirect already handled
message.error(String((e.data as { msg?: string })?.msg ?? e.statusText));
return;
}
throw e; // network failure etc.
} Prevention
- Always read err.data.msg — the backend puts the actionable reason there
- Run setupHttp() before any request so CSRF and base path are initialized
- Wrap mutations in one shared error toast helper instead of per-call alerts
When it happens
Trigger: Any POST/GET to /panel/api/* returning 4xx/5xx: submitting an inbound with invalid data (400 with JSON body), hitting a route behind session auth after cookie loss but before the 401 handler fires on a safe method, 500s from panicking handlers, or gateway timeouts. Safe-method CSRF 403s are retried once; a second 403 falls through to this throw.
Common situations: Form validation errors surfaced from Gin ShouldBind; expired sessions on non-redirected requests; backend route changed but frontend stale (dev server not restarted after rebuild); reverse proxy returning 502 while the panel restarts.
Related errors
- HTTP %d from remote panel
- %s %s: %w
- %s %s: HTTP %d: %q
- %s %s: HTTP %d
- %s %s: %w (content-length %d, cap %d)
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/49f342ee1b270338.
Report an issue: GitHub.