{"record":{"id":"847860ae3d232a9d","repo":"actualbudget/actual","slug":"file-denied","errorCode":"file-denied","errorMessage":"You don't have permissions over this file","messagePattern":"You don't have permissions over this file","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"packages/sync-server/src/app-admin.js","lineNumber":239,"sourceCode":"\n  res.json(accesses);\n});\n\napp.post('/access', (req, res) => {\n  const userAccess = req.body || {};\n  const session = validateSession(req, res);\n\n  if (!session) return;\n\n  const { granted } = UserService.checkFilePermission(\n    userAccess.fileId,\n    session.user_id,\n  ) || {\n    granted: 0,\n  };\n\n  if (granted === 0 && !isAdmin(session.user_id)) {\n    res.status(400).send({\n      status: 'error',\n      reason: 'file-denied',\n      details: \"You don't have permissions over this file\",\n    });\n    return;\n  }\n\n  const fileIdInDb = UserService.getFileById(userAccess.fileId);\n  if (!fileIdInDb) {\n    res.status(404).send({\n      status: 'error',\n      reason: 'invalid-file-id',\n      details: 'File not found at server',\n    });\n    return;\n  }\n\n  if (!userAccess.userId) {","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-admin.js#L221-L257","documentation":"Thrown by the POST /access endpoint in the Actual sync-server admin app when the authenticated session user has no granted permission over the target fileId and is not an admin. The server checks UserService.checkFilePermission(fileId, session.user_id); if granted is 0 (or undefined) and isAdmin fails, it rejects the access-grant request with reason 'file-denied'. It means the caller is trying to share a file they do not own or manage.","triggerScenarios":"POST /access with a body {fileId, userId} where the session user has no users_files row granting permission on fileId; passing a wrong/typo'd fileId the user never had access to; calling while logged in as a non-admin user for a file owned by someone else.","commonSituations":"A manager user tries to share a budget file owned by another account; a client caches a stale fileId after the file was re-uploaded under a different id; scripts run with a personal (non-admin) token attempting bulk sharing.","solutions":["Verify the fileId in the request body belongs to the authenticated user (check via UserService.checkFilePermission or the UI file list).","Log in as an admin account or grant the current user permission over the file first.","Fix client-side state so the correct fileId is sent; clear stale cached budgets."],"exampleFix":"// before\nawait fetch('/access', { method: 'POST', body: JSON.stringify({ fileId: staleId, userId }) });\n// after\nconst { granted } = await checkFilePermission(fileId, currentUserId);\nif (granted) await fetch('/access', { method: 'POST', body: JSON.stringify({ fileId, userId }) });","handlingStrategy":"validation","validationCode":"async function canShare(fileId, userId, session) {\n  const { granted } = await checkFilePermission(fileId, session.user_id) || { granted: 0 };\n  return granted > 0 || session.isAdmin;\n}\n// call before POST /access","typeGuard":"function hasPermission(p) {\n  return typeof p === 'object' && p !== null && typeof p.granted === 'number' && p.granted > 0;\n}","tryCatchPattern":"try {\n  await api.post('/access', { fileId, userId });\n} catch (e) {\n  if (e.response?.data?.reason === 'file-denied') {\n    throw new Error(`No permission over file ${fileId}; use an owner or admin account`);\n  }\n  throw e;\n}","preventionTips":["Always verify permission over the fileId with the current session before sharing.","Use admin credentials for administrative sharing scripts.","Keep client-side file ids in sync with the server file list."],"tags":["permissions","authorization","sync-server","http-400"],"backgroundTag":"file-permission-denied","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}