{"record":{"id":"9470fb7a13ad7062","repo":"actualbudget/actual","slug":"too-many-redirects","errorCode":null,"errorMessage":"Too many redirects","messagePattern":"Too many redirects","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sync-server/src/app-simplefin/app-simplefin.js","lineNumber":458,"sourceCode":"  // default cross-origin stripping) so the bridge credentials aren't leaked.\n  const MAX_REDIRECTS = 5;\n  let currentUrl = url.toString();\n  let response;\n  for (let hop = 0; ; hop++) {\n    await assertUrlAllowed(currentUrl, { allowPrivateNetwork: true });\n\n    response = await fetch(currentUrl, {\n      method: 'GET',\n      headers,\n      redirect: 'manual',\n    });\n\n    const location = response.headers.get('location');\n    if (response.status < 300 || response.status >= 400 || !location) {\n      break;\n    }\n    if (hop >= MAX_REDIRECTS) {\n      throw new Error('Too many redirects');\n    }\n\n    const nextUrl = new URL(location, currentUrl);\n    if (nextUrl.origin !== new URL(currentUrl).origin) {\n      delete headers.Authorization;\n    }\n    currentUrl = nextUrl.toString();\n  }\n\n  if (response.status === 403) {\n    throw new Error('Forbidden');\n  }\n\n  const text = await response.text();\n  try {\n    const results = JSON.parse(text);\n    results.sferrors = results.errors;\n    results.hasError = false;","sourceCodeStart":440,"sourceCodeEnd":476,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-simplefin/app-simplefin.js#L440-L476","documentation":"SimpleFIN's fetch helper follows redirects manually (up to MAX_REDIRECTS) so it can strip the Authorization header when a redirect crosses origins. If the hop counter reaches MAX_REDIRECTS before a non-3xx response arrives, it throws 'Too many redirects' to prevent an infinite redirect loop against the SimpleFIN API.","triggerScenarios":"The SimpleFIN base URL (from the access key) keeps responding with 3xx + Location headers beyond the redirect limit — e.g. a misconfigured custom base URL, a loop in the server's redirects, or an intermediate proxy bouncing between URLs.","commonSituations":"Self-hosted SimpleFIN bridge behind a reverse proxy with a redirect loop (http↔https or trailing-slash rewrites); wrong custom server URL configured; DNS/proxy issues causing repeated redirects.","solutions":["Fix the redirect loop at the source (reverse proxy config): ensure the SimpleFIN server responds 200 directly at the configured base URL.","Verify the base URL embedded in the access key points to the final, canonical https address and doesn't need rewriting.","Test the access-key URL with curl -IL and resolve any repeated 3xx chain manually.","Bypass intermediaries (proxy/CDN) or correct their rewrite rules to stop the bounce."],"exampleFix":"// before (nginx)\nreturn 301 https://$host$request_uri; // loops back to itself\n\n// after (nginx)\nproxy_pass http://sfin-backend;\n# no self-redirect; serve API directly at https","handlingStrategy":"try-catch","validationCode":"// pre-flight: check the base URL resolves without a long redirect chain\nconst res = await fetch(baseUrl, { redirect: 'manual' });\nif ([301,302,303,307,308].includes(res.status)) console.warn('base URL redirects; fix proxy config');","typeGuard":null,"tryCatchPattern":"try {\n  const accounts = await getAccounts(accessKey, startDate, endDate);\n} catch (e) {\n  if (e.message === 'Too many redirects') {\n    throw new Error('SimpleFIN server/proxy redirect loop — fix reverse proxy or base URL');\n  }\n  throw e;\n}","preventionTips":["Test the SimpleFIN base URL with curl -IL before configuring it.","Avoid reverse-proxy rewrite rules that bounce the API path.","Pin the canonical https URL in the access key."],"tags":["simplefin","network","redirects","http"],"backgroundTag":"too-many-redirects","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}