actualbudget/actual · error · BankSyncError

res.error_code

res.error_code

Error message

BankSyncError(res.error_type, res.error_code, errorDetails)

What it means

downloadGoCardlessTransactions posts to the GoCardless Bank Account Details proxy and, when the response carries an error_code, throws a BankSyncError wrapping the upstream error_type and error_code (plus rateLimitHeaders in details). This surfaces upstream GoCardless/Nordigen failures — expired requisitions, permission errors, rate limits — as structured bank-sync errors. The error_code is the raw upstream code, so its meaning depends on the provider response.

Source

Thrown at packages/loot-core/src/server/accounts/sync.ts:163

    {
      userId,
      key: userKey,
      requisitionId: bankId,
      accountId: acctId,
      startDate: since,
      includeBalance,
    },
    {
      'X-ACTUAL-TOKEN': userToken,
    },
  );

  if (res.error_code) {
    const errorDetails = {
      rateLimitHeaders: res.rateLimitHeaders,
    };

    throw BankSyncError(res.error_type, res.error_code, errorDetails);
  }

  if (includeBalance) {
    const {
      transactions: { all },
      balances,
      startingBalance,
    } = res;

    logger.log('Response:', res);

    return {
      transactions: all,
      accountBalance: balances,
      startingBalance,
    };
  } else {
    logger.log('Response:', res);

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Inspect error_code/error_type on the thrown BankSyncError; if it is rate-limit related, wait and retry honoring rateLimitHeaders.
  2. If the requisition expired, re-run the GoCardless link/requisition flow to get fresh consent, then resync.
  3. Verify bankId (requisitionId) and acctId are current by listing accounts for the requisition via the sync server.
  4. Check sync-server logs and GoCardless status for upstream outages before retrying.
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the requisition is still valid before syncing:
const accounts = await bankSync.listGoCardlessAccounts(); // or via sync server API
if (!accounts.some(a => a.accountId === acctId)) {
  throw new Error('GoCardless account/requisition no longer valid — re-link required');
}

Type guard

function isBankSyncError(e: unknown): e is { errorType: string; errorCode: string; details?: { rateLimitHeaders?: unknown } } {
  return typeof e === 'object' && e !== null && 'errorType' in e && 'errorCode' in e;
}

Try / catch

try {
  await syncAccount(accountId);
} catch (e) {
  if (isBankSyncError(e)) {
    if (e.errorCode === 'RE_RATE_LIMIT') {
      const retryAfter = e.details?.rateLimitHeaders; // wait per headers then retry
    } else {
      // expired requisition / permissions: prompt user to re-link the bank
    }
  } else throw e;
}

Prevention

When it happens

Trigger: Any GoCardless /transactions response containing error_code, e.g. an expired or cancelled requisition, an account whose access has lapsed, or HTTP 429 rate limiting (RE_RATE_LIMIT) captured via rateLimitHeaders.

Common situations: End-user's bank consent expired and must be re-authenticated; too many sync requests hitting provider rate limits; bankId/requisitionId referencing a deleted requisition; GoCardless account credentials revoked.

Related errors


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