actualbudget/actual · error
invalid-file-id
invalid-file-id
Error message
invalid fileId
What it means
When a POST /secrets request includes an X-Actual-File-Id header it is treated as a per-budget secret, and the server validates the header with isValidFileId(fileId) before touching the store. A header that is present but not a well-formed budget file id is rejected with HTTP 400 and reason 'invalid-file-id'.
Source
Thrown at packages/sync-server/src/app-secrets.js:52
}
app.post('/', async (req, res) => {
const { name, value } = req.body || {};
const fileId = req.get('X-Actual-File-Id');
const perBudgetFile = fileId != null;
if (!(name in SecretName)) {
res.status(400).send({
status: 'error',
reason: 'invalid-secret-name',
details: 'Unknown secret name',
});
return;
}
if (perBudgetFile) {
if (!isValidFileId(fileId)) {
res.status(400).send({
status: 'error',
reason: 'invalid-file-id',
details: 'invalid fileId',
});
return;
}
if (!canManagePerBudgetFileSecrets(fileId, res.locals.user_id)) {
res.status(403).send({
status: 'error',
reason: 'file-access-denied',
details: "You don't have permissions over this file",
});
return;
}
} else if (!canManageGlobalSecrets(res.locals.user_id)) {
res.status(403).send({
status: 'error',View on GitHub (pinned to d4334cb6e6)
Solutions
- Send the actual budget file id (cloud file id) in X-Actual-File-Id, obtained from the app or the files listing.
- Omit the X-Actual-File-Id header entirely if you intend to set a global secret (requires admin).
- Validate the id format client-side before calling (UUID-like) to fail fast.
- Re-fetch the file id if the budget was recreated or synced from another instance.
Example fix
// before
headers: { 'X-Actual-File-Id': 'my-budget' }
// after
headers: { 'X-Actual-File-Id': '3f1c2a4e-....' } // real budget file id, or omit header for global secret Defensive patterns
Strategy: validation
Validate before calling
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function assertValidFileId(fileId) {
if (!UUID_RE.test(fileId)) throw new Error(`Bad file id: ${fileId}`);
} Type guard
function isFileId(v) {
return typeof v === 'string' && v.length > 0 && /^[0-9a-f-]{36}$/i.test(v);
} Try / catch
if (!isFileId(fileId)) {
// fail fast client-side; do not call the API
} else {
const res = await fetch('/secrets', { method: 'POST', headers: { 'X-Actual-File-Id': fileId }, ... });
if (res.status === 400 && (await res.json()).reason === 'invalid-file-id') { /* re-fetch budget id */ }
} Prevention
- Copy file ids from the app/API, never hand-type them
- Do not commit placeholder header values into scripts
- Re-resolve file ids after budget recreation or server migration
- Validate UUID format before every request
When it happens
Trigger: POST /secrets with an X-Actual-File-Id header that is an empty string, random UUID, or a value not matching the expected budget file id format; sending placeholder header values from templates; a budget id from a different server instance.
Common situations: Copy-pasting a budget name or account id instead of the file id; hardcoded dummy header values ('<fileId>') left in scripts; old ids after re-creating a budget.
Related errors
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/8a1bfc9611e99cef.
Report an issue: GitHub.