Skip to main content
Real-world reconciliation often involves transactions that don’t match 1:1. A single payment may cover multiple invoices, or several deposits may consolidate into one bank entry. Matcher handles these complex scenarios through split and aggregate matching.

Overview


Matcher lets you four matching patterns:
PatternDescriptionExample
1:1One source to one targetSingle invoice payment
1:NOne source to many targetsBulk payment covering invoices
N:1Many sources to one targetDeposits consolidated at bank
N:MMany sources to many targetsComplex netting

Enabling complex matching


Context configuration

Enable split and aggregate matching at the context level:
cURL
curl -X PATCH "https://api.matcher.example.com/v1/contexts/ctx_abc123" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "settings": {
     "matching": {
       "allow_split": true,
       "allow_aggregate": true,
       "allow_many_to_many": false,
       "max_transactions_per_group": 20,
       "require_full_allocation": true
     }
   }
 }'

Response

{
  "id": "ctx_abc123",
  "name": "Payment Reconciliation",
  "settings": {
    "matching": {
      "allow_split": true,
      "allow_aggregate": true,
      "allow_many_to_many": false,
      "max_transactions_per_group": 20,
      "require_full_allocation": true
    }
  }
}

Matching settings

SettingTypeDefaultDescription
allow_splitBooleanfalseEnable 1:N matching
allow_aggregateBooleanfalseEnable N:1 matching
allow_many_to_manyBooleanfalseEnable N:M matching
max_transactions_per_groupInteger10Maximum transactions in a group
require_full_allocationBooleantrueRequire 100% allocation
partial_match_thresholdDecimal0.95Minimum allocation for partial (95%)

1:N split matching


One source transaction matches multiple target transactions.

Common use cases

  • Bulk payment: Single wire covering multiple invoices
  • Payroll: One bank debit for multiple salary payments
  • Settlement: One gateway payout for multiple orders

Example: bulk invoice payment

Source (Bank Statement):
IDAmountReference
bank_001$15,000.00BULK-PAY-2024-001
Targets (Ledger Entries):
IDAmountInvoice
inv_001$5,000.00INV-2024-001
inv_002$7,500.00INV-2024-002
inv_003$2,500.00INV-2024-003
Result: 1:3 match with full allocation

API: create split match

cURL
curl -X POST "https://api.matcher.example.com/v1/contexts/ctx_abc123/matches" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "type": "SPLIT",
   "source_transactions": [
     {
       "transaction_id": "bank_001",
       "amount": 15000.0
     }
   ],
   "target_transactions": [
     {
       "transaction_id": "inv_001",
       "allocated_amount": 5000.0
     },
     {
       "transaction_id": "inv_002",
       "allocated_amount": 7500.0
     },
     {
       "transaction_id": "inv_003",
       "allocated_amount": 2500.0
     }
   ]
 }'

Response

{
  "match_id": "match_split_001",
  "type": "SPLIT",
  "status": "PROPOSED",
  "confidence": 95,
  "source": {
    "transaction_count": 1,
    "total_amount": 15000.0,
    "allocated_amount": 15000.0,
    "allocation_percent": 100.0
  },
  "targets": {
    "transaction_count": 3,
    "total_amount": 15000.0,
    "allocated_amount": 15000.0,
    "allocation_percent": 100.0
  },
  "residual": 0.0,
  "transactions": [
    {
      "id": "bank_001",
      "role": "SOURCE",
      "amount": 15000.0,
      "allocated": 15000.0
    },
    {
      "id": "inv_001",
      "role": "TARGET",
      "amount": 5000.0,
      "allocated": 5000.0
    },
    {
      "id": "inv_002",
      "role": "TARGET",
      "amount": 7500.0,
      "allocated": 7500.0
    },
    {
      "id": "inv_003",
      "role": "TARGET",
      "amount": 2500.0,
      "allocated": 2500.0
    }
  ],
  "created_at": "2024-01-20T10:00:00Z"
}

N:1 aggregate matching


Multiple source transactions match one target transaction.

Common use cases

  • Bank deposits: Multiple checks deposited as one credit
  • Card settlements: Daily batch of transactions as one deposit
  • Cash consolidation: Multiple register receipts to one deposit

Example: consolidated deposit

Sources (Point of Sale):
IDAmountRegister
pos_001$1,250.00REG-01
pos_002$980.00REG-02
pos_003$1,770.00REG-03
Target (Bank Statement):
IDAmountReference
bank_002$4,000.00DEPOSIT-20240120
Result: 3:1 match with full allocation

API: create aggregate match

cURL
curl -X POST "https://api.matcher.example.com/v1/contexts/ctx_abc123/matches" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "type": "AGGREGATE",
   "source_transactions": [
     {
       "transaction_id": "pos_001",
       "allocated_amount": 1250.0
     },
     {
       "transaction_id": "pos_002",
       "allocated_amount": 980.0
     },
     {
       "transaction_id": "pos_003",
       "allocated_amount": 1770.0
     }
   ],
   "target_transactions": [
     {
       "transaction_id": "bank_002",
       "amount": 4000.0
     }
   ]
 }'

Response

{
  "match_id": "match_agg_001",
  "type": "AGGREGATE",
  "status": "PROPOSED",
  "confidence": 92,
  "source": {
    "transaction_count": 3,
    "total_amount": 4000.0,
    "allocated_amount": 4000.0,
    "allocation_percent": 100.0
  },
  "targets": {
    "transaction_count": 1,
    "total_amount": 4000.0,
    "allocated_amount": 4000.0,
    "allocation_percent": 100.0
  },
  "residual": 0.0
}

N:M many-to-many matching


Multiple source transactions match multiple target transactions. This is the most complex pattern and requires the graph solver.

Common use cases

  • Intercompany netting: Multiple invoices netted against multiple payments
  • Trade settlements: Complex clearing with partial fills
  • Revenue recognition: Multiple deliveries against multiple advances

Example: intercompany netting

Sources (Company A Payables):
IDAmountReference
pay_001$10,000.00IC-PAY-001
pay_002$8,000.00IC-PAY-002
Targets (Company A Receivables):
IDAmountReference
rec_001$12,000.00IC-REC-001
rec_002$6,000.00IC-REC-002
Result: 2:2 match, $18,000 total matched

API: create N:M match

curl -X POST "https://api.matcher.example.com/v1/contexts/ctx_abc123/matches" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "type": "MANY_TO_MANY",
   "source_transactions": [
     {
       "transaction_id": "pay_001",
       "allocated_amount": 10000.0
     },
     {
       "transaction_id": "pay_002",
       "allocated_amount": 8000.0
     }
   ],
   "target_transactions": [
     {
       "transaction_id": "rec_001",
       "allocated_amount": 12000.0
     },
     {
       "transaction_id": "rec_002",
       "allocated_amount": 6000.0
     }
   ]
 }'

Allocation tracking


Matcher tracks how much of each transaction has been allocated to matches.

Transaction allocation status

StatusDescription
UNALLOCATEDNo allocation (0%)
PARTIALLY_ALLOCATEDSome allocation (1-99%)
FULLY_ALLOCATEDComplete allocation (100%)

View transaction allocation

curl -X GET "https://api.matcher.example.com/v1/transactions/bank_001/allocation" \
 -H "Authorization: Bearer $TOKEN"

Response

{
  "transaction_id": "bank_001",
  "total_amount": 15000.0,
  "currency": "USD",
  "allocation_status": "FULLY_ALLOCATED",
  "allocations": [
    {
      "match_id": "match_split_001",
      "allocated_amount": 15000.0,
      "allocation_percent": 100.0,
      "status": "CONFIRMED"
    }
  ],
  "available_amount": 0.0,
  "available_percent": 0.0
}

Partial allocation example

Transaction with remaining unallocated amount:
{
  "transaction_id": "bank_003",
  "total_amount": 10000.0,
  "allocation_status": "PARTIALLY_ALLOCATED",
  "allocations": [
    {
      "match_id": "match_002",
      "allocated_amount": 7500.0,
      "allocation_percent": 75.0,
      "status": "CONFIRMED"
    }
  ],
  "available_amount": 2500.0,
  "available_percent": 25.0
}

Partial matches


By default, Matcher requires full allocation. Enable partial matching for scenarios where complete matching isn’t possible.

Enable partial matching

{
  "settings": {
    "matching": {
      "require_full_allocation": false,
      "partial_match_threshold": 0.9,
      "partial_match_behavior": "propose_with_residual"
    }
  }
}

Partial match behaviors

BehaviorDescription
propose_with_residualCreate match, track residual
create_exceptionCreate exception for unmatched portion
hold_for_reviewHold until additional transactions arrive

Partial match response

{
  "match_id": "match_partial_001",
  "type": "SPLIT",
  "status": "PROPOSED",
  "confidence": 78,
  "source": {
    "total_amount": 10000.0,
    "allocated_amount": 9500.0,
    "allocation_percent": 95.0
  },
  "targets": {
    "total_amount": 9500.0,
    "allocated_amount": 9500.0,
    "allocation_percent": 100.0
  },
  "residual": {
    "amount": 500.0,
    "percent": 5.0,
    "status": "UNALLOCATED",
    "exception_id": "exc_residual_001"
  }
}

Graph solver


For complex N:M scenarios, Matcher uses a bipartite graph solver to find optimal matches.

How it works

  1. Build Graph: Create bipartite graph with sources and targets as nodes
  2. Edge Weights: Calculate match confidence as edge weights
  3. Solve: Find maximum weighted matching
  4. Allocate: Distribute amounts across matched pairs

Graph solver configuration

{
  "settings": {
    "graph_solver": {
      "enabled": true,
      "algorithm": "hungarian",
      "max_nodes": 100,
      "min_edge_confidence": 50,
      "optimization_goal": "maximize_allocation"
    }
  }
}

Solver algorithms

AlgorithmBest ForComplexity
hungarianSmall graphs (<100 nodes)O(n³)
auctionMedium graphs (<1000 nodes)O(n² log n)
greedyLarge graphs (>1000 nodes)O(n log n)

Run graph solver

cURL
curl -X POST "https://api.matcher.example.com/v1/contexts/ctx_abc123/solve" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "source_transactions": [
     "pay_001",
     "pay_002",
     "pay_003"
   ],
   "target_transactions": [
     "rec_001",
     "rec_002",
     "rec_003"
   ],
   "options": {
     "algorithm": "hungarian",
     "allow_partial": true,
     "min_confidence": 60
   }
 }'

Response

{
  "solver_id": "solve_001",
  "status": "COMPLETED",
  "algorithm": "hungarian",
  "execution_time_ms": 45,
  "results": {
    "proposed_matches": [
      {
        "match_id": "match_proposed_001",
        "type": "MANY_TO_MANY",
        "confidence": 88,
        "allocations": [
          {
            "source": "pay_001",
            "target": "rec_001",
            "amount": 10000.0
          },
          {
            "source": "pay_002",
            "target": "rec_001",
            "amount": 2000.0
          },
          {
            "source": "pay_002",
            "target": "rec_002",
            "amount": 6000.0
          }
        ]
      }
    ],
    "unmatched_sources": [],
    "unmatched_targets": [],
    "total_matched": 18000.0,
    "match_rate": 100.0
  }
}

Tolerance rules


Configure how tolerance applies to split and aggregate matches.

Tolerance modes

ModeDescriptionUse Case
residualApply to final unmatched amountDefault, most common
per_itemApply to each transactionStrict matching
totalApply to sum of all transactionsFlexible matching

Configure tolerance mode

{
  "settings": {
    "matching": {
      "tolerance_mode": "residual",
      "tolerance_percent": 1.0,
      "tolerance_absolute": 50.0
    }
  }
}

Tolerance examples

Residual Mode (Default):
  • Source: $10,000.00
  • Targets: $9,980.00 total
  • Residual: $20.00 (0.2%)
  • Tolerance: 1% = $100.00
  • Result: Match (residual within tolerance)
Per-Item Mode:
  • Source: $10,000.00
  • Target 1: $5,010.00 (0.2% variance)
  • Target 2: $4,980.00 (0.4% variance)
  • Each within 1% tolerance
  • Result: Match (all items within tolerance)

Auto-discovery


Matcher can automatically discover split and aggregate patterns.

Enable auto-discovery

{
  "settings": {
    "auto_discovery": {
      "enabled": true,
      "max_group_size": 10,
      "time_window_days": 7,
      "min_confidence": 75
    }
  }
}

Discovery heuristics

HeuristicDescription
Amount summingFind combinations that sum to target
Reference patternsMatch based on common references
Temporal clusteringGroup transactions by date proximity
Counterparty groupingGroup by same counterparty

View discovered groups

curl -X GET "https://api.matcher.example.com/v1/contexts/ctx_abc123/discoveries" \
 -H "Authorization: Bearer $TOKEN"

Response

{
  "discoveries": [
    {
      "id": "disc_001",
      "type": "AGGREGATE",
      "confidence": 85,
      "heuristic": "amount_summing",
      "source_transactions": [
        "pos_001",
        "pos_002",
        "pos_003"
      ],
      "target_transactions": [
        "bank_002"
      ],
      "source_total": 4000.0,
      "target_total": 4000.0,
      "residual": 0.0
    }
  ]
}

Reporting


Group match summary

curl -X GET "https://api.matcher.example.com/v1/contexts/ctx_abc123/reports/group-matches" \
 -H "Authorization: Bearer $TOKEN" \
 -G \
 -d "date_from=2024-01-01" \
 -d "date_to=2024-01-31"
{
  "period": {
    "from": "2024-01-01",
    "to": "2024-01-31"
  },
  "summary": {
    "total_matches": 1250,
    "by_type": {
      "one_to_one": 980,
      "split": 150,
      "aggregate": 100,
      "many_to_many": 20
    },
    "avg_group_size": {
      "split": 3.2,
      "aggregate": 4.5,
      "many_to_many": 6.1
    },
    "partial_matches": 45,
    "total_residual": 12500.0
  }
}

Best practices


Many-to-many matching is complex. Start with simpler patterns and enable N:M only when necessary.
Large groups are harder to verify. Limit group size based on your audit requirements.
Enable auto-discovery to find patterns, but require human review before confirming complex matches.
Track residual amounts over time. Growing residuals may indicate systematic matching issues.
When multiple allocation strategies are possible, document why specific allocations were chosen.
Before enabling graph solver in production, test with representative data to verify results.

Next steps