{"record":{"id":"306a1d9648bc9971","repo":"actualbudget/actual","slug":"single-file-id-is-required","errorCode":null,"errorMessage":"Single file ID is required","messagePattern":"Single file ID is required","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"packages/sync-server/src/app-sync.ts","lineNumber":418,"sourceCode":"  // Regardless, update some properties\n  filesService.update(\n    fileId,\n    new FileUpdate({\n      syncVersion: syncFormatVersion,\n      encryptMeta,\n      name,\n    }),\n  );\n\n  res.send({ status: 'ok', groupId });\n});\n\napp.get('/download-user-file', async (req, res) => {\n  const fileId = req.headers['x-actual-file-id'];\n  if (typeof fileId !== 'string') {\n    // FIXME: Not sure how this cannot be a string when the header is\n    // set.\n    res.status(400).send('Single file ID is required');\n    return;\n  }\n  if (!isValidFileId(fileId)) {\n    res.status(400).send('invalid fileId');\n    return;\n  }\n\n  const filesService = new FilesService(getAccountDb());\n  const file = verifyFileExists(\n    fileId,\n    filesService,\n    res,\n    'User or file not found',\n  );\n\n  if (!file) {\n    return;\n  }","sourceCodeStart":400,"sourceCodeEnd":436,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/sync-server/src/app-sync.ts#L400-L436","documentation":"GET /download-user-file requires the file id in the x-actual-file-id header. The server sends this 400 with the plain-text body 'Single file ID is required' when the header is absent or its value is not a string (per express header typing, an array). It is a request-shape validation error.","triggerScenarios":"Calling GET /download-user-file without setting the x-actual-file-id header, or in rare setups where the header appears multiple times so express parses it as an array instead of a string.","commonSituations":"Scripts or reverse proxies stripping custom X- headers; clients forgetting the header entirely; duplicate headers from misconfigured proxy config (headers: 'string' vs array in express types).","solutions":["Set the x-actual-file-id header to a single valid file id string on the download request.","Ensure no proxy adds or duplicates the header; remove duplicate x-actual-file-id entries so express yields a string.","Verify with curl -H \"x-actual-file-id: <fileId>\" that the header survives your proxy chain."],"exampleFix":"// before\nawait fetch(base + '/download-user-file');\n// after\nawait fetch(base + '/download-user-file', {\n  headers: { 'x-actual-file-id': fileId },\n});","handlingStrategy":"validation","validationCode":"if (typeof fileId !== 'string' || fileId.length === 0) {\n  throw new Error('x-actual-file-id header required');\n}","typeGuard":"function hasFileIdHeader(v: unknown): v is string {\n  return typeof v === 'string' && v.length > 0;\n}","tryCatchPattern":"const res = await fetch(url, { headers: { 'x-actual-file-id': fileId } });\nif (res.status === 400) throw new Error('check x-actual-file-id header is set to a single string');","preventionTips":["Always set x-actual-file-id as a single header value on download requests.","Check reverse-proxy config doesn't strip or duplicate custom X- headers.","Centralize sync-server API calls in one client helper that injects the header."],"tags":["http","validation","headers","client-error"],"backgroundTag":"missing-required-header","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}