actualbudget/actual · error

invalid-file-id

invalid-file-id

Error message

invalid fileId

What it means

The Pluggy.ai integration's POST /status endpoint validates the X-Actual-File-Id header with isValidFileId when present. If the header value is not a valid file id, it returns HTTP 400 with reason 'invalid-file-id' and details 'invalid fileId'. This is an input-validation guard before any authorization or database lookup.

Source

Thrown at packages/sync-server/src/app-pluggyai/app-pluggyai.js:31

import { pluggyaiService } from './pluggyai-service';

const app = express();
export { app as handlers };
app.use(requestLoggerMiddleware);
app.use(express.json());
app.use(validateSessionMiddleware);

function canAccessFile(fileId, userId) {
  return isAdmin(userId) || UserService.countUserAccess(fileId, userId) > 0;
}

app.post(
  '/status',
  handleError(async (req, res) => {
    const fileId = req.get('X-Actual-File-Id');
    if (!!fileId) {
      if (!isValidFileId(fileId)) {
        res.status(400).send({
          status: 'error',
          reason: 'invalid-file-id',
          details: 'invalid fileId',
        });
        return;
      }

      if (!canAccessFile(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;
      }
    }

    const source = pluggyaiService.getCredentialSource(fileId);

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Send the correct budget fileId — the exact id string from the Actual app/server — in X-Actual-File-Id
  2. Omit the X-Actual-File-Id header entirely if no budget context is needed (the check only runs when a value is present)
  3. Log the outgoing header and compare it against a fileId listed by the sync-server
  4. Regenerate/locate the id via the server API rather than manual input

Example fix

// before
headers: { 'X-Actual-File-Id': 'My Budget' }
// after
headers: { 'X-Actual-File-Id': '3f1c9a2e-...-valid-uuid' }
Defensive patterns

Strategy: validation

Validate before calling

const fileId = '3f1c9a2e-8b7d-4c2a-9e1f-0d6b5a4c3e2d'; // example
function isPlausibleFileId(id) {
  return typeof id === 'string' && id.trim().length > 0 && /^[0-9a-f-]{16,}$/i.test(id.trim());
}
if (!isPlausibleFileId(fileId)) throw new Error('Refusing request: malformed fileId');

Type guard

function isValidFileId(v) {
  return typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);
}

Try / catch

const res = await fetch('/pluggyai/status', { headers: { 'X-Actual-File-Id': fileId } });
if (res.status === 400) {
  const body = await res.json();
  if (body.reason === 'invalid-file-id') {
    throw new Error(`Bad fileId header: ${body.details}`);
  }
}

Prevention

When it happens

Trigger: POST /status with X-Actual-File-Id set to a malformed/non-UUID value (empty-ish string, arbitrary text, truncated id).

Common situations: Client integration hard-coding a wrong id; passing a budget name instead of its fileId; copy/paste losing characters; sending the header with an empty-but-present value.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/730a108581efe653. Report an issue: GitHub.