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

  1. Query get-budget-bounds and pick a month inside the returned start/end range
  2. Budget the target month in the app (open it in the budget table) to extend the bounds before using it via API
  3. Call the API after ensuring at least one budget month exists in the file
  4. 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

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


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