AP Invoice Reconciliation

Invoice-to-payment matching with an agent for the exceptions and a person for the write-offs. The universal starting point: every company has this problem, most solve it with spreadsheets. When purchase orders, receipts, and invoices must all agree, swap the first step for the three-way match shown under Customization Notes.

Runs as: CTR-11 in the Process Studio catalog, with the three-way match, a payment-release approval, and an idempotent authorization record.

What Gets Automated

PO-to-invoice matching on PO number, amount tolerance, and date windows. Fuzzy vendor name matching catches data entry inconsistencies. Agent investigates discrepancies: wrong amounts, missing POs, duplicate invoices, partial payments.

What Humans Still Own

Write-off approvals above threshold. Vendor dispute resolution. New vendor onboarding decisions.

Pipeline

flowchart TD A[Input: Invoices + Payments + POs] --> B[Matcher] B -->|~80% matched| C[Auto-Reconcile] B -->|~20% exceptions| D{Exception Type} D -->|Amount discrepancy| E[ReAct Agent: Evidence Brief] D -->|Missing PO| E D -->|Duplicate suspected| E E --> G[Human Approval] G -->|Approved| H[Process + Log] G -->|Rejected| I[Run Ends; Case Rows Remain] C --> J[Custom Table: Reconciliation Log] H --> J style B fill:#e8a84c,color:#09090b,stroke:none style E fill:#4ade80,color:#09090b,stroke:none style G fill:#60a5fa,color:#09090b,stroke:none

Workflow Definition

json
{
  "name": "ap_invoice_reconciliation",
  "definition": {
    "actions": [
      {
        "type": "matcher",
        "properties": {
          "left": "@input.invoices",
          "right": "@input.payments",
          "matchOn": ["po_number", "vendor_id"],
          "tolerance": 50,
          "dateWindowDays": 5,
          "fuzzyThreshold": 85,
          "descriptionKey": "vendor_name",
          "outputMatched": "reconciled",
          "outputUnmatchedLeft": "unmatched_invoices",
          "outputUnmatchedRight": "unmatched_payments"
        }
      },
      {
        "type": "loop",
        "filter": {
          "condition": {
            "greaterThan": [{ "length": "@unmatched_invoices" }, 0]
          }
        },
        "properties": {
          "mode": "foreach",
          "items_path": "@unmatched_invoices",
          "item_variable_name": "exception",
          "actions_to_execute": [
            {
              "type": "custom-table",
              "properties": {
                "table": "ap_reconciliation_cases",
                "operation": "upsert",
                "key_fields": ["run_id", "invoice_id"],
                "keys": ["run_id", "invoice_id", "vendor_name", "amount", "status"],
                "values": ["@__run_id", "@exception.invoice_id", "@exception.vendor_name", "@exception.amount", "pending_review"]
              }
            }
          ],
          "max_concurrency": 5,
          "failure_strategy": "fail_fast",
          "collect_results": false,
          "result_key": "exception_case_writes"
        }
      },
      {
        "type": "loop",
        "filter": {
          "condition": {
            "greaterThan": [{ "length": "@unmatched_invoices" }, 0]
          }
        },
        "properties": {
          "mode": "react",
          "objective": "Read the current run's AP reconciliation cases. Investigate each invoice using the declared tools. Determine whether the likely cause is timing, an amount discrepancy, a duplicate, a missing purchase order, or a data-entry issue. Recommend a next action, but do not approve a write-off.",
          "tools": [
            { "type": "action", "name": "read_ap_reconciliation_cases" },
            { "type": "action", "name": "lookup_purchase_order" },
            { "type": "action", "name": "check_payment_history" },
            { "type": "action", "name": "search_duplicate_invoices" }
          ],
          "max_iterations": 12,
          "on_stuck": {
            "iterations": 3,
            "action": "retry_with_hint",
            "hint": "If the evidence is insufficient, complete with a low-confidence recommendation for human review."
          },
          "result_key": "all_investigations"
        }
      },
      {
        "type": "PbotApproval",
        "filter": {
          "condition": {
            "greaterThan": [{ "length": "@unmatched_invoices" }, 0]
          }
        },
        "properties": {
          "comment": "{{unmatched_invoices.length}} exceptions investigated. Review AI findings and approve recommended actions.",
          "request_payload": {
            "reconciled_count": "@reconciled.length",
            "exception_count": "@unmatched_invoices.length",
            "investigations": "@all_investigations",
            "unmatched_payments": "@unmatched_payments"
          }
        }
      },
      {
        "type": "custom-table",
        "properties": {
          "table": "reconciliation_log",
          "operation": "write",
          "keys": ["run_id", "run_date"],
          "values": ["@__run_id", "@now"],
          "fields": {
            "total_invoices": "@input.invoices.length",
            "auto_reconciled": "@reconciled.length",
            "exceptions_investigated": "@unmatched_invoices.length",
            "unmatched_payments": "@unmatched_payments.length",
            "status": "completed"
          }
        }
      }
    ]
  }
}

Required Registered Actions

Action Kind Purpose
lookup_purchase_order http Query ERP for PO details by PO number
check_payment_history db Search payment records for a vendor within date range
search_duplicate_invoices db Check for invoices with matching amounts and close dates
read_ap_reconciliation_cases custom-table Read the current run's persisted exception rows

The workflow writes unmatched invoices to ap_reconciliation_cases, keyed by run_id and invoice_id. Bind read_ap_reconciliation_cases to that table with operation: "read" and where: { "run_id": "@__run_id" }. This gives the batch agent a fixed, read-only view of the current exceptions. For large batches or one agent per invoice, launch bounded agent runs from your application with one record or doc: reference in each objective.

Customization Notes

Tolerance. The default $50 tolerance handles typical rounding, fee, and settlement differences. Lower to $5 for high-precision environments; raise to $250 if partial payments are common.

Date window. 5 days covers standard payment processing lag. Extend to 15-30 days for international vendors with longer settlement cycles.

Fuzzy threshold. 85 catches minor name variations ("Acme Corp" vs "ACME Corporation"). Lower to 75 if vendor names are highly inconsistent across systems.

Agent iterations. 8 iterations gives the agent room to check multiple data sources. Reduce to 5 for simpler investigations; increase to 12 if your exception patterns are complex.

Three legs. Replace the matcher step with an N-way match when a purchase order, its receipts, and its invoices must reconcile as one group. Configured amounts are summed within each group, so partial receipts and partial invoices settle against one order line:

json
{
  "type": "nway_match",
  "properties": {
    "sets": [
      { "key": "purchase_order", "records": "@input.purchase_order_rows" },
      { "key": "receipt", "records": "@input.receipt_rows" },
      { "key": "invoice", "records": "@input.invoice_rows" }
    ],
    "matchOn": ["authorization_line_id"],
    "tolerances": {
      "line_amount": { "type": "currency", "value": 0.01 },
      "quantity": { "type": "abs", "value": 0 }
    },
    "exceptionTable": "nway_match_exceptions"
  }
}

The step writes @matchGroups, @matchOrphans, @matchGroupCounts, and @matchGroupExceptions. Point the exception loop at @matchGroupExceptions and keep the agent, approval, and audit steps as they are.