actualbudget/actual · error
SimpleFIN claim failed with HTTP ${response.status}
Error message
SimpleFIN claim failed with HTTP ${response.status} What it means
claimAccessKey POSTs the temporary setup token to SimpleFIN's claim URL and returns the resulting access key text. A 403 is treated as an expected 'not claimable/already claimed' response, but any other non-OK HTTP status causes it to throw 'SimpleFIN claim failed with HTTP <status>'. This signals an upstream problem with the token exchange.
Source
Thrown at packages/sync-server/src/app-simplefin/app-simplefin.js:365
}
return decoded;
}
async function claimAccessKey(claimUrl) {
// Self-hosters may run their own SimpleFIN bridge on the local network, so
// private addresses are allowed here; cloud metadata and other always-blocked
// ranges are still rejected.
await assertUrlAllowed(claimUrl, { allowPrivateNetwork: true });
// don't auto-follow redirects for SSRF safety
const response = await fetch(claimUrl, {
method: 'POST',
redirect: 'manual',
});
if (!response.ok && response.status !== 403) {
throw new Error(`SimpleFIN claim failed with HTTP ${response.status}`);
}
return (await response.text()).trim();
}
function isForbidden(value) {
return typeof value === 'string' && value.startsWith('Forbidden');
}
function isInvalidAccessKey(accessKey) {
return (
typeof accessKey !== 'string' ||
isForbidden(accessKey) ||
!ACCESS_KEY_FORMAT.test(accessKey)
);
}
async function getTransactions(accessKey, accounts, startDate, endDate) {View on GitHub (pinned to d4334cb6e6)
Solutions
- Generate a new setup token at bridge.simplefin.org and retry the claim — tokens expire quickly.
- Check the HTTP status in the message: 429 means back off and retry later; 5xx means wait for SimpleFIN to recover.
- Retry after a short delay for transient 5xx errors (the message includes the exact status to branch on).
- Verify network/proxy settings if a non-SimpleFIN status appears (e.g. 502 from a corporate proxy).
Example fix
// before
const key = await claimAccessKey(token); // throws on 500
// after
let key;
try {
key = await claimAccessKey(token);
} catch (e) {
if (e.message.includes('HTTP 5')) key = await claimAccessKey(token); // retry transient
else throw e;
} Defensive patterns
Strategy: retry
Try / catch
try {
key = await claimAccessKey(token);
} catch (e) {
const m = /HTTP (\d{3})/.exec(e.message);
if (m && +m[1] >= 500) {
await sleep(2000);
key = await claimAccessKey(token); // retry transient 5xx
} else {
throw e; // 4xx: token problem, get a fresh token
}
} Prevention
- Claim the token immediately after generating it — setup tokens expire fast.
- Parse the HTTP status from the message and branch: retry 5xx, regenerate token on 4xx.
- Monitor SimpleFIN status before mass setup attempts.
When it happens
Trigger: POSTing to the SimpleFIN claim endpoint and receiving e.g. 400, 404, 429, or 5xx; expired setup token returning an unexpected status; SimpleFIN outage; network intermediary returning an error status.
Common situations: Setup token older than ~30 minutes (expired) or already claimed with unexpected status handling; rate limiting after repeated setup attempts; transient SimpleFIN server errors during bank setup; a proxy stripping the redirect-manual POST.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- results.reason || results.error_code
- results.reason || results.error
- Too many redirects
- Forbidden
- TIMED_OUT
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/7daa4c2b635c1b70.
Report an issue: GitHub.