actualbudget/actual · error · EnableBankingError

TIMED_OUT

TIMED_OUT

Error message

Request timed out

What it means

The Enable Banking HTTP wrapper aborts fetch requests after a timeout; when fetch rejects with an AbortError it is converted to EnableBankingError('TIMED_OUT','TIMED_OUT','Request timed out') so callers get a typed, retryable error instead of a raw abort.

Source

Thrown at packages/sync-server/src/app-enablebanking/services/enablebanking-service.ts:162

        headers[key] = value;
      }
    }
  }

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);

  const options: RequestInit = { method, headers, signal: controller.signal };
  if (body !== undefined) {
    options.body = JSON.stringify(body);
  }

  let response: Response;
  try {
    response = await fetch(url, options);
  } catch (error) {
    if (error instanceof Error && error.name === 'AbortError') {
      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);

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Retry the request with backoff — TIMED_OUT is transient by design
  2. Check outbound network access from the sync-server to api.enablebanking.com (proxy/firewall/DNS)
  3. Increase the request timeout if the deployment routinely needs longer
  4. Check Enable Banking status or their support for ongoing incidents

Example fix

// before: assume immediate success
const app = await enableBankingService.getApplication();
// after
try {
  const app = await enableBankingService.getApplication();
} catch (e) {
  if (e instanceof EnableBankingError && e.code === 'TIMED_OUT') {
    await delay(2000); return enableBankingService.getApplication(); // retry
  }
  throw e;
}
Defensive patterns

Strategy: retry

Try / catch

const maxRetries = 3;
for (let i = 0; i < maxRetries; i++) {
  try {
    return await enableBankingService.getSession(sessionId);
  } catch (e) {
    if (e instanceof EnableBankingError && e.code === 'TIMED_OUT' && i < maxRetries - 1) {
      await new Promise(r => setTimeout(r, 1000 * 2 ** i)); // exponential backoff
      continue;
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: Any Enable Banking API call (validateCredentials, getApplication, getAspsps, startAuth, createSession, getSession) where the upstream Enable Banking endpoint does not respond within the configured AbortController timeout, or the connection hangs.

Common situations: Enable Banking API outage or degradation; network firewall/proxy blocking the sync-server's egress; very slow bank backend behind Enable Banking; timeout configured too aggressively for the deployment's network.

Understand the failure class

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/15570ae7e51520a9. Report an issue: GitHub.