{"record":{"id":"5c6412d25de0ce73","repo":"actualbudget/actual","slug":"missing-key-5c6412","errorCode":"missing-key","errorMessage":"File ${activeFile.name} is encrypted. Please provide a password.","messagePattern":"File (.+?) is encrypted\\. Please provide a password\\.","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/api.ts","lineNumber":217,"sourceCode":"    const file = files.find(f => f.groupId === syncId);\n    if (!file) {\n      throw withErrorCode(\n        new Error(\n          `Budget \"${syncId}\" not found. Check the sync id of your budget in the Advanced section of the settings page.`,\n        ),\n        'budget-not-found',\n      );\n    }\n\n    remoteBudget = file;\n  }\n\n  const activeFile = remoteBudget ? remoteBudget : localBudget;\n\n  // Set the e2e encryption keys\n  if (activeFile.encryptKeyId) {\n    if (!password) {\n      throw withErrorCode(\n        new Error(\n          `File ${activeFile.name} is encrypted. Please provide a password.`,\n        ),\n        'missing-key',\n      );\n    }\n\n    const result = await handlers['key-test']({\n      cloudFileId: remoteBudget ? remoteBudget.fileId : localBudget.cloudFileId,\n      password,\n    });\n    if (result.error) {\n      throw withErrorCode(\n        new Error(getTestKeyError(result.error)),\n        result.error.reason,\n      );\n    }\n  }","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/api.ts#L199-L235","documentation":"Thrown by api/download-budget in packages/loot-core/src/server/api.ts when the active budget file (remote or local) has an encryptKeyId — i.e. it is end-to-end encrypted — but downloadBudget() was called without a password. Tagged with code 'missing-key'. The API refuses to proceed because it cannot derive the decryption key.","triggerScenarios":"Calling downloadBudget(syncId) without the { password } option on a budget that was encrypted via Settings > Advanced > End-to-end encryption; also triggered when the local file references an encryptKeyId but the caller supplied no password to key-test/key setup.","commonSituations":"Automated scripts written before the budget was encrypted later gaining a key; users forgetting the file was E2E encrypted; headless jobs with credentials stored but password omitted from the downloadBudget call.","solutions":["Pass the encryption password: downloadBudget(syncId, { password })","Store/retrieve the password from your secrets manager in headless environments","Remove end-to-end encryption on the budget if you no longer need it (via the settings page)","If password unknown, the data cannot be decrypted — restore from an unencrypted backup"],"exampleFix":"// before\nawait api.downloadBudget(syncId);\n// after\nawait api.downloadBudget(syncId, { password: process.env.BUDGET_ENCRYPTION_PASSWORD });","handlingStrategy":"validation","validationCode":"const file = (await getBudgetFiles()).find(f => f.groupId === syncId);\nif (file?.encryptKeyId && !password) throw new Error('Password required: budget is E2E encrypted');","typeGuard":"function requiresPassword(f?: { encryptKeyId?: string | null }): boolean {\n  return !!f?.encryptKeyId;\n}","tryCatchPattern":"try {\n  await api.downloadBudget(syncId, { password });\n} catch (e) {\n  if (e.code === 'missing-key') throw new Error('Supply a password for the encrypted budget');\n  throw e;\n}","preventionTips":["Always check encryptKeyId on the file metadata before downloading","Store the encryption password alongside server credentials in secrets management","Include the password parameter in headless/CI download scripts","Disable E2E encryption if it is no longer needed"],"tags":["encryption","password","api"],"backgroundTag":"missing-encryption-key","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}