---
title: "Create a reconciliation rule"
method: POST
path: "/reconciliation_rules"
---

# Create a reconciliation rule

`POST /reconciliation_rules`

## Request body

- object
  - `reconciliation_id` string, required
  - `filters` Filters, required — unresolved $ref
  - `priority` integer
  - `group_by` GroupBy — unresolved $ref
  - `cardinality` Cardinality — unresolved $ref
  - `rollup` Rollup — unresolved $ref
  - `description` string — Optional human-readable description of what this rule matches

## Response `201`

Rule created

- ReconciliationRule
  - `id` integer
  - `priority` integer — Lower values run first
  - `filters` object, required — A rule tree with group nodes (AND/OR) and condition leaf nodes. Conditions compare a field on one side to a field on the other side.
    - `operand` 'and' | 'or' — Logical operator for combining child rules
    - `rules` object[] — Child rules (groups or conditions)
      - `key` string — Source field to compare (e.g. "amount", "date", "metadata.invoice_id")
      - `operand` 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'in' | 'not_in' — Comparison operator
      - `value` string — Target field to compare against (e.g. "amount", "external_id")
      - `tolerance_amount` number — Optional tolerance for fuzzy matching with the 'eq' operand. Must be a positive number.
      - `tolerance_unit` 'dollar' | 'percent' | 'day' | 'month' — Unit for tolerance. 'dollar' and 'percent' for number fields, 'day' and 'month' for date fields. 'month' matches records in the same calendar month (tolerance_amount is ignored).
      - `transforms` object — Optional transforms applied to field values before comparison. Each side (key/value) can have a single transform string, a parameterized transform object, or an array of transforms applied in order (first = innermost). Available transforms: lowercase, uppercase, alphanumeric_only, digits_only, first_n_chars (requires {"name": "first_n_chars", "args": {"n": 8}}).
        - `key` unknown
        - `value` unknown
  - `group_by` string[] — Side A field keys to group records by before matching. The first key is the primary axis: in `rollup.group_by_kind` 'date' mode it must be a date field; in 'identifier' mode it can be any field (e.g. a batch id in metadata). Additional keys further segment Side A — in date mode each segment matches only Side B records with the same value for those fields; in identifier mode they form a composite cohort key.
  - `cardinality` 'one_to_one' | 'many_to_one' — Match cardinality. 'one_to_one' matches single records on each side. 'many_to_one' rolls up multiple records on one side to match a single record on the other side — requires the `rollup` field to be set.
  - `rollup` object, nullable — Required when cardinality is 'many_to_one'. Configures how records on the many side are aggregated before matching.
    - `lookback_days` integer — Optional. Number of days of history to include when rolling up records. Must be set together with `date_field`. Omit both to match without a time bound.
    - `date_field` string — Optional. The date field on the rolled-up records used to anchor the lookback window. Must be set together with `lookback_days`.
    - `group_by_kind` 'date' | 'identifier' — How `group_by` is interpreted. 'date' (default) buckets records per day and walks a date-window running sum. 'identifier' buckets records by exact value (e.g. a batch id) and matches the bucket total to one record on the other side, optionally bounded by the lookback window.
  - `description` string — Optional human-readable description of what this rule matches
  - `source` 'user' | 'ai' | 'default' — Origin of the rule. 'user' = created manually via the API or UI. 'ai' = created by the AI rules engine. 'default' = seeded built-in rule.
  - `status` 'active' | 'proposed' — Active rules are used by the reconciliation engine. Proposed rules are awaiting review from a cold start.
  - `confidence` 'high' | 'medium' | 'low' — AI confidence rating. Only present on AI-generated rules from cold start.
  - `sample_match_rate` string — How many sample records this rule matched during dry-run evaluation (e.g. '42/50'). Only present on AI-generated rules from cold start.
  - `reconciliation_id` integer, required
  - `created_at` string, date-time

## Other responses

- `422` — Validation error

---

[API](https://skmtc.net/endclose/apis/end-close-api.md) · [All operations](https://skmtc.net/endclose/apis/end-close-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/endclose/end-close-api/revisions/613ceff68dc7/schema)
