Matcher

The matcher takes two datasets and finds records that correspond to each other. It outputs three arrays: matched pairs, unmatched records from the left side, and unmatched records from the right side.

For three or four datasets that belong to one group, such as a purchase order, its receipts, and its invoices, use the N-Way Match step instead of chaining two-way matches.

flowchart LR L["Left Dataset<br/>(e.g. invoices)"] --> M{"Matcher"} R["Right Dataset<br/>(e.g. payments)"] --> M M --> Matched["Matched Pairs"] M --> UL["Unmatched Left"] M --> UR["Unmatched Right"]

Basic Usage

json
{
  "type": "matcher",
  "properties": {
    "left": "@input.invoices",
    "right": "@input.payments",
    "matchOn": ["invoice_id"],
    "outputMatched": "matched",
    "outputUnmatchedLeft": "unmatched_invoices",
    "outputUnmatchedRight": "unmatched_payments"
  }
}

This matches invoices to payments by exact invoice_id. Records with matching IDs land in @matched. Invoices with no payment go to @unmatched_invoices. Payments with no invoice go to @unmatched_payments.


Properties Reference

Property Type Required Description
left array / @path / doc: Yes First dataset as an inline array, @path reference, or doc: uploaded document
right array / @path / doc: Yes Second dataset as an inline array, @path reference, or doc: uploaded document
matchOn string[] Yes Fields that must match exactly
tolerance number No Absolute numeric tolerance applied to the amount field. A tolerance of 0.02 means amounts differing by more than $0.02 won't match
dateWindowDays number No Date tolerance in days, applied to the date field
fuzzyThreshold number No Text similarity threshold from 0 to 100, applied to descriptionKey
descriptionKey string No Field name for fuzzy text matching
rules array No Custom matching rules
outputMatched string No Context key for matched pairs (default: "matched")
outputUnmatchedLeft string No Context key for unmatched left records (default: "unmatchedLeft")
outputUnmatchedRight string No Context key for unmatched right records (default: "unmatchedRight")

Matching Criteria

Exact Key Matching (matchOn)

Fields listed in matchOn must match exactly. Records are compared only when their matchOn fields align.

json
{
  "matchOn": ["invoice_id"]
}

Multiple keys create a composite match. Every key must match:

json
{
  "matchOn": ["vendor_id", "invoice_number"]
}

Numeric Tolerance (tolerance)

Allow the amount field to differ by an absolute value. A tolerance of 50 means amounts within $50 of each other are still considered a match.

json
{
  "matchOn": ["invoice_id"],
  "tolerance": 50
}

With this configuration, an invoice for $1,000.00 would match a payment from $950.00 to $1,050.00. Smaller tolerances such as 0.02 are valid when you mean two cents.

Date Window (dateWindowDays)

Allow date fields to differ by up to N days:

json
{
  "matchOn": ["invoice_id"],
  "dateWindowDays": 3
}

An invoice dated January 10 would match a payment dated January 7 through January 13.

Fuzzy Text Matching (fuzzyThreshold + descriptionKey)

Compare text fields using fuzzy string similarity. The threshold runs from 0 to 100, where 100 is an exact match:

json
{
  "matchOn": ["vendor_id"],
  "fuzzyThreshold": 85,
  "descriptionKey": "description"
}

This matches records where vendor_id is identical and the description fields are at least 85% similar. Useful for matching line-item descriptions that may be worded differently across systems.

Custom Rules (rules)

Add a compact comparison rule when two records must agree on another field:

json
{
  "matchOn": ["invoice_id"],
  "rules": [
    {
      "field": "left.currency",
      "operator": "eq",
      "value": "@right.currency"
    }
  ]
}

Each rule uses field, operator, and value. The supported operators are eq, neq, >, >=, <, <=, and contains. Use left.<field> for the left record and @right.<field> for a value from the right record. Use tolerance for an allowed difference on amount.


Output Format

Matched Pairs

Each matched record contains both the left and right record:

json
[
  {
    "left": { "invoice_id": "INV-001", "amount": 1000, "vendor": "Acme" },
    "right": { "invoice_id": "INV-001", "amount": 1000, "vendor": "Acme Corp" }
  }
]

The matcher returns the original records under left and right. Counts and the applied policy are available in the normalized matcher result.

Unmatched Records

Unmatched arrays contain the original records with no modifications:

json
[
  { "invoice_id": "INV-099", "amount": 5000, "vendor": "NewVendor" }
]

Worked Example

Input:

json
{
  "invoices": [
    { "invoice_id": "INV-001", "amount": 1000.00, "date": "2025-01-10", "description": "Monthly service fee" },
    { "invoice_id": "INV-002", "amount": 2500.00, "date": "2025-01-15", "description": "Equipment rental" },
    { "invoice_id": "INV-003", "amount": 750.00, "date": "2025-01-20", "description": "Consulting hours" }
  ],
  "payments": [
    { "invoice_id": "INV-001", "amount": 1000.00, "date": "2025-01-12", "description": "Monthly service" },
    { "invoice_id": "INV-002", "amount": 2475.00, "date": "2025-01-15", "description": "Equip rental Jan" }
  ]
}

Matcher configuration:

json
{
  "type": "matcher",
  "properties": {
    "left": "@input.invoices",
    "right": "@input.payments",
    "matchOn": ["invoice_id"],
    "tolerance": 50,
    "dateWindowDays": 3,
    "fuzzyThreshold": 80,
    "descriptionKey": "description",
    "outputMatched": "reconciled",
    "outputUnmatchedLeft": "exceptions"
  }
}

Results:

  • @reconciled: INV-001 (exact match), INV-002 (amount difference $25 within tolerance, descriptions 80%+ similar)
  • @exceptions: INV-003 (no matching payment found)

Using Uploaded Documents

Instead of embedding datasets in the execution payload, upload files to Document Storage and reference them with the doc: prefix:

json
{
  "type": "matcher",
  "properties": {
    "left": "doc:doc_a1b2c3d4e5f6",
    "right": "doc:doc_x7y8z9w0v1u2",
    "matchOn": ["invoice_id"],
    "tolerance": 50,
    "outputMatched": "matched",
    "outputUnmatchedLeft": "exceptions"
  }
}

CSV files resolve to Array<Object> with header rows as keys. JSON files resolve as-is. Pin a specific version with doc:doc_xxx@2 for audit reproducibility.


Working with larger datasets

The matcher keeps the same input and output contract across supported dataset sizes. For large files, upload CSV or JSON data and pass doc: references instead of placing every row in the workflow execution request.

Matcher as a front-end step. Many operational workflows put a matcher near the start. Matched records continue through deterministic processing. Exceptions can move to an agent or a person for review.

→ Next: [Loop](/primitives/loop)