actualbudget/actual · error · APIError

Invalid month format, use YYYY-MM: ${month}

Error message

Invalid month format, use YYYY-MM: ${month}

What it means

validateMonth throws APIError when a month argument passed to a public API method does not match the required YYYY-MM format. The API validates all month-typed inputs before executing handlers so budget lookups receive a well-formed month string. This is an input validation guard, not a data problem.

Source

Thrown at packages/loot-core/src/server/api.ts:92

        if (connection.getNumClients() > 1) {
          connection.send('sync-event', {
            type: 'success',
            tables: rows.map(row => row.dataset),
          });
        }

        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 = ?',

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Format the month as a 4-digit year, hyphen, 2-digit zero-padded month (e.g. '2024-01')
  2. Use a formatter like month.toISOString().slice(0, 7) or date-fns format(date, 'yyyy-MM') to derive the string
  3. Validate the string with /^\d{4}-\d{2}$/ before calling the API
  4. If you have a full date, strip the day portion first

Example fix

// before
await q.getBudgetMonth('2024-1'); // APIError: Invalid month format
await q.getBudgetMonth(new Date().toISOString()); // '2024-01-15T...'
// after
await q.getBudgetMonth('2024-01');
await q.getBudgetMonth(new Date().toISOString().slice(0, 7));
Defensive patterns

Strategy: validation

Validate before calling

function isValidMonth(m) {
  return typeof m === 'string' && /^\d{4}-\d{2}$/.test(m);
}
if (!isValidMonth(month)) throw new Error(`month must be YYYY-MM, got: ${month}`);

Type guard

function isMonth(value) {
  return typeof value === 'string' && /^\d{4}-\d{2}$/.test(value);
}

Prevention

When it happens

Trigger: Calling API methods that take a month (e.g. getBudgetMonth, setBudgetAmount, getCategories) with strings like '2024', '2024-1', '2024/01', '2024-01-01', or an empty string.

Common situations: Passing a full ISO date ('2024-01-15') instead of a month; building the month string with JS Date without zero-padding; forgetting month is 1-indexed in some libraries (month '1' not '01'); passing undefined coerced to a string.

Related errors


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