actualbudget/actual · error · APIError

Failed creating a new rule

Error message

Failed creating a new rule

What it means

api.createRule() delegates to the internal rule-add handler, which validates the rule (conditions, actions, condition structure). If the internal handler returns { error }, the API wraps it in this APIError with the original error attached.

Solutions

  1. Inspect the attached addedRule.error (the APIError's cause) for the underlying validation message
  2. Build the rule object with a valid conditionOp/conditions shape per docs — verify field names and operator/value compatibility
  3. Test the same rule in the UI Rules editor; if the UI rejects it too, fix the rule, not the API call

Example fix

// before
await api.createRule({ conditions: [{ field: 'payee_name', op: 'contains', value: 123 }] });
// after
await api.createRule({ conditions: [{ field: 'payee_name', op: 'contains', value: 'Store' }], actions: [{ field: 'category', value: 'Groceries' }], operation: 'and' });
Defensive patterns

Strategy: try-catch

Validate before calling

const parsed = ruleModel.fromExternal(rule);
const res = await handlers['rule-add'](parsed);
if ('error' in res) preflightCheck(res.error);

Type guard

function isRuleError(r) {
  return r != null && typeof r === 'object' && 'error' in r;
}

Try / catch

const added = await ruleAdd(rule);
if (isRuleError(added)) {
  throw new APIError('Failed creating a new rule', added.error);
}

Prevention

When it happens

Trigger: Calling api.createRule(rule) with a rule whose conditions/actions reference unknown fields, invalid operators (e.g. wrong value type for the operator), or a malformed conditions object.

Common situations: Hand-constructed rule payloads in scripts; rules exported from an older Actual version with operators no longer supported; typos in field names like 'payee' vs 'payee_name'.

Related errors


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

Appendix: source

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

  return handlers['payees-get-nearby']({ latitude, longitude, maxDistance });
};

handlers['api/rules-get'] = async function () {
  checkFileOpen();
  return handlers['rules-get']();
};

handlers['api/payee-rules-get'] = async function ({ id }) {
  checkFileOpen();
  return handlers['payees-get-rules']({ id });
};

handlers['api/rule-create'] = withMutation(async function ({ rule }) {
  checkFileOpen();
  const addedRule = await handlers['rule-add'](ruleModel.fromExternal(rule));

  if ('error' in addedRule) {
    throw APIError('Failed creating a new rule', addedRule.error);
  }

  return addedRule;
});

handlers['api/rule-update'] = withMutation(async function ({ rule }) {
  checkFileOpen();
  const updatedRule = await handlers['rule-update'](
    ruleModel.fromExternal(rule),
  );

  if ('error' in updatedRule) {
    throw APIError('Failed updating the rule', updatedRule.error);
  }

  return updatedRule;
});

View on GitHub (pinned to d4334cb6e6)