actualbudget/actual · error · HTTPError
${text}
Error message
${text} What it means
checkHTTPStatus inspects a fetch response against the cloud storage server; when the status is not OK it throws an HTTPError carrying the status code and, when the body is not JSON, the raw response text as the message. fetchJSON always routes responses through it before parsing JSON.
Source
Thrown at packages/loot-core/src/server/cloud-storage.ts:67
async function checkHTTPStatus(res) {
if (res.status === 200) {
return res;
}
const text = await res.text();
if (res.status === 401 || res.status === 403) {
try {
const body = JSON.parse(text);
const error = res.status === 403 ? body.data : body;
if (getServerErrorReason(error) === 'token-expired') {
await asyncStorage.removeItem('user-token');
}
} catch {
// Preserve the original HTTP error when the response is not JSON.
}
}
throw new HTTPError(res.status, text);
}
async function fetchJSON(...args: Parameters<typeof fetch>) {
let res = await fetch(...args);
res = await checkHTTPStatus(res);
return res.json();
}
export async function checkKey(): Promise<{
valid: boolean;
error?: { reason: string };
}> {
const userToken = await asyncStorage.getItem('user-token');
const { cloudFileId, encryptKeyId } = prefs.getPrefs();
let res;
try {View on GitHub (pinned to d4334cb6e6)
Solutions
- Read the HTTPError status and text in the message to identify the cause (401/403 auth, 404 wrong id/URL, 5xx server fault).
- Verify the server URL in Settings points at the correct sync-server root and that the server is running (curl the /status endpoint).
- Re-authenticate: sign out/in to refresh the token if the status is 401/403.
- If the body is an HTML page, a proxy/load balancer is intercepting — check reverse-proxy config and upstream health.
- Retry later or inspect sync-server logs for 5xx causes.
Example fix
// before
const res = await fetchJSON(url); // 404 HTML page -> throws with raw text
// after
try {
const res = await fetchJSON(url);
} catch (e) {
if (e instanceof HTTPError && e.status === 404) {
throw new Error(`Budget not found at ${url}; check server URL and budget id`);
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Server returned ${res.status} before sync: ${await res.text()}`);
}
await res.body?.cancel(); Type guard
function isHTTPError(e: unknown): e is { status: number; message: string } {
return e instanceof Error && 'status' in e && typeof (e as any).status === 'number';
} Try / catch
try {
const data = await fetchJSON(url);
} catch (e) {
if (isHTTPError(e)) {
if (e.status === 401 || e.status === 403) await reauthenticate();
else if (e.status === 404) console.error('Check server URL / budget id');
else console.error(`Server error ${e.status}; retry later`, e.message);
} else throw e;
} Prevention
- Confirm the sync server is reachable (curl <server>/status) before operations.
- Refresh auth tokens when 401s appear; sessions expire.
- Check reverse-proxy config if responses are HTML instead of JSON.
- Log the status code and body text for diagnosis before retrying.
- Verify the server URL matches the running sync-server exactly.
When it happens
Trigger: Any cloud-storage call made through fetchJSON (upload/download files, get/create budget, etc.) where the server returns a non-2xx status: 401 for an expired token, 404 for an unknown budget id, 409/500 from the sync server, or a proxy returning an HTML/text error page.
Common situations: Self-hosted sync server behind an auth-reverse-proxy returning 401 text; wrong server URL hitting a random site's 404; server restarted and returning 502 from a load balancer; expired login token.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Failed to fetch catalog: ${response.statusText}
- Failed to fetch CSS from ${url}: ${response.status} ${respon
- API request redirected
- getServerErrorReason(json)
- text (raw server response body)
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/707c680b6371ccf0.
Report an issue: GitHub.