actualbudget/actual · error
fileAccessError (requireFileAccess denial)
Error message
fileAccessError (requireFileAccess denial)
What it means
After the file is resolved, requireFileAccess checks that the authenticated user has permission over the budget file. On denial the server sends a 403 with the returned fileAccessError object describing why access was refused.
Source
Thrown at packages/sync-server/src/app-sync.ts:179
return;
}
const filesService = new FilesService(getAccountDb());
const currentFile = verifyFileExists(
fileId,
filesService,
res,
'file-not-found',
);
if (!currentFile) {
return;
}
const fileAccessError = requireFileAccess(currentFile, res.locals.user_id);
if (fileAccessError) {
res.status(403);
res.send(fileAccessError);
return;
}
const errorMessage = validateSyncedFile(groupId, keyId, currentFile);
if (errorMessage) {
res.status(400);
res.send(errorMessage);
return;
}
const { trie, newMessages } = simpleSync.sync(messages, since, groupId);
const responsePb = create(SyncResponseSchema, {
merkle: JSON.stringify(trie),
messages: newMessages,
});
View on GitHub (pinned to d4334cb6e6)
Solutions
- Ask the file owner to re-share the budget with your user on the sync server.
- Sync with the account that owns the file.
- Verify user_id/token pairing on self-hosted setups (re-create the user, re-login, and re-download the budget).
Example fix
// before: token for user A, fileId owned by user B -> 403 fileAccessError
// after: login as the owning user (or be granted access), then sync
await actual.login({ password }); // as the account that owns the file
await actual.downloadBudget(fileId); Defensive patterns
Strategy: try-catch
Validate before calling
const files = await (await fetch(serverUrl + '/files', { headers: authHeaders })).json();
if (!files.data.some(f => f.fileId === fileId)) throw new Error('user cannot access file ' + fileId + ' — sync would be denied'); Try / catch
try {
const res = await sync(fileId);
if (res.status === 403) {
const err = await res.json();
throw new Error('file access denied: ' + JSON.stringify(err));
}
return res;
} catch (e) { throw e; } Prevention
- Sync only with accounts that own or are shared the budget
- Re-login after user accounts are recreated on self-hosted servers
- Confirm sharing grants survive owner-side budget deletions/re-creations
When it happens
Trigger: A /sync (or currentFile/file) call where the fileId resolves via filesService.get but res.locals.user_id is not the owner and has no share/access grant for that file.
Common situations: Syncing a budget after being unshared by the owner; using a server account that is not the budget owner; a user id mismatch after re-creating accounts on a self-hosted server; tokens issued for a different user on multi-user instances.
Related errors
- forbidden
- file-access-denied
- Sync ID is required for sync ${flag}. Set --sync-id or ACTUA
- Authentication required. Set --password/--session-token, ACT
- Could not resolve on-disk budget id for syncId ${syncId} aft
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/106029d02e6f8c4b.
Report an issue: GitHub.