{"record":{"id":"574b6fd90dc85386","repo":"actualbudget/actual","slug":"api-request-redirected","errorCode":null,"errorMessage":"API request redirected","messagePattern":"API request redirected","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/platform/server/fetch/index.ts","lineNumber":16,"sourceCode":"import * as connection from '#platform/server/connection';\n\nexport const fetch = async (\n  input: RequestInfo | URL,\n  options: RequestInit = {},\n): Promise<Response> => {\n  // Set redirect to manual so that we can detect and respond to redirects.\n  if (!options.redirect) options.redirect = 'manual';\n\n  const response = await globalThis.fetch(input, options);\n\n  // Authentication proxies redirect when authentication has expired. In this case,\n  // we want to fully reload and yeild control from the service worker back to the server.\n  if (response.type === 'opaqueredirect') {\n    connection.send('api-fetch-redirected');\n    throw new Error(`API request redirected`);\n  }\n\n  return response;\n};\n","sourceCodeStart":1,"sourceCodeEnd":21,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/platform/server/fetch/index.ts#L1-L21","documentation":"The server-side fetch wrapper detects `response.type === 'opaqueredirect'`, which happens when an authentication proxy redirected the request (typically because the session expired). It notifies the client via `api-fetch-redirected` and throws so the app can fully reload and hand control back to the server.","triggerScenarios":"`fetch()` (packages/loot-core/src/platform/server/fetch) with a `redirect: 'manual'` request where the server responds with a redirect — e.g. an auth proxy bouncing an expired session to a login page.","commonSituations":"Running Actual behind an auth proxy (oauth2-proxy, Authelia, etc.) whose session expired mid-use; the service worker forwarding a request that gets redirected instead of answered.","solutions":["Log in again / refresh the page to re-establish the proxy session","Check your auth proxy's session timeout and cookie configuration so long-running budgets aren't logged out","Disable or bypass the auth proxy for the app's API routes if it interferes with server-side requests"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// re-check auth/session before long fetch sequences\nconst res = await fetch('/auth-check', { redirect: 'manual' });\nif (res.type === 'opaqueredirect') location.reload(); // session expired","typeGuard":null,"tryCatchPattern":"try {\n  const res = await serverFetch(url, opts);\n} catch (e) {\n  if (e.message === 'API request redirected') {\n    // session expired behind auth proxy — trigger full page reload / re-login\n    window.location.href = '/login';\n  } else throw e;\n}","preventionTips":["Increase auth-proxy session lifetime for long budget sessions","Monitor for 'api-fetch-redirected' messages to prompt re-login proactively","Exclude the app's API routes from proxy redirects where possible"],"tags":["fetch","auth","redirect","network"],"backgroundTag":"unexpected-redirect","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}