actualbudget/actual · error · Error
missing-key
missing-key
Error message
File ${activeFile.name} is encrypted. Please provide a password. What it means
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.
Source
Thrown at packages/loot-core/src/server/api.ts:217
const file = files.find(f => f.groupId === syncId);
if (!file) {
throw withErrorCode(
new Error(
`Budget "${syncId}" not found. Check the sync id of your budget in the Advanced section of the settings page.`,
),
'budget-not-found',
);
}
remoteBudget = file;
}
const activeFile = remoteBudget ? remoteBudget : localBudget;
// Set the e2e encryption keys
if (activeFile.encryptKeyId) {
if (!password) {
throw withErrorCode(
new Error(
`File ${activeFile.name} is encrypted. Please provide a password.`,
),
'missing-key',
);
}
const result = await handlers['key-test']({
cloudFileId: remoteBudget ? remoteBudget.fileId : localBudget.cloudFileId,
password,
});
if (result.error) {
throw withErrorCode(
new Error(getTestKeyError(result.error)),
result.error.reason,
);
}
}View on GitHub (pinned to d4334cb6e6)
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
Example fix
// before
await api.downloadBudget(syncId);
// after
await api.downloadBudget(syncId, { password: process.env.BUDGET_ENCRYPTION_PASSWORD }); Defensive patterns
Strategy: validation
Validate before calling
const file = (await getBudgetFiles()).find(f => f.groupId === syncId);
if (file?.encryptKeyId && !password) throw new Error('Password required: budget is E2E encrypted'); Type guard
function requiresPassword(f?: { encryptKeyId?: string | null }): boolean {
return !!f?.encryptKeyId;
} Try / catch
try {
await api.downloadBudget(syncId, { password });
} catch (e) {
if (e.code === 'missing-key') throw new Error('Supply a password for the encrypted budget');
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- getTestKeyError(result.error)
- Error importing budget: ${result.error}
- Error importing budget: no budget was loaded
- Error exporting budget: ${result.error}
- Error exporting budget: no data was returned
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/5c6412d25de0ce73.
Report an issue: GitHub.