---
title: "Save a rule that changes how matching transactions read, tags included."
method: POST
path: "/v1/rules"
---

# Save a rule that changes how matching transactions read, tags included.

`POST /v1/rules`

## Request

- Base URL: `https://api.candor.money`
- URL: `https://api.candor.money/v1/rules`
- Auth: HTTP bearer

Save a rule that changes how matching transactions read, past and as new ones arrive: their category, label, cashflow role, review status, or tags. Tags group money the categories do not, such as a side business, a trip, or one person's costs; a goal measured as tagged reads their monthly net. The shorthand args cover the usual rule: name, a match (match_description_contains, which takes one text or a list of payees, or match_merchant_contains, narrowed by match_direction), one effect (set_category, set_label, set_cashflow_role, or add_tag), authority, and basis_reason. Give every payee that belongs as one list. The full body goes in input. rules.preview takes the same args or input before you save, and lists what the rule would reach; rules apply in the background.

## Headers

- `Idempotency-Key` string, required

## Request body

- InterpretationRuleCreateRequest
  - `name` string, required: What this rule is for, in the user's terms. It appears when explaining why a transaction reads the way it does.
  - `family` 'normalization', required: What the rule governs. Normalization is the only family today.
  - `target` 'transaction_fields', required: What the rule changes.
  - `authority` 'agent_verified' | 'user_approved', required: Where the rule's meaning came from. `user_approved` records that the user said so; `agent_verified` records that you established it from evidence and can show the basis.
  - `selector` union, required: What the rule matches: one exact transaction, or a criteria set that reaches past and future ones. A broad selector reaches far: rules.preview with the draft lists what it would reach before you save, and rules.disable reverts every change a saved rule made.
    - TransactionRecordRuleSelector
      - `kind` 'transaction_record', required: Selects one exact record, and nothing it resembles.
      - `transaction_id` string, required: The canonical transaction this rule applies to.
    - TransactionMatchRuleSelector
      - `kind` 'transaction_match', required: Selects every transaction the criteria match, including ones that arrive later.
      - `criteria` object, required: What a transaction must look like for this rule to reach it. A broad criteria set reaches further than the records you looked at; check the draft with rules.preview before saving, and rules.disable reverts every change if it reaches too far.
        - `account_role` 'operating_cash' | 'cash_reserve' | 'health_savings' | 'retirement' | 'investment' | 'credit_card' | 'debt' | 'unknown': The role of the account a transaction posted to, as accounts list reports it. Prefer this over source_account_id for anything that should keep matching after a connection is replaced.
        - `currency` string: Three-letter uppercase ISO code, such as USD.
        - `current_category` string
        - `description_contains` string: Case-insensitive substring of the raw source description only. Bank text changes per charge, so this anchor stays broad, and the rule cannot auto-apply, until a narrowing criterion joins it (account_role, direction, currency, source_account_id, min_amount, or max_amount).
        - `description_contains_any` string[]: Several payees in one rule. Matches when the raw source description contains any of these, without regard to case; plain text, not patterns. Narrowed the same way as description_contains.
        - `direction` 'inflow' | 'outflow'
        - `has_tag` string
        - `max_amount` union: Exact Money input using a supported currency code, one space, and a plain signed decimal within Candor's supported range.
          - string
          - string
          - string
        - `merchant_name_contains` string: Case-insensitive substring of the provider's merchant name, the effective label, or the description. The provider's merchant name is stable across charges, so this anchor is narrow on its own and the rule may reach future records without another criterion. Prefer it for "this merchant is X".
        - `merchant_name_equals` string: Case-insensitive exact match of the provider's merchant name, the effective label, or the description. Narrow on its own, like merchant_name_contains.
        - `min_amount` union: Exact Money input using a supported currency code, one space, and a plain signed decimal within Candor's supported range.
          - string
          - string
          - string
        - `source_classification_contains` string
        - `source_classification_equals` string
        - `review_status` 'unreviewed' | 'needs_review' | 'reviewed' | 'ignored'
        - `since` string
        - `source_account_id` string: The account this belongs to.
        - `until` string
  - `effect` SetTransactionFieldsRuleEffect, required: The field changes the rule applies where it matches.
    - `kind` 'set_transaction_fields', required: The sort of change this rule makes.
    - `actions` object, required: The field changes to apply. Everything omitted is left as the source recorded it.
      - `add_tags` string[]
      - `remove_tags` string[]
      - `set_cashflow_role` 'income' | 'expense' | 'transfer' | 'debt_payment' | 'refund' | 'unknown'
      - `set_category` string
      - `set_label` string
      - `set_review_status` 'unreviewed' | 'needs_review' | 'reviewed' | 'ignored'
  - `basis` InterpretationRuleBasis, required: The evidence behind the rule, so a later run can judge whether it still holds.
    - `reason` string, required: Why this rule is correct, in one line a later run can weigh. Not approval and not evidence — the transaction ids carry that.
    - `transaction_ids` string[]
  - `priority` number
  - `created_by` string

## Response `200`

Envelope with rule metadata, caveats, and suggested commands.

- ResponseEnvelope
  - `schema_version` string, required
  - `generated_at` string, date-time, required
  - `request_id` string, required
  - `status` 'success' | 'partial_success' | 'action_required' | 'error', required
  - `user_message` string: Optional concise user-safe message the calling agent may adapt.
  - `data_freshness` DataFreshness
    - `balances_last_synced_at` string
    - `coverage` 'complete' | 'partial' | 'stale' | 'unavailable', required
    - `holdings_last_synced_at` string
    - `transactions_last_synced_at` string
  - `metadata` object: Response-level protocol metadata such as package compatibility and workspace opening identity or timing. It is not financial workspace data and must not be treated as attention.
    - `view_url` string, uri: Authenticated dashboard destination related to this response. May open a section rather than reproduce the returned records or filters. Per-record view_url values identify exact supported record views. Use returned URLs instead of constructing paths.
  - `warnings` object[], required
  - `errors` AgentError[], required
    - `code` string, required
    - `expires_at` string
    - `idempotency_key` string: Stable key to reuse when retrying this identical logical operation.
    - `message` string, required
    - `recovery_url` string, uri
    - `required_capability` string
    - `retry_after_seconds` integer: Machine-actionable delay before retrying the identical logical operation.
    - `retryable` boolean, required
    - `suggested_command` string
  - `next_actions` AgentAction[], required
    - `intent` string, required
    - `command` string, required
    - `args` object, required
    - `shell` string, required
  - `action` WorkspaceAction
    - `id` string, required
    - `operation` string, required
    - `reason` string, required
    - `root_action_id` string, required
    - `parent_action_id` string
    - `task_key` string
    - `status` 'started' | 'succeeded' | 'refused' | 'failed', required
    - `created_at` string, date-time, required
    - `completed_at` string, date-time
  - `data` object, nullable, required: Operation-specific result data, or null when the request ends in an error.

## Other responses

- `default`: Structured Candor error envelope with machine-readable codes, recovery guidance, and retry timing when applicable.

---

[API](https://skmtc.dev/candor/apis/candor-api.md) · [All operations](https://skmtc.dev/candor/apis/candor-api/llms.txt) · [OpenAPI document](https://skmtc.dev/candor/apis/candor-api/revisions/6c050f72e956?raw)
