{"record":{"id":"373b690a73dc7cfc","repo":"actualbudget/actual","slug":"connection","errorCode":"Connection","errorMessage":"Connection","messagePattern":"Connection","errorType":"error_code","errorClass":"BankSyncError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/accounts/sync.ts","lineNumber":304,"sourceCode":"  logger.log('Pulling transactions from Pluggy.ai');\n\n  const res = await post(\n    getServer().PLUGGYAI_SERVER + '/transactions',\n    {\n      accountId: acctId,\n      startDate: since,\n    },\n    {\n      'X-ACTUAL-TOKEN': userToken,\n      ...(fileId ? { 'X-Actual-File-Id': fileId } : {}),\n    },\n    60000,\n  );\n\n  if (res.error_code) {\n    throw BankSyncError(res.error_type, res.error_code);\n  } else if ('error' in res) {\n    throw BankSyncError('Connection', res.error);\n  }\n\n  let retVal = {};\n  const singleRes = res as BankSyncResponse;\n  retVal = {\n    transactions: singleRes.transactions.all,\n    accountBalance: singleRes.balances,\n    startingBalance: singleRes.startingBalance,\n  };\n\n  logger.log('Response:', retVal);\n  return retVal;\n}\n\nasync function downloadAkahuTransactions(\n  acctId: AccountEntity['id'],\n  since: string,\n) {","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/accounts/sync.ts#L286-L322","documentation":"downloadPluggyAiTransactions throws BankSyncError('Connection', res.error) when the Pluggy.ai response has a generic 'error' field but no structured error_code. This indicates a connection-level or non-standard failure communicated by the sync server/proxy rather than a categorized provider error code.","triggerScenarios":"syncAccount on a Pluggy.ai account where the fetch to /pluggy/... returns JSON containing an 'error' key (e.g. sync-server proxy error, missing Pluggy credentials configured on the server, malformed upstream response).","commonSituations":"Self-hosted sync server without PLUGGY_CLIENT_ID/PLUGGY_CLIENT_SECRET set; network/proxy failure between the sync server and Pluggy; server returns an HTML/JSON error page parsed into res.error.","solutions":["Read res.error from the thrown BankSyncError to see the underlying message","Verify the sync server has Pluggy credentials configured (PLUGGY_CLIENT_ID, PLUGGY_CLIENT_SECRET env vars)","Check sync-server logs for the failing upstream request and confirm the server can reach api.pluggy.ai","Retry the sync after confirming server connectivity"],"exampleFix":"// before: sync server missing Pluggy env vars\n# .env\n# (PLUGGY_CLIENT_ID and PLUGGY_CLIENT_SECRET absent)\n// after\n# .env\nPLUGGY_CLIENT_ID=your-client-id\nPLUGGY_CLIENT_SECRET=your-client-secret\n# then restart the sync server and retry sync","handlingStrategy":"retry","validationCode":"if (!syncServerUrl.startsWith('https://')) {\n  throw new Error('Sync server URL must be reachable over HTTPS');\n}","typeGuard":null,"tryCatchPattern":"async function syncWithRetry(accountId, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await runQuery(syncAccount(accountId));\n    } catch (e) {\n      if (e.type === 'BankSyncError' && e.code === 'Connection' && i < attempts - 1) {\n        await new Promise(r => setTimeout(r, 2000 * (i + 1)));\n        continue;\n      }\n      throw e;\n    }\n  }\n}","preventionTips":["Ensure the sync server has outbound access to api.pluggy.ai","Set Pluggy env vars on self-hosted sync servers before enabling the bank","Monitor sync-server logs for upstream proxy errors","Use HTTPS endpoints without intermediate error pages that break JSON parsing"],"tags":["bank-sync","pluggy","connection","configuration"],"backgroundTag":"connection-refused","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}