{"record":{"id":"ba30dc87d4a944ba","repo":"actualbudget/actual","slug":"invalid-access-token","errorCode":"INVALID_ACCESS_TOKEN","errorMessage":"INVALID_ACCESS_TOKEN","messagePattern":"INVALID_ACCESS_TOKEN","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/desktop-client/src/accounts/syncStatus.ts","lineNumber":21,"sourceCode":"export function isAccountFailedSync(\n  account: Pick<AccountEntity, 'bank_sync_status'>,\n) {\n  const status = account.bank_sync_status;\n  return (\n    status != null &&\n    status !== 'ok' &&\n    status !== 'pending' &&\n    status !== 'sync-requested'\n  );\n}\n\nexport function getFailedSyncError(\n  account: Pick<AccountEntity, 'bank_sync_status' | 'account_sync_source'>,\n): { type: string; code: string } {\n  switch (account.bank_sync_status) {\n    case 'reauth-required':\n      if (account.account_sync_source === 'simpleFin') {\n        return { type: 'INVALID_ACCESS_TOKEN', code: 'INVALID_ACCESS_TOKEN' };\n      }\n      return { type: 'ITEM_ERROR', code: 'ITEM_LOGIN_REQUIRED' };\n    case 'attention-required':\n      return {\n        type: 'ACCOUNT_NEEDS_ATTENTION',\n        code: 'ACCOUNT_NEEDS_ATTENTION',\n      };\n    case 'rate-limit-exceeded':\n      return { type: 'RATE_LIMIT_EXCEEDED', code: 'RATE_LIMIT_EXCEEDED' };\n    case 'timed-out':\n      return { type: 'TIMED_OUT', code: 'TIMED_OUT' };\n    case 'account-missing':\n      return { type: 'ACCOUNT_MISSING', code: 'ACCOUNT_MISSING' };\n    default:\n      return { type: 'SYNC_ERROR', code: 'SYNC_ERROR' };\n  }\n}\n","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-client/src/accounts/syncStatus.ts#L3-L39","documentation":"This is not a thrown exception but an error descriptor returned by getFailedSyncError when a bank-synced account's status is 'reauth-required' and its sync source is simpleFin. It means the SimpleFin access token/credentials are no longer valid and the bank link must be re-authenticated before syncing can resume. The UI uses the descriptor to render an error banner with a re-link action.","triggerScenarios":"Calling getFailedSyncError on an account whose bank_sync_status === 'reauth-required' and account_sync_source === 'simpleFin' (e.g. rendering account sync status after a failed SimpleFin refresh).","commonSituations":"SimpleFin revoking or expiring an access token; user revoking access on the bank side; SimpleFin returning 401 during account refresh; tokens invalidated after bank credential changes.","solutions":["Re-authenticate the SimpleFin link via the account's Re-link flow to obtain a fresh SimpleFin access token","Verify the SimpleFin setup URL/token is still valid on the SimpleFin dashboard","After re-linking, run a sync to confirm bank_sync_status clears to 'ok'","If re-auth keeps failing, delete and re-add the account link with a new SimpleFin setup URL"],"exampleFix":"// before\nconst err = getFailedSyncError(account); // INVALID_ACCESS_TOKEN surfaces\n// after\nawait relinkAccount(account.id); // re-run SimpleFin auth, then sync","handlingStrategy":"validation","validationCode":"function needsSimpleFinReauth(account) {\n  return account.bank_sync_status === 'reauth-required' &&\n    account.account_sync_source === 'simpleFin';\n}","typeGuard":"function isSyncErrorDescriptor(v) {\n  return !!v && typeof v === 'object' && typeof v.type === 'string' && typeof v.code === 'string';\n}","tryCatchPattern":null,"preventionTips":["Periodically validate SimpleFin access tokens before syncing instead of waiting for reauth-required","Monitor bank_sync_status and prompt users to re-link as soon as the status flips","Keep the SimpleFin setup URL/token fresh; rotate on bank credential changes"],"tags":["bank-sync","simplefin","auth"],"backgroundTag":"invalid-access-token","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}