actualbudget/actual · error · APIError
No budget exists for month: ${month}
Error message
No budget exists for month: ${month} What it means
validateMonth throws APIError when the month is well-formed (YYYY-MM) but falls outside the budget's existing range. Actual queries get-budget-bounds and builds the month range between the budget start and end; the requested month must lie inside it. Thrown only when IMPORT_MODE is off, i.e. in normal API usage.
Source
Thrown at packages/loot-core/src/server/api.ts:99
return result;
},
{ undoDisabled: true },
);
};
}
let handlers = {} as unknown as Handlers;
async function validateMonth(month) {
if (!month.match(/^\d{4}-\d{2}$/)) {
throw APIError('Invalid month format, use YYYY-MM: ' + month);
}
if (!IMPORT_MODE) {
const { start, end } = await handlers['get-budget-bounds']();
const range = monthUtils.range(start, end);
if (!range.includes(month)) {
throw APIError('No budget exists for month: ' + month);
}
}
}
async function validateExpenseCategory(debug, id) {
if (id == null) {
throw APIError(`${debug}: category id is required`);
}
const row = await db.first<Pick<db.DbCategory, 'is_income'>>(
'SELECT is_income FROM categories WHERE id = ?',
[id],
);
if (!row) {
throw APIError(`${debug}: category "${id}" does not exist`);
}
View on GitHub (pinned to d4334cb6e6)
Solutions
- Query get-budget-bounds and pick a month inside the returned start/end range
- Budget the target month in the app (open it in the budget table) to extend the bounds before using it via API
- Call the API after ensuring at least one budget month exists in the file
- If running bulk imports, set IMPORT_MODE intentionally to skip bounds checks
Example fix
// before
await q.getBudgetMonth('2030-01'); // APIError: No budget exists for month
// after
const { start, end } = await q.run('get-budget-bounds');
if ('2030-01' >= start && '2030-01' <= end) {
await q.getBudgetMonth('2030-01');
} Defensive patterns
Strategy: validation
Validate before calling
const { start, end } = await q.run('get-budget-bounds');
if (month < start || month > end) {
throw new Error(`month ${month} outside budget bounds ${start}..${end}`);
} Try / catch
try {
await q.getBudgetMonth(month);
} catch (e) {
if (String(e.message).startsWith('No budget exists for month')) {
// clamp month into budget bounds or budget the month first
} else throw e;
} Prevention
- Fetch get-budget-bounds and clamp requested months into range
- Budget/visit months in the app before scripting against them
- For bulk imports, use IMPORT_MODE deliberately
- Avoid hard-coding future months in automation
When it happens
Trigger: Calling getBudgetMonth/setBudgetAmount/etc. with a month before the budget was created or after its last budgeted month (e.g. requesting '2023-01' for a budget that starts '2024-01', or a future month beyond created budget pages).
Common situations: Reading budgets of a brand-new budget file without creating any months; requesting next year's months on a budget whose bounds end earlier; testing against a demo budget with a limited date range; IMPORT_MODE assumptions differing between scripts and app usage.
Related errors
- Unknown payee name normalization: ${String(normalization)}
- `date` is required when adding a transaction
- Amount is invalid, must be an integer: ${trans.amount}
- There is already a filter named ${item.name}
- Filter name is required
AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29).
Data as JSON: /api/errors/722718d9c3545f60.
Report an issue: GitHub.