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

  1. Send the actual budget file id (cloud file id) in X-Actual-File-Id, obtained from the app or the files listing.
  2. Omit the X-Actual-File-Id header entirely if you intend to set a global secret (requires admin).
  3. Validate the id format client-side before calling (UUID-like) to fail fast.
  4. 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

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.