actualbudget/actual · error · APIError

${debug}: category id is required

Error message

${debug}: category id is required

What it means

validateExpenseCategory throws APIError when the category id argument is null or undefined. API methods that operate on expense categories require a concrete category id to look up in the database. The debug prefix identifies which API call failed.

Source

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

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`);
  }

  if (row.is_income !== 0) {
    throw APIError(`${debug}: category "${id}" is not an expense category`);
  }
}

function checkFileOpen() {
  if (!(prefs.getPrefs() || {}).id) {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Supply a valid expense category id obtained from q.getCategories()
  2. Default the parameter to a real category id (e.g. an 'Uncategorized' category) before calling
  3. Guard the call site: if (categoryId == null) resolve or reject before invoking the API
  4. For transactions that genuinely have no category, use the appropriate API path for uncategorized transactions rather than null category ids

Example fix

// before
await q.createTransaction({ accountId, date, amount: 2500, category: null });
// APIError: createTransaction: category id is required
// after
const [category] = await q.getCategories();
await q.createTransaction({ accountId, date, amount: 2500, category: category.id });
Defensive patterns

Strategy: validation

Validate before calling

if (categoryId == null) {
  throw new Error('categoryId is required before calling expense-category APIs');
}

Type guard

function hasCategoryId(tx) {
  return typeof tx.category === 'string' && tx.category.length > 0;
}

Prevention

When it happens

Trigger: Calling methods like createTransaction or setBudgetAmount with categoryId undefined — e.g. destructuring an object whose field is missing, passing null explicitly, or omitting the category parameter for a categorized transaction.

Common situations: Spreadsheets/CSV importers that leave the category column empty; API clients that send JSON without the category key; code that passes a variable which is null because a prior lookup failed silently.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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