{"record":{"id":"59e40ddf4d785568","repo":"actualbudget/actual","slug":"transactions-import-accountid-must-be-an-id","errorCode":null,"errorMessage":"transactions-import: accountId must be an id","messagePattern":"transactions-import: accountId must be an id","errorType":"exception","errorClass":"APIError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/accounts/app.ts","lineNumber":1646,"sourceCode":"export type ImportTransactionsResult = bankSync.ReconcileTransactionsResult & {\n  errors: Array<{\n    message: string;\n  }>;\n};\n\nasync function importTransactions({\n  accountId,\n  transactions,\n  isPreview,\n  opts,\n}: {\n  accountId: AccountEntity['id'];\n  transactions: ImportTransactionEntity[];\n  isPreview: boolean;\n  opts?: ImportTransactionsOpts;\n}): Promise<ImportTransactionsResult> {\n  if (typeof accountId !== 'string') {\n    throw APIError('transactions-import: accountId must be an id');\n  }\n\n  const payeeNameNormalization = opts?.payeeNameNormalization ?? 'title-case';\n  if (!bankSync.PAYEE_NAME_NORMALIZATIONS.includes(payeeNameNormalization)) {\n    throw APIError(\n      `transactions-import: payeeNameNormalization must be one of ${bankSync.PAYEE_NAME_NORMALIZATIONS.join(\n        ', ',\n      )}, got '${String(payeeNameNormalization)}'`,\n    );\n  }\n\n  try {\n    const reconciled = await bankSync.reconcileTransactions(\n      accountId,\n      transactions,\n      {\n        isPreview,\n        defaultCleared: opts?.defaultCleared,","sourceCodeStart":1628,"sourceCodeEnd":1664,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/accounts/app.ts#L1628-L1664","documentation":"Actual's transactions-import API validates its inputs up front and throws an APIError when accountId is not a string. Account ids in Actual are internal string identifiers; the check at app.ts:1645 runs before any reconciliation work, so callers passing null, undefined, a number, or an object are rejected immediately. It guards the rest of the import pipeline from operating on a malformed account reference.","triggerScenarios":"Calling importTransactions / api.transactions-import with accountId === null, undefined, a numeric id, an object like {id: ...}, or a value read from the wrong field of an account entity.","commonSituations":"API consumers mapping external bank account objects to Actual accounts and passing the wrong property; using a numeric internal DB row id instead of Actual's string id; a failed lookup returning undefined before the import call.","solutions":["Log and inspect the accountId value just before the call; ensure it comes from an Actual account entity's id field (a string).","Resolve the account by name via the accounts/query API and use the returned id.","Fix type mappings so numeric ids from external systems are converted to Actual string ids before calling.","Wrap the call in try/catch on APIError and surface a clear validation message to the user."],"exampleFix":"// before\nawait api.transactionsImport(bankAccount.rowId, txns);\n// after\nconst acct = await api.qbi-query;\n// resolve first:\nconst accounts = await api.getAccounts();\nconst acct = accounts.find(a => a.name === 'Checking');\nif (!acct) throw new Error('Account not found');\nawait api.transactionsImport(acct.id, txns);","handlingStrategy":"validation","validationCode":"if (typeof accountId !== 'string' || accountId === '') {\n  throw new Error(`transactionsImport: expected string account id, got ${String(accountId)}`);\n}","typeGuard":"function isAccountId(v: unknown): v is string {\n  return typeof v === 'string' && v.length > 0;\n}","tryCatchPattern":"try {\n  await api.transactionsImport(accountId, transactions);\n} catch (e) {\n  if (e instanceof APIError && e.message.includes('accountId must be an id')) {\n    // surface a validation message / re-resolve the account id\n  } else throw e;\n}","preventionTips":["Always take accountId from an Actual account entity's id field, never from external DB rows.","Resolve accounts by name through the query API instead of hardcoding ids.","Add a pre-call assertion/validation of the id type in integration code.","Log the id before calling to make mis-mapped fields obvious."],"tags":["validation","api","typescript","account-id"],"backgroundTag":"invalid-api-argument","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}