{"record":{"id":"53f547020299da61","repo":"actualbudget/actual","slug":"debug-category-id-is-required","errorCode":null,"errorMessage":"${debug}: category id is required","messagePattern":"(.+?): category id is required","errorType":"validation","errorClass":"APIError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/api.ts","lineNumber":106,"sourceCode":"let handlers = {} as unknown as Handlers;\n\nasync function validateMonth(month) {\n  if (!month.match(/^\\d{4}-\\d{2}$/)) {\n    throw APIError('Invalid month format, use YYYY-MM: ' + month);\n  }\n\n  if (!IMPORT_MODE) {\n    const { start, end } = await handlers['get-budget-bounds']();\n    const range = monthUtils.range(start, end);\n    if (!range.includes(month)) {\n      throw APIError('No budget exists for month: ' + month);\n    }\n  }\n}\n\nasync function validateExpenseCategory(debug, id) {\n  if (id == null) {\n    throw APIError(`${debug}: category id is required`);\n  }\n\n  const row = await db.first<Pick<db.DbCategory, 'is_income'>>(\n    'SELECT is_income FROM categories WHERE id = ?',\n    [id],\n  );\n\n  if (!row) {\n    throw APIError(`${debug}: category \"${id}\" does not exist`);\n  }\n\n  if (row.is_income !== 0) {\n    throw APIError(`${debug}: category \"${id}\" is not an expense category`);\n  }\n}\n\nfunction checkFileOpen() {\n  if (!(prefs.getPrefs() || {}).id) {","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/api.ts#L88-L124","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Supply a valid expense category id obtained from q.getCategories()","Default the parameter to a real category id (e.g. an 'Uncategorized' category) before calling","Guard the call site: if (categoryId == null) resolve or reject before invoking the API","For transactions that genuinely have no category, use the appropriate API path for uncategorized transactions rather than null category ids"],"exampleFix":"// before\nawait q.createTransaction({ accountId, date, amount: 2500, category: null });\n// APIError: createTransaction: category id is required\n// after\nconst [category] = await q.getCategories();\nawait q.createTransaction({ accountId, date, amount: 2500, category: category.id });","handlingStrategy":"validation","validationCode":"if (categoryId == null) {\n  throw new Error('categoryId is required before calling expense-category APIs');\n}","typeGuard":"function hasCategoryId(tx) {\n  return typeof tx.category === 'string' && tx.category.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Resolve a default/uncategorized expense category id at startup","Validate input objects for required category fields before API calls","Never rely on possibly-null lookups as category ids","In importers, map empty category columns to a real category id"],"tags":["validation","api","missing-argument","category"],"backgroundTag":"missing-required-parameter","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}