actualbudget/actual · error · APIError

transactions-import: payeeNameNormalization must be one of $

Error message

transactions-import: payeeNameNormalization must be one of ${bankSync.PAYEE_NAME_NORMALIZATIONS.join(', ')}, got '${String(payeeNameNormalization)}'

What it means

The transactions-import API accepts an optional opts.payeeNameNormalization that controls how payee names are normalized during reconciliation. The value must be one of PAYEE_NAME_NORMALIZATIONS = ['original', 'title-case'] (sync.ts:416); anything else, including typos or differently-cased values, throws this APIError before any import runs. It defaults to 'title-case' when omitted.

Source

Thrown at packages/loot-core/src/server/accounts/app.ts:1651

async function importTransactions({
  accountId,
  transactions,
  isPreview,
  opts,
}: {
  accountId: AccountEntity['id'];
  transactions: ImportTransactionEntity[];
  isPreview: boolean;
  opts?: ImportTransactionsOpts;
}): Promise<ImportTransactionsResult> {
  if (typeof accountId !== 'string') {
    throw APIError('transactions-import: accountId must be an id');
  }

  const payeeNameNormalization = opts?.payeeNameNormalization ?? 'title-case';
  if (!bankSync.PAYEE_NAME_NORMALIZATIONS.includes(payeeNameNormalization)) {
    throw APIError(
      `transactions-import: payeeNameNormalization must be one of ${bankSync.PAYEE_NAME_NORMALIZATIONS.join(
        ', ',
      )}, got '${String(payeeNameNormalization)}'`,
    );
  }

  try {
    const reconciled = await bankSync.reconcileTransactions(
      accountId,
      transactions,
      {
        isPreview,
        defaultCleared: opts?.defaultCleared,
        reimportDeleted: opts?.reimportDeleted,
        payeeNameNormalization,
      },
    );
    return {

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Set opts.payeeNameNormalization to exactly 'title-case' or 'original' (lowercase).
  2. Omit the option entirely to accept the 'title-case' default.
  3. Validate the value against PAYEE_NAME_NORMALIZATIONS before calling (the array is exported from loot-core's bankSync module).
  4. If migrating from an older version, update stale option values to the current enum.

Example fix

// before
await importTransactions({ accountId, transactions, isPreview: false, opts: { payeeNameNormalization: 'Title Case' } });
// after
await importTransactions({ accountId, transactions, isPreview: false, opts: { payeeNameNormalization: 'title-case' } });
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED = ['original', 'title-case'];
if (opts?.payeeNameNormalization && !ALLOWED.includes(opts.payeeNameNormalization)) {
  throw new Error(`payeeNameNormalization must be one of ${ALLOWED.join(', ')}`);
}

Type guard

const PAYEE_NAME_NORMALIZATIONS = ['original', 'title-case'] as const;
type PayeeNameNormalization = (typeof PAYEE_NAME_NORMALIZATIONS)[number];
function isPayeeNameNormalization(v: unknown): v is PayeeNameNormalization {
  return typeof v === 'string' && (PAYEE_NAME_NORMALIZATIONS as readonly string[]).includes(v);
}

Try / catch

try {
  await api.transactionsImport(accountId, txns, opts);
} catch (e) {
  if (e instanceof APIError && e.message.includes('payeeNameNormalization')) {
    // fall back to the default
    await api.transactionsImport(accountId, txns, { ...opts, payeeNameNormalization: 'title-case' });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling importTransactions with opts.payeeNameNormalization set to anything other than 'original' or 'title-case', e.g. 'Title-Case', 'lowercase', 'none', or an outdated value from an older API version.

Common situations: Copy-pasting option names from old docs or blog posts; case-mismatched enum values from loosely typed JS callers; a config UI passing user-entered text instead of a constrained select value.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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