actualbudget/actual · error
handleEnableBankingError(response.status, responseBody)
Error message
handleEnableBankingError(response.status, responseBody)
What it means
The Enable Banking API wrapper request() throws handleEnableBankingError(response.status, responseBody) whenever the upstream HTTP response is not ok. The raw status plus parsed JSON/text body are converted into a normalized EnableBankingError, so callers get a consistent error type instead of raw fetch responses. Raised in request() and surfaced through validateCredentials, getApplication, getAspsps, startAuth, createSession, and getSession.
Source
Thrown at packages/sync-server/src/app-enablebanking/services/enablebanking-service.ts:180
throw new EnableBankingError(
'TIMED_OUT',
'TIMED_OUT',
'Request timed out',
);
}
throw error;
} finally {
clearTimeout(timer);
}
if (!response.ok) {
let responseBody: unknown;
try {
responseBody = await response.json();
} catch {
responseBody = await response.text().catch(() => 'unknown');
}
throw handleEnableBankingError(response.status, responseBody);
}
// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- generic API wrapper, type is validated by caller
return (await response.json()) as T;
}
// --- Normalization functions ---
// SEPA / ISO 20022 structured remittance prefixes (e.g. `EREF+invoice-42`).
// They are metadata for clearing systems, not user-facing text, so we strip
// them from the front of each remittance line. The list is an allowlist of
// known prefixes rather than a catch-all `[A-Z]{3,}\+` so we don't accidentally
// strip merchant tokens like `BMW+` or `USB+` that legitimately start a
// description.
const SEPA_PREFIX_RE =
/^(?:EREF|KREF|MREF|CRED|DBTR|CDTR|SVWZ|SVCL|PURP|RTRN|REJT|REFE|SDVA|INDA|NTAV|ULTC|ULTD|ULTB|ABWA|ABWE|IBAN|BIC|COAM|OAMT|REMI|SQTP|ROC)\+/;
function stripSepaPrefix(s: string): string {View on GitHub (pinned to d4334cb6e6)
Solutions
- Inspect the thrown EnableBankingError's type/status and detail from the response body
- For 401/403, check enablebanking_applicationId and enablebanking_secretKey secrets and that the JWT is freshly generated
- For 4xx on createSession/startAuth, validate the aspsp id and PSU payload sent to the API
- For 5xx, retry with backoff — likely a bank or Enable Banking outage
- If rate-limited, ensure PSU headers are forwarded so requests count as user-triggered
Example fix
// before
const aspsp = await getAspsps(unknownId); // throws raw 404 normalized error
// after
try {
const aspsp = await getAspsps(id);
} catch (e) {
if (e instanceof EnableBankingError && e.status === 404) {
return null; // unknown ASPSP id
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Verify Enable Banking secrets are configured before any API call
const applicationId = secretsService.get(SecretName.enablebanking_applicationId);
const secretKey = secretsService.get(SecretName.enablebanking_secretKey);
if (!applicationId || !secretKey) {
throw new Error('Enable Banking not configured'); // avoids a guaranteed 401 round-trip
} Type guard
function isEnableBankingHttpError(e: unknown): e is EnableBankingError & { status: number } {
return e instanceof EnableBankingError && typeof e.status === 'number' && e.status >= 400;
} Try / catch
try {
return await request<T>(method, path, body);
} catch (e) {
if (isEnableBankingHttpError(e)) {
if (e.status === 401 || e.status === 403) return refreshCredentialsAndRetry();
if (e.status >= 500) return retryWithBackoff(() => request<T>(method, path, body));
}
throw e;
} Prevention
- Match on EnableBankingError status/type codes instead of parsing raw response bodies
- Refresh the JWT per request (getAuthorizationHeader already does) — never cache long-lived bearer tokens
- Forward PSU headers for user-triggered fetches to avoid ASPSP background-fetch rate limits
- Add exponential backoff only for 5xx/timeouts; treat 4xx as non-retryable configuration or input errors
- Monitor Enable Banking status/bank outages before blaming your integration
When it happens
Trigger: Any Enable Banking REST call returning 4xx/5xx: invalid or expired JWT (401), unknown ASPSP id (404), malformed session request (400), bank-side rejection during startAuth/createSession, or upstream 5xx outage. Also non-JSON error bodies are tolerated and passed through.
Common situations: Expired Enable Banking application credentials; wrong applicationId/secretKey secrets configured; rate limits or daily fetch caps enforced by ASPSPs (when PSU headers are absent); bank API downtime returning 502/503.
Related errors
- TIMED_OUT
- Error importing budget: ${result.error}
- Error importing budget: no budget was loaded
- Error exporting budget: ${result.error}
- Error exporting budget: no data was returned
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/867bda0df9126c9e.
Report an issue: GitHub.