Skip to main content
Exceptions are transactions that Matcher can’t reconcile automatically. This guide shows how to review exceptions, prioritize work based on severity, and resolve items with the right level of documentation.

What is an exception?


An exception is created when a transaction from one source has no valid counterpart in another source. Common causes include:
  • No candidate found: No transaction in the other source meets the active rule criteria.
  • Below confidence threshold: Candidates exist, but score below the minimum confidence (default: 60).
  • Duplicate rejection: A previous match was rejected and no alternative candidate remains.
  • Source imbalance: One source contains transactions that are missing from the other.

Exception lifecycle


Exceptions move through a simple workflow:
  • When Matcher can’t reconcile a transaction, it creates an exception in OPEN status.
  • From there, an analyst picks it up for investigation (IN_REVIEW). Most exceptions resolve directly after investigation.
  • If the resolution requires approval—like a write-off above a certain threshold—the exception moves to ESCALATED.
  • A supervisor can then approve and close it, or return it with guidance for further review.
Matcher Exception Lifecycle

Status definitions

StatusDescriptionWho Can Transition
OPENNew exception waiting for assignmentSystem, Analyst
IN_REVIEWAssigned to a reviewer for investigationAnalyst
ESCALATEDRequires approval before resolutionAnalyst, Supervisor
RESOLVEDClosed with an auditable resolutionAnalyst, Supervisor

Exception severity


Matcher classifies exceptions by severity so you can work the queue in the right order.
SeverityCriteriaSLA
CriticalAmount >= 100,000 OR Age >= 5 days OR Regulatory source flagResolve within 24 hours
HighAmount >= 10,000 OR Age >= 3 daysResolve within 48 hours
MediumAmount >= 1,000 OR Age >= 1 dayResolve within 72 hours
LowAll othersResolve within 5 business days

Severity escalation

Severity can increase automatically as an exception ages:
Day 0: Low → remains Low
Day 1: Low → Medium (if amount < 1,000)
Day 3: Medium → High (if amount < 10,000)
Day 5: High → Critical (all unresolved)

Viewing exceptions


List exceptions for a context

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

Response

{
  "exceptions": [
    {
      "id": "exc_001",
      "status": "OPEN",
      "severity": "HIGH",
      "transaction_id": "txn_bank_999",
      "source_id": "src_bank456",
      "source_name": "Chase Bank",
      "amount": 15000.0,
      "currency": "USD",
      "date": "2024-01-15",
      "reference": "Wire transfer #7890",
      "reason": "NO_MATCH_FOUND",
      "created_at": "2024-01-20T10:30:00Z",
      "age_days": 5
    },
    {
      "id": "exc_002",
      "status": "IN_REVIEW",
      "severity": "MEDIUM",
      "transaction_id": "txn_ledger_888",
      "source_id": "src_ledger789",
      "source_name": "Main Ledger",
      "amount": 2500.0,
      "currency": "USD",
      "date": "2024-01-18",
      "reference": "Invoice #4567",
      "reason": "BELOW_CONFIDENCE",
      "assigned_to": "user_123",
      "created_at": "2024-01-20T10:30:00Z",
      "age_days": 2
    }
  ],
  "summary": {
    "total": 45,
    "by_severity": {
      "CRITICAL": 2,
      "HIGH": 8,
      "MEDIUM": 15,
      "LOW": 20
    },
    "by_status": {
      "OPEN": 30,
      "IN_REVIEW": 12,
      "ESCALATED": 3
    }
  }
}

Get exception details

cURL
curl -X GET "https://api.matcher.example.com/v1/exceptions/exc_001" \
 -H "Authorization: Bearer $TOKEN"
Response
{
  "id": "exc_001",
  "status": "OPEN",
  "severity": "HIGH",
  "transaction": {
    "id": "txn_bank_999",
    "source_id": "src_bank456",
    "source_name": "Chase Bank",
    "external_id": "BANK-2024-999",
    "amount": 15000.0,
    "currency": "USD",
    "date": "2024-01-15",
    "reference": "Wire transfer #7890",
    "counterparty": "Acme Corporation",
    "type": "credit"
  },
  "reason": "NO_MATCH_FOUND",
  "reason_details": "No transactions in Main Ledger within date tolerance (±3 days) match the amount",
  "candidates": [
    {
      "transaction_id": "txn_ledger_777",
      "amount": 15250.0,
      "currency": "USD",
      "date": "2024-01-14",
      "reference": "Invoice #1234",
      "confidence": 55,
      "rejection_reason": "Below 60% threshold"
    }
  ],
  "created_at": "2024-01-20T10:30:00Z",
  "age_days": 5,
  "sla_due_at": "2024-01-21T10:30:00Z"
}

Filter exceptions

# By severity
curl -X GET "https://api.matcher.example.com/v1/contexts/ctx_abc123/exceptions?severity=CRITICAL" \
 -H "Authorization: Bearer $TOKEN"

# By status
curl -X GET "https://api.matcher.example.com/v1/contexts/ctx_abc123/exceptions?status=OPEN" \
 -H "Authorization: Bearer $TOKEN"

# By assigned user
curl -X GET "https://api.matcher.example.com/v1/contexts/ctx_abc123/exceptions?assigned_to=user_123" \
 -H "Authorization: Bearer $TOKEN"

# By age (older than n days)
curl -X GET "https://api.matcher.example.com/v1/contexts/ctx_abc123/exceptions?age_min=3" \
 -H "Authorization: Bearer $TOKEN"

# Combined: critical/high that are overdue
curl -X GET "https://api.matcher.example.com/v1/contexts/ctx_abc123/exceptions?severity=CRITICAL,HIGH&sla_status=OVERDUE" \
 -H "Authorization: Bearer $TOKEN"

Resolution methods


You can resolve an exception in four ways.

1. Force match

Manually link transactions when you’ve confirmed they belong together, but the system couldn’t match them.
cURL
curl -X POST "https://api.matcher.example.com/v1/exceptions/exc_001/force-match" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "target_transaction_id": "txn_ledger_777",
   "reason": "Amount difference is documented bank fee",
   "notes": "Verified with bank statement showing $250 wire fee deducted"
 }'

Response

{
  "exception_id": "exc_001",
  "status": "RESOLVED",
  "resolution": {
    "type": "FORCE_MATCH",
    "match_id": "match_forced_001",
    "transactions": [
      "txn_bank_999",
      "txn_ledger_777"
    ],
    "resolved_by": "user_123",
    "resolved_at": "2024-01-20T14:30:00Z",
    "notes": "Verified with bank statement showing $250 wire fee deducted"
  }
}
Use Force Match when:
  • The correct counterpart exists, but variances blocked automatic matching.
  • You can clearly explain and document the rationale.
  • The variance is expected (fees, timing, rounding).

2. Create adjustment

Create an adjusting entry to account for a variance or to balance an unmatched item.
cURL
curl -X POST "https://api.matcher.example.com/v1/exceptions/exc_001/adjust" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "adjustment_type": "BANK_FEE",
   "amount": 250.0,
   "currency": "USD",
   "description": "Wire transfer fee charged by bank",
   "target_transaction_id": "txn_ledger_777",
   "notes": "Fee per bank schedule, account #4567"
 }'

Response

{
  "exception_id": "exc_001",
  "status": "RESOLVED",
  "resolution": {
    "type": "ADJUSTMENT",
    "adjustment_id": "adj_001",
    "adjustment_type": "BANK_FEE",
    "amount": 250.0,
    "currency": "USD",
    "match_id": "match_adj_001",
    "transactions": [
      "txn_bank_999",
      "txn_ledger_777",
      "adj_001"
    ],
    "resolved_by": "user_123",
    "resolved_at": "2024-01-20T14:30:00Z"
  }
}
Common adjustment types:
TypeUse Case
BANK_FEEBank charges not recorded in the ledger
FX_VARIANCECurrency conversion differences
TIMING_DIFFERENCESettlement timing adjustments
ROUNDINGSmall rounding differences
CORRECTIONError corrections
OTHEROther documented variances

3. Write-off

Write off a transaction that has no valid counterpart. This should be rare and typically requires approval.
cURL
curl -X POST "https://api.matcher.example.com/v1/exceptions/exc_002/write-off" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "reason": "DUPLICATE_ENTRY",
   "notes": "Duplicate posting in ledger - original entry is txn_ledger_500",
   "reference_document": "IT-TICKET-2024-001"
 }'

Response

{
  "exception_id": "exc_002",
  "status": "RESOLVED",
  "resolution": {
    "type": "WRITE_OFF",
    "reason": "DUPLICATE_ENTRY",
    "resolved_by": "user_123",
    "resolved_at": "2024-01-20T14:45:00Z",
    "notes": "Duplicate posting in ledger - original entry is txn_ledger_500",
    "reference_document": "IT-TICKET-2024-001"
  }
}
Write-off reasons:
ReasonDescription
DUPLICATE_ENTRYTransaction was entered twice
CANCELLEDTransaction was reversed or cancelled
NOT_APPLICABLEDoesn’t belong in this reconciliation scope
BELOW_THRESHOLDAmount is below the investigation threshold
APPROVED_VARIANCEVariance approved by management

4. Split transaction

Use split when one transaction should match multiple counterparts.
cURL
curl -X POST "https://api.matcher.example.com/v1/exceptions/exc_003/split" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "splits": [
     {
       "amount": 5000.0,
       "target_transaction_id": "txn_ledger_100",
       "notes": "Invoice #1001"
     },
     {
       "amount": 3000.0,
       "target_transaction_id": "txn_ledger_101",
       "notes": "Invoice #1002"
     },
     {
       "amount": 2000.0,
       "target_transaction_id": "txn_ledger_102",
       "notes": "Invoice #1003"
     }
   ],
   "notes": "Combined payment for three invoices"
 }'

Response

{
  "exception_id": "exc_003",
  "status": "RESOLVED",
  "resolution": {
    "type": "SPLIT",
    "original_amount": 10000.0,
    "split_count": 3,
    "matches": [
      {
        "match_id": "match_split_001",
        "amount": 5000.0
      },
      {
        "match_id": "match_split_002",
        "amount": 3000.0
      },
      {
        "match_id": "match_split_003",
        "amount": 2000.0
      }
    ],
    "resolved_by": "user_123",
    "resolved_at": "2024-01-20T15:00:00Z"
  }
}

Exception assignment


Assign exceptions to the right owner before you start resolving.

Assign single exception

curl -X POST "https://api.matcher.example.com/v1/exceptions/exc_001/assign" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "assigned_to": "user_456",
   "notes": "Please review - appears to be timing difference"
 }'

Bulk assignment

curl -X POST "https://api.matcher.example.com/v1/exceptions/bulk-assign" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "filter": {
     "context_id": "ctx_abc123",
     "severity": [
       "HIGH",
       "CRITICAL"
     ],
     "status": "OPEN"
   },
   "assigned_to": "user_456"
 }'

Auto-assignment rules

Automate routing so exceptions land with the right team by default:
curl -X POST "https://api.matcher.example.com/v1/contexts/ctx_abc123/assignment-rules" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "rules": [
     {
       "name": "High-value to senior analyst",
       "condition": {
         "amount_min": 50000
       },
       "assign_to": "user_senior"
     },
     {
       "name": "Bank source to treasury",
       "condition": {
         "source_id": "src_bank456"
       },
       "assign_to": "user_treasury"
     },
     {
       "name": "Default assignment",
       "condition": {},
       "assign_to": "user_general"
     }
   ]
 }'

Escalation workflow


Escalate when the resolution needs approval.

Escalate an exception

cURL
curl -X POST "https://api.matcher.example.com/v1/exceptions/exc_001/escalate" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "reason": "Write-off over $10,000 requires manager approval",
   "escalate_to": "user_manager",
   "proposed_action": "WRITE_OFF",
   "notes": "Cannot locate counterpart after investigation"
 }'

Response

{
  "exception_id": "exc_001",
  "status": "ESCALATED",
  "escalation": {
    "escalated_by": "user_123",
    "escalated_to": "user_manager",
    "escalated_at": "2024-01-20T15:30:00Z",
    "reason": "Write-off over $10,000 requires manager approval",
    "proposed_action": "WRITE_OFF"
  }
}

Approve or return escalation

Approve:
curl -X POST "https://api.matcher.example.com/v1/exceptions/exc_001/approve" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "notes": "Approved - documentation sufficient"
 }'
Return for more information:
curl -X POST "https://api.matcher.example.com/v1/exceptions/exc_001/return" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "notes": "Please provide bank confirmation before write-off"
 }'

Audit requirements


Every resolution creates an audit record. Some resolution types require stronger evidence and approvals.

Required documentation by resolution type

ResolutionRequired FieldsApproval Needed
Force Matchreason, notesNo (unless amount > threshold)
Adjustmentadjustment_type, amount, descriptionIf amount > $1,000
Write-Offreason, notes, reference_documentAlways
Splitsplits[] with amounts and targetsNo

View audit trail

curl -X GET "https://api.matcher.example.com/v1/exceptions/exc_001/audit" \
 -H "Authorization: Bearer $TOKEN"

Response

{
  "events": [
    {
      "timestamp": "2024-01-20T10:30:00Z",
      "action": "CREATED",
      "user": "system",
      "details": {
        "reason": "NO_MATCH_FOUND",
        "severity": "HIGH"
      }
    },
    {
      "timestamp": "2024-01-20T12:00:00Z",
      "action": "ASSIGNED",
      "user": "user_admin",
      "details": {
        "assigned_to": "user_123"
      }
    },
    {
      "timestamp": "2024-01-20T14:30:00Z",
      "action": "ESCALATED",
      "user": "user_123",
      "details": {
        "escalate_to": "user_manager",
        "reason": "Approval required"
      }
    },
    {
      "timestamp": "2024-01-20T15:00:00Z",
      "action": "APPROVED",
      "user": "user_manager",
      "details": {
        "notes": "Approved per policy"
      }
    },
    {
      "timestamp": "2024-01-20T15:01:00Z",
      "action": "RESOLVED",
      "user": "user_123",
      "details": {
        "type": "WRITE_OFF",
        "reason": "APPROVED_VARIANCE"
      }
    }
  ]
}

Exception resolution workflow


Use this flow to keep reviews consistent and audit-friendly.
1

Triage

Review the queue by severity and SLA. Start with Critical and High.
curl -X GET "https://api.matcher.example.com/v1/contexts/ctx_abc123/exceptions?status=OPEN&severity=CRITICAL,HIGH" \
-H "Authorization: Bearer $TOKEN"
2

Investigate

Use the exception payload to understand what failed and what candidates exist.
  • Read reason_details to see why matching failed.
  • Review candidates for near matches below threshold.
  • Look for patterns (same counterparty, recurring reference formats).
3

Resolve

Pick the resolution that best reflects reality and policy.
  • Force Match: You found the correct counterpart.
  • Adjust: You need an adjusting entry for variance.
  • Split: One transaction maps to multiple counterparts.
  • Write-Off: No counterpart exists and policy allows it (approval required).
4

Document

Capture enough detail for someone else to replay your decision later:
  • What you checked
  • What you concluded
  • Links or IDs for supporting evidence
5

Escalate if Needed

If the action requires approval, escalate with a clear recommendation and supporting notes.

Best practices


Start with Critical and High items. They carry the highest risk and the tightest deadlines.
Notes aren’t optional. Treat them as part of the resolution:
  • What you checked
  • Why this resolution is correct
  • Any ticket IDs, statements, or confirmations
Repeating exceptions usually point to configuration issues:
  • Same counterparty → Normalize names or mapping
  • Same date window → Validate ingestion completeness
  • Same source → Review field mapping and sign conventions
If you force-match regularly, your rules or tolerances need attention.
Use assignment rules to reduce triage time and keep ownership clear.
If you write off often, revisit thresholds, context scope, or upstream data quality.

Next steps