---
title: "Simulate a match rule against a context (read-only dry run)"
method: POST
path: "/v1/matching/simulate"
tags: ["Matching"]
---

# Simulate a match rule against a context (read-only dry run)

`POST /v1/matching/simulate`

Previews how a single rule — an existing configured rule (ruleId) OR an inline candidate rule (rule) — would match a context's unmatched transactions, WITHOUT committing anything. Returns the number of 1:1 groups the rule would form, a bounded sample of would-match pairs (each with a confidence score, a per-component rationale (the "why"), and matched composite keys), and the per-side unmatched counts. Powers the rule-authoring "will this rule actually match?" preview. Scope: scored by the deterministic rule engine over raw transaction amounts; it does NOT apply run-time fee normalization or the FX-variance band, and previews only 1:1 pairwise grouping (no 1:N/N:M allocation). Nothing is persisted; the tenant is taken from the JWT, never the body.

## Request body

- SimulateMatchRequest
  - `contextId` string, uuid, required — Context ID whose transactions the rule is simulated against
  - `rule` SimulateRuleDefinition
    - `config` object, required — Rule configuration, same shape as a stored match rule's config (e.g. matchAmount, matchCurrency, tolerance settings)
    - `type` string, required — Rule strategy of the inline candidate rule
  - `ruleId` string, uuid — Identifier of an existing configured rule to preview. Provide this OR rule, not both.
  - `sampleLimit` integer — Maximum number of would-match pairs to return in the sample

## Response `200`

OK

- SimulateMatchResponse
  - `matchedGroups` integer, required — Number of 1:1 groups the rule would form across the context
  - `ruleId` string — Identifier of the previewed rule. Empty for an inline candidate rule (not persisted).
  - `ruleType` string, required — Strategy of the previewed rule
  - `sample` SimulateMatchPairResponse[], nullable, required — Bounded sample of would-match pairs (highest score first)
    - `amountDelta` string, required — right.amount - left.amount as a signed decimal string
    - `dateDeltaDays` integer, required — Signed whole-day difference (right - left) in UTC days
    - `left` CandidateTransactionView, required
      - `amount` string, required — Candidate amount as a decimal string
      - `baseAmount` string — Candidate base-currency amount, when the transaction was FX-converted
      - `currency` string, required — Candidate ISO 4217 currency code
      - `date` string, required — Candidate transaction date (RFC 3339 timestamp)
      - `externalId` string — External reference/id of the candidate transaction
      - `id` string, required — Candidate transaction ID (UUID)
      - `sourceId` string, required — Source the candidate belongs to (UUID)
    - `right` CandidateTransactionView, required
      - `amount` string, required — Candidate amount as a decimal string
      - `baseAmount` string — Candidate base-currency amount, when the transaction was FX-converted
      - `currency` string, required — Candidate ISO 4217 currency code
      - `date` string, required — Candidate transaction date (RFC 3339 timestamp)
      - `externalId` string — External reference/id of the candidate transaction
      - `id` string, required — Candidate transaction ID (UUID)
      - `sourceId` string, required — Source the candidate belongs to (UUID)
    - `score` integer, required — Engine confidence score for this pair (0..100)
    - `why` CandidateWhy, required
      - `amountMatch` boolean, required — Whether the amounts are literally equal on the matched rule's sign axis (not merely within tolerance)
      - `currencyMatch` boolean, required — Whether the currency codes are literally equal and both non-empty
      - `dateMatch` boolean, required — Whether both transactions fall on the same UTC calendar day (not the full DATE_LAG window)
      - `matchedKeys` CandidateMatchedKey[], nullable — Per-key agreement for the matched rule's configured composite match fields (matchFields), in field-name order. Empty when the matched rule declares no active matchFields.
        - `agreed` boolean, required — Whether the two transactions agree on this key under the field's configured mode/tolerance — the engine's own per-field gate decision
        - `field` string, required — Name of the configured composite match field (metadata key)
      - `referenceScore` number, double, required — Graded 0..1 reference-similarity contribution
  - `sampleTruncated` boolean, required — Whether matchedGroups exceeds the returned sample length
  - `unmatchedLeft` integer, required — Left-side transactions that would remain unmatched
  - `unmatchedRight` integer, required — Right-side transactions that would remain unmatched

## Other responses

- `default` — Error

---

[API](https://skmtc.net/lerian/apis/identity-plugin.md) · [All operations](https://skmtc.net/lerian/apis/identity-plugin/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/lerian/identity-plugin/versions/25daba385532/schema)
