Invoice-to-Pay AP Automation: ERP Integration Playbook
How do you integrate ERP with OCR/AI invoice capture and payment gateways for AP automation?
TL;DR
- Bottom line: End-to-end AP automation integrates OCR/AI invoice capture, ERP 3-way matching, approval workflows, and payment gateways — reducing per-invoice cost from $15-25 (manual) to $2-5 (automated) with 70-87% cost savings.
- Key limit: OCR accuracy on header fields reaches 97%+ but line-item extraction on non-standard formats still requires human review for 10-20% of invoices.
- Watch out for: Skipping the 3-way match validation step to "speed things up" — this is the #1 source of duplicate payments and overpayments in AP automation.
- Best for: Organizations processing 500+ invoices/month with established PO-based purchasing and ERP with open API access.
- Authentication: OAuth 2.0 for cloud ERPs and payment gateways; API keys for most OCR platforms; service accounts for server-to-server integration.
System Profile
This playbook covers the end-to-end invoice-to-pay integration pattern connecting three system categories: OCR/AI invoice capture platforms (Kofax, ABBYY, Rossum, Coupa, Stampli), ERP AP modules (SAP, Oracle, NetSuite, Dynamics 365), and payment gateways (Tipalti, AvidXchange, Bill.com, Corpay). The architecture is vendor-agnostic — the integration pattern, data mapping, and failure handling apply regardless of specific vendor combination.
This card does NOT cover: upstream procurement/PO creation, expense management (T&E), or accounts receivable automation.
| System | Role | API Surface | Direction |
|---|---|---|---|
| OCR/AI Platform (Kofax, ABBYY, Rossum, Stampli) | Invoice ingestion, data extraction, confidence scoring | REST API | Outbound to middleware |
| ERP AP Module (SAP, Oracle, NetSuite, D365) | Master data (vendors, GL, POs), 3-way match, GL posting | REST/OData/SOAP | Inbound (invoices) + Outbound (PO/GRN data) |
| Approval Engine (ERP-native or Coupa, Stampli) | Routing, threshold-based approvals, exception handling | REST API or ERP-native | Bidirectional |
| Payment Gateway (Tipalti, AvidXchange, Bill.com) | Payment execution (ACH, wire, check, virtual card) | REST API | Inbound (approved invoices) + Outbound (payment status) |
| Middleware/iPaaS (MuleSoft, Boomi, Workato, Celigo) | Orchestration, transformation, error handling, retry | N/A | Orchestrator |
API Surfaces & Capabilities
| Component | Protocol | Best For | Throughput | Real-time? | Bulk? |
|---|---|---|---|---|---|
| OCR extraction API (Rossum, ABBYY) | HTTPS/JSON | Single invoice extraction | 1-5 sec/page | Yes | Batch mode available |
| ERP vendor master lookup | REST/OData | Vendor ID resolution | 50-200 req/sec | Yes | No |
| ERP PO/GRN query | REST/OData | 3-way match data retrieval | 50-200 req/sec | Yes | Pagination required |
| ERP AP invoice posting | REST/SOAP | GL journal entry creation | 10-50 invoices/sec | Yes | Batch posting preferred |
| Payment gateway submission | HTTPS/JSON | Payment batch execution | 1,000+ payments/batch | Batch (daily cutoff) | Yes |
| Payment status webhook | HTTPS/JSON | Payment confirmation | Event-driven | Yes (webhook) | N/A |
Rate Limits & Quotas
Per-Request Limits
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| OCR pages per request | 1-50 pages | ABBYY, Rossum | Batch endpoints accept multi-page PDFs |
| ERP AP invoice line items | 200-1,000 lines | SAP, Oracle, NetSuite | Split mega-invoices before posting |
| Payment batch size | 1,000-10,000 payments | Tipalti, AvidXchange | Larger batches require pre-approval |
| Attachment size | 10-25 MB | Most platforms | Compress scans before upload |
Rolling / Daily Limits
| Limit Type | Value | Window | Platform Differences |
|---|---|---|---|
| OCR API calls | 5,000-50,000/day | 24h | Depends on subscription tier |
| ERP API calls | 10,000-100,000/day | 24h rolling | SAP: fair-use throttle; Salesforce: 100K; NetSuite: governance units |
| Payment batches | 1-4 per day | Daily cutoff | Tipalti: 4pm ET ACH cutoff; AvidXchange: 2pm ET |
| Webhook deliveries | Unlimited (push) | Per event | Retry 3x with exponential backoff |
Authentication
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 Client Credentials | Server-to-server ERP + payment gateway | 1-2 hours | Yes (auto) | Recommended for all production integrations |
| API Key + Secret | OCR platform integration | Permanent until rotated | N/A | Rotate every 90 days minimum |
| OAuth 2.0 Authorization Code | User-context approval workflows | 1h access / long-lived refresh | Yes | Required when integration acts on behalf of approvers |
| Certificate-based (mTLS) | SAP S/4HANA on-premise, bank connections | Session-based | New cert per session | Required for some payment rails |
Authentication Gotchas
- OAuth tokens for ERP APIs expire during long-running batch imports — implement token refresh mid-batch, not just at start. [src5]
- Payment gateway API keys are environment-specific (sandbox vs production) — accidentally using sandbox keys in production silently fails payments. [src1]
- SAP S/4HANA Cloud uses different auth flows than on-premise — JWT bearer for cloud, certificate-based for on-prem. [src2]
Constraints
- OCR accuracy drops below 90% on handwritten, thermally-printed, or heavily skewed invoices — always route low-confidence extractions (below 80% per field) to human review queue
- 3-way matching requires PO and goods receipt note (GRN) data available in ERP before invoice arrives — out-of-order data flow creates false exceptions that clog the queue
- Payment gateway daily batch windows have hard cutoffs: Tipalti 4pm ET for same-day ACH, AvidXchange 2pm ET — missed cutoffs delay payment by 1 business day
- GL coding automation requires 500+ historical invoices per vendor/category combination to reach 95%+ prediction accuracy — new vendors start with manual coding
- Multi-currency invoices must lock exchange rate at invoice date, not posting date — most ERPs default to posting date, requiring explicit configuration override
- Most ERP APIs limit AP invoice line items to 200-1,000 per document — invoices with more lines must be split before posting or use batch/bulk API endpoints
Integration Pattern Decision Tree
START — Need to automate invoice-to-pay AP process
├── What invoice types?
│ ├── PO-backed invoices (3-way match required)
│ │ ├── Volume < 500/month → Embedded AP tool (Stampli, Ramp, BILL)
│ │ ├── Volume 500-5,000/month → Mid-market platform (Tipalti, AvidXchange)
│ │ └── Volume > 5,000/month → Enterprise OCR + iPaaS + ERP + payment gateway
│ ├── Non-PO invoices (GL coding required)
│ │ ├── AI/ML GL coding available → Auto-code with confidence threshold → approval
│ │ └── No AI coding → Manual coding → approval workflow
│ └── Recurring invoices → Create schedule in ERP → auto-match → skip approval if within tolerance
├── What ERP system?
│ ├── SAP S/4HANA → SAP Invoice Management or Kofax + SAP API
│ ├── Oracle ERP Cloud → Oracle AP module + FBDI or REST API
│ ├── NetSuite → Embedded AP or SuiteScript + REST API
│ └── Dynamics 365 → D365 vendor invoice automation or third-party + OData
├── Payment execution?
│ ├── Domestic only (US ACH + check) → BILL, AvidXchange
│ ├── Global payments (multi-currency) → Tipalti, Corpay
│ └── Virtual card (rebate capture) → Platform with vCard support
└── Error tolerance?
├── Zero tolerance (regulated) → Full 3-way match + dual approval + audit trail
└── Standard tolerance (±2-5%) → Auto-approve within tolerance, exception queue for outliers
Quick Reference
| Step | Source System | Action | Target System | Data Objects | Failure Handling |
|---|---|---|---|---|---|
| 1. Ingest | Email / SFTP / Scanner | Invoice arrives (PDF, image, XML, EDI) | OCR Platform | Raw document | Dead letter queue for unreadable files |
| 2. Extract | OCR Platform | AI extracts header + line items with confidence scores | Middleware | Extracted invoice JSON | Low-confidence fields → human review queue |
| 3. Validate | Middleware | Vendor lookup, duplicate check, field validation | ERP (vendor master) | Vendor ID, invoice number | Unknown vendor → onboarding workflow |
| 4. Match | Middleware / ERP | 3-way match: Invoice vs PO vs GRN | ERP (PO + GRN tables) | PO lines, GRN lines, tolerances | Mismatch → exception queue with variance details |
| 5. Code GL | AP Platform / AI | Auto-assign GL accounts, cost centers, tax codes | ERP (chart of accounts) | GL codes, tax codes | Low-confidence coding → manual review |
| 6. Approve | Approval Engine | Route based on amount thresholds and department rules | ERP or AP Platform | Approval status, approver ID | Escalation after SLA breach (48h default) |
| 7. Post | Middleware | Create AP invoice / journal entry in ERP | ERP AP Module | AP voucher, GL entries | Posting failure → retry 3x, then alert |
| 8. Schedule | ERP / Payment Platform | Queue for payment based on terms and cash position | Payment Gateway | Payment batch | Missed cutoff → next business day |
| 9. Execute | Payment Gateway | Execute ACH, wire, check, or virtual card payment | Bank / Vendor | Payment confirmation | Failed payment → retry + vendor notification |
| 10. Reconcile | Payment Gateway | Payment status webhook → update ERP | ERP AP Module | Payment reference, clearing date | Unmatched payment → manual reconciliation |
Step-by-Step Integration Guide
1. Configure OCR/AI invoice capture endpoint
Set up the OCR platform to accept invoices from all ingestion channels (email forwarding, SFTP upload, API submission) and extract structured data. [src2]
# Example: Rossum invoice extraction via REST API
import requests
ROSSUM_API_KEY = "your_api_key"
ROSSUM_QUEUE_ID = "123456"
def submit_invoice_to_ocr(file_path: str) -> dict:
"""Submit invoice PDF to OCR and get extraction results."""
url = f"https://elis.rossum.ai/api/v1/queues/{ROSSUM_QUEUE_ID}/upload"
headers = {"Authorization": f"Bearer {ROSSUM_API_KEY}"}
with open(file_path, "rb") as f:
response = requests.post(url, headers=headers, files={"content": f})
response.raise_for_status()
annotation_url = response.json()["results"][0]["annotation"]
return poll_annotation(annotation_url, headers)
Verify: Check that extracted fields include vendor_name, invoice_number, invoice_date, total_amount, line_items[] with confidence scores above 0.80.
2. Validate vendor and resolve master data
Before 3-way matching, validate the extracted vendor against the ERP vendor master. Resolve vendor ID, payment terms, and default GL coding. [src5]
# Example: NetSuite vendor lookup via SuiteQL
def lookup_vendor_in_erp(vendor_name: str, tax_id: str = None) -> dict:
"""Resolve vendor in ERP master data."""
if tax_id:
query = f"SELECT id, entityid, companyname FROM vendor WHERE taxidnum = '{tax_id}'"
else:
query = f"SELECT id, entityid, companyname FROM vendor WHERE companyname LIKE '%{vendor_name}%' LIMIT 5"
response = requests.post(
f"{NETSUITE_URL}/services/rest/query/v1/suiteql",
headers={"Authorization": f"Bearer {access_token}"},
json={"q": query}
)
results = response.json().get("items", [])
if not results:
raise VendorNotFoundError(f"No vendor match for: {vendor_name} / {tax_id}")
return results[0]
Verify: Vendor ID resolved, payment terms and default GL account returned. If no match, invoice routes to vendor onboarding queue.
3. Execute 3-way match (Invoice vs PO vs GRN)
Compare invoice header and line items against the purchase order and goods receipt note. Apply tolerance thresholds for auto-approval. [src3, src4]
def three_way_match(invoice, po, grn, tolerances) -> dict:
"""
3-way match with configurable tolerances.
tolerances = {"price_pct": 0.02, "qty_pct": 0.05, "total_abs": 100.00}
"""
variances = []
for inv_line in invoice["line_items"]:
po_line = find_matching_po_line(inv_line, po["lines"])
grn_line = find_matching_grn_line(inv_line, grn["lines"])
if not po_line:
variances.append({"line": inv_line["line_num"], "type": "NO_PO_MATCH", "severity": "high"})
continue
price_diff = abs(inv_line["unit_price"] - po_line["unit_price"]) / po_line["unit_price"]
if price_diff > tolerances["price_pct"]:
variances.append({"line": inv_line["line_num"], "type": "PRICE_VARIANCE",
"variance_pct": price_diff})
if grn_line:
qty_diff = abs(inv_line["quantity"] - grn_line["received_qty"]) / grn_line["received_qty"]
if qty_diff > tolerances["qty_pct"]:
variances.append({"line": inv_line["line_num"], "type": "QTY_VARIANCE",
"variance_pct": qty_diff})
high_severity = [v for v in variances if v.get("severity") == "high" or v.get("variance_pct", 0) > tolerances["price_pct"]]
return {"status": "exception" if high_severity else "matched", "variances": variances}
Verify: Matched invoices return status: "matched". Exception invoices have detailed variance records for human review.
4. Post approved invoice to ERP GL
After approval, create the AP invoice/voucher in the ERP with full GL coding, tax allocation, and payment terms. [src2, src5]
# Example: SAP S/4HANA AP invoice posting via API
def post_invoice_to_erp(approved_invoice):
payload = {
"CompanyCode": approved_invoice["company_code"],
"InvoicingParty": approved_invoice["vendor_id"],
"DocumentDate": approved_invoice["invoice_date"],
"PostingDate": approved_invoice["posting_date"],
"SupplierInvoiceIDByInvcgParty": approved_invoice["invoice_number"],
"InvoiceGrossAmount": str(approved_invoice["total_amount"]),
"DocumentCurrency": approved_invoice["currency"],
"to_SuplrInvcItemPurOrdRef": [
{"PurchaseOrder": line["po_number"], "PurchaseOrderItem": line["po_line"],
"SupplierInvoiceItemAmount": str(line["amount"]), "TaxCode": line["tax_code"],
"GLAccount": line["gl_account"], "CostCenter": line["cost_center"]}
for line in approved_invoice["line_items"]
]
}
response = requests.post(
f"{SAP_API_URL}/API_SUPPLIERINVOICE_PROCESS_SRV/A_SupplierInvoice",
headers={"Authorization": f"Bearer {token}"}, json=payload)
response.raise_for_status()
return response.json()
Verify: ERP returns AP document number. GL entries visible in trial balance. Payment due date calculated from payment terms.
5. Submit payment batch to payment gateway
Collect approved, posted invoices due for payment and submit to the payment gateway for execution. [src1, src5]
# Example: Tipalti payment batch submission
def submit_payment_batch(invoices, payment_date):
payments = [{"payeeId": inv["vendor_external_id"],
"amountSubmitted": inv["payment_amount"],
"currency": inv["currency"],
"invoiceRefCode": inv["erp_document_number"]}
for inv in invoices]
response = requests.post(f"{TIPALTI_API_URL}/api/v1/payments/batch",
headers={"Authorization": f"Bearer {tipalti_token}"},
json={"payments": payments, "paymentDate": payment_date})
response.raise_for_status()
return response.json()
Verify: Payment gateway returns batch ID. Monitor status via webhook or polling until all payments reach completed status.
6. Handle payment confirmation and ERP reconciliation
When payments execute, update the ERP AP module to clear the open invoice and record the payment reference. [src5]
def handle_payment_webhook(webhook_payload):
for payment in webhook_payload["payments"]:
if payment["status"] == "completed":
clear_ap_document(
document_number=payment["invoiceRefCode"],
payment_reference=payment["paymentId"],
clearing_date=payment["executedDate"])
elif payment["status"] == "failed":
create_exception(document_number=payment["invoiceRefCode"],
error=payment["failureReason"], severity="high")
Verify: Open AP items cleared in ERP. Bank reconciliation matches payment gateway records. No orphaned open items.
Code Examples
See Step-by-Step Integration Guide above for complete, runnable code examples covering OCR submission (Python/Rossum), vendor lookup (Python/NetSuite SuiteQL), 3-way matching (Python), ERP posting (Python/SAP), payment batch submission (Python/Tipalti), and webhook reconciliation (Python).
Data Mapping
Field Mapping Reference
| Source Field (OCR Output) | Target Field (ERP AP Module) | Type | Transform | Gotcha |
|---|---|---|---|---|
| vendor_name | Vendor ID (internal) | String → Lookup | Fuzzy match against vendor master | Name variations (Inc. vs LLC vs Ltd) cause duplicate vendor creation |
| invoice_number | Supplier Invoice Reference | String | Direct (trim whitespace, normalize) | Some vendors reuse invoice numbers across years |
| invoice_date | Document Date | Date | Parse from various formats → ISO 8601 | MM/DD/YYYY vs DD/MM/YYYY confusion |
| due_date | Payment Due Date | Date | Parse or calculate from payment terms | If missing, calculate from invoice_date + terms |
| total_amount | Invoice Gross Amount | Decimal | Extract, validate against line item sum | Currency symbol extraction errors |
| tax_amount | Tax Amount | Decimal | Extract or calculate from rate × base | Tax-inclusive vs tax-exclusive varies by region |
| po_number | Purchase Order Reference | String | Direct match against open POs | Partial PO numbers, prefixed zeros cause failures |
| line_item.description | PO Line Description | String | Fuzzy match against PO line descriptions | OCR truncation on long descriptions |
| line_item.quantity | Invoice Quantity | Decimal | Direct | Unit of measure mismatch (each vs case vs pallet) |
| line_item.unit_price | Invoice Unit Price | Decimal | Direct, convert currency if needed | Rounding differences — use tolerance threshold |
Data Type Gotchas
- OCR extracts dates in the document's locale format — a European invoice shows 03/01/2026 as January 3rd, but US parsing reads it as March 1st. Always detect locale from vendor country before parsing. [src2]
- Currency amounts extracted by OCR may include locale-specific thousands separators (1,234.56 vs 1.234,56) — strip and re-parse using vendor country locale. [src2]
- SAP stores amounts in the smallest currency unit (cents for USD) while most OCR platforms return decimal format — always convert before posting. [src5]
- NetSuite multi-currency invoices require the exchange rate to be explicitly set; if omitted, NetSuite uses its internal rate table which may differ from the invoice rate. [src5]
Error Handling & Failure Points
Common Error Codes
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| OCR_LOW_CONFIDENCE | Field extraction below threshold | Poor scan quality, handwriting | Route to human review; re-scan at higher DPI |
| VENDOR_NOT_FOUND | No vendor match in ERP master | New vendor, name variation | Trigger vendor onboarding workflow |
| PO_NOT_FOUND | Purchase order not found or closed | Wrong PO number extracted, PO closed | Manual review; check for OCR errors |
| GRN_MISSING | Goods receipt not yet posted | Invoice arrived before goods received | Park invoice; auto-retry when GRN posts |
| PRICE_VARIANCE | Unit price exceeds tolerance vs PO | Price change, surcharge included | Route to buyer for confirmation |
| QTY_VARIANCE | Quantity mismatch vs GRN | Partial shipment, damaged goods | Route to receiving department |
| DUPLICATE_INVOICE | Same vendor + invoice number exists | Re-submitted invoice | Block posting; alert AP team |
| GL_POSTING_FAILED | ERP rejected the journal entry | Period closed, invalid GL account | Check fiscal calendar; validate GL coding |
| PAYMENT_REJECTED | Bank rejected payment | Invalid bank details, sanctions screening | Update vendor banking info; retry next batch |
Failure Points in Production
- OCR confidence score ignored: Teams auto-post all OCR output without checking per-field confidence scores, posting wrong amounts to GL. Fix:
Set minimum field-level confidence threshold (80%+ for amount fields, 90%+ for vendor ID) and route failures to human review.[src6] - 3-way match bypassed for non-PO invoices: Non-PO invoices skip matching entirely and post with only one approval — the #1 fraud vector. Fix:
Require 2-way match (invoice vs GL budget) and dual approval for all non-PO invoices above $500.[src4] - Duplicate invoice detection fails on vendor number variations: OCR reads invoice #1234 as #01234, passing duplicate check. Fix:
Normalize invoice numbers (strip leading zeros, remove alpha suffixes) and check duplicates within 90-day window.[src6] - Payment gateway timeout treated as failure: Integration retries a timed-out payment submission, causing double payment. Fix:
Use idempotency keys on all payment submissions. Check status before retrying.[src1] - Exchange rate drift on multi-currency batches: Invoices posted over multiple days use different exchange rates, causing reconciliation variances. Fix:
Lock exchange rate at invoice posting time and pass explicit rate to payment gateway.[src5] - Fiscal period closure race condition: Month-end batch tries to post invoices after the accounting period closes. Fix:
Check fiscal period status before batch processing. Implement period-aware queuing.[src2]
Anti-Patterns
Wrong: Posting OCR output directly to ERP without validation
# BAD — posts whatever OCR extracts, no matter the confidence
def auto_post_invoice(ocr_result):
erp_client.post_invoice({
"vendor": ocr_result["vendor_name"], # Not resolved to vendor ID
"amount": ocr_result["total"], # No confidence check
"po": ocr_result["po_number"] # No PO validation
})
Correct: Validate, resolve, and match before posting
# GOOD — validates every field, resolves references, checks confidence
def process_invoice(ocr_result):
if ocr_result["confidence"]["total_amount"] < 0.80:
route_to_human_review(ocr_result)
return
vendor = lookup_vendor(ocr_result["vendor_name"], ocr_result["tax_id"])
po = get_purchase_order(ocr_result["po_number"])
if not po or po["status"] == "closed":
create_exception("PO_NOT_FOUND", ocr_result)
return
match_result = three_way_match(ocr_result, po, get_grn(po["id"]))
if match_result["status"] == "exception":
route_to_exception_queue(ocr_result, match_result)
return
erp_client.post_invoice(build_erp_payload(ocr_result, vendor, po))
Wrong: Synchronous payment execution per invoice
# BAD — processes payments one at a time, slow and expensive
for invoice in approved_invoices:
payment_gateway.submit_payment(invoice)
time.sleep(1) # Rate limit workaround
Correct: Batch payments with daily cutoff awareness
# GOOD — batches payments, respects cutoff times, uses idempotency keys
import hashlib
from datetime import datetime, time as dtime
def submit_daily_payment_batch(approved_invoices):
cutoff = dtime(16, 0) # 4:00 PM ET for same-day ACH
if datetime.now().time() > cutoff:
log.warning("Past cutoff — payments execute next business day")
batch = []
for inv in approved_invoices:
key = hashlib.sha256(
f"{inv['vendor_id']}:{inv['invoice_number']}:{inv['amount']}".encode()
).hexdigest()
batch.append({**inv, "idempotency_key": key})
return payment_gateway.submit_batch(batch)
Wrong: Ignoring partial success in batch operations
# BAD — assumes entire batch succeeded or failed
result = erp_client.post_invoice_batch(invoices)
if result.status_code == 200:
mark_all_as_posted(invoices) # Some may have failed individually
Correct: Handle partial success with per-record status tracking
# GOOD — tracks individual record outcomes within a batch
result = erp_client.post_invoice_batch(invoices)
for i, item_result in enumerate(result["items"]):
if item_result["status"] == "success":
mark_as_posted(invoices[i], item_result["document_number"])
else:
create_exception(invoices[i], item_result["error"])
Common Pitfalls
- Vendor master data quality: Duplicate vendors in ERP cause invoices to post against wrong vendor accounts, breaking AP aging and 1099 reporting. Fix:
Implement deduplication rules using tax ID as primary key; merge duplicates before go-live.[src6] - Tolerance thresholds too tight at launch: Setting 0% tolerance creates a tsunami of exceptions that overwhelms the AP team. Fix:
Start with 5% price / 10% quantity tolerance for first 90 days; tighten to 2% / 5% after exception patterns stabilize.[src4] - No duplicate detection across OCR re-processing: Both original and retry create invoice records, leading to double payment. Fix:
Generate document fingerprint (hash of vendor + amount + date) on first ingestion; check before creating new record.[src6] - Payment term calculation ignoring holidays: Due date uses calendar days but payment execution skips non-business days, missing early discount windows. Fix:
Use business day calendar for due date calculation; account for bank holidays in payment scheduling.[src1] - Missing audit trail for approval overrides: Managers override exceptions without documenting reasons, creating SOX audit findings. Fix:
Require mandatory reason codes and comments for all exception overrides.[src7] - ERP sandbox not reflecting production volumes: Testing with 50 invoices works but 5,000 hits rate limits. Fix:
Load test with production-scale volumes; monitor API consumption during tests; implement batch chunking.[src8]
Diagnostic Commands
# Check OCR extraction quality for a specific invoice
curl -X GET "https://elis.rossum.ai/api/v1/annotations/{annotation_id}" \
-H "Authorization: Bearer $ROSSUM_TOKEN" \
| jq '.content[] | {field: .schema_id, value: .value, confidence: .confidence}'
# Query ERP for open POs awaiting invoice match (NetSuite example)
curl -X POST "$NETSUITE_URL/services/rest/query/v1/suiteql" \
-H "Authorization: Bearer $NS_TOKEN" \
-d '{"q": "SELECT tranid, entity, total FROM transaction WHERE type = '\''PurchOrd'\'' AND status = '\''open'\''"}'
# Check for duplicate invoices in ERP before posting
curl -X POST "$NETSUITE_URL/services/rest/query/v1/suiteql" \
-H "Authorization: Bearer $NS_TOKEN" \
-d '{"q": "SELECT tranid, total FROM transaction WHERE type = '\''VendBill'\'' AND tranid = '\''INV-12345'\''"}'
# Check payment batch status in Tipalti
curl -X GET "https://api.tipalti.com/api/v1/payments/batch/{batch_id}/status" \
-H "Authorization: Bearer $TIPALTI_TOKEN"
# Monitor 3-way match exception queue depth
curl -X GET "$AP_PLATFORM_URL/api/v1/exceptions?status=open" \
-H "Authorization: Bearer $AP_TOKEN" | jq '.total_count'
# Verify GL posting in ERP (SAP S/4HANA example)
curl -X GET "$SAP_URL/API_SUPPLIERINVOICE_PROCESS_SRV/A_SupplierInvoice('5105600001')" \
-H "Authorization: Bearer $SAP_TOKEN"
Version History & Compatibility
| Component | Version/Release | Date | Status | Breaking Changes |
|---|---|---|---|---|
| Rossum API | v3 | 2025-11 | Current | Schema validation changes on extraction output |
| ABBYY Vantage | 2.5 | 2025-09 | Current | None |
| Kofax TotalAgility | 8.0 | 2025-06 | Current | New connector framework replaces legacy adapters |
| Tipalti API | v6 | 2025-10 | Current | Payment batch endpoint changed from /bills to /payments |
| AvidXchange API | v3 | 2025-08 | Current | OAuth 2.0 required (API key deprecated) |
| Bill.com API | v3 | 2025-07 | Current | Pagination changed to cursor-based |
| SAP S/4HANA Cloud | 2408 | 2024-08 | Current | New AP API endpoints for 2-step verification |
| Oracle ERP Cloud | 24B | 2024-06 | Current | Invoice REST API v2 replaces v1 |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| PO-backed invoice processing with 3-way match requirements | Expense reimbursement or T&E processing | Expense management platforms (Concur, Expensify) |
| Volume exceeds 500 invoices/month | Fewer than 100 invoices/month with simple GL coding | Basic AP module in accounting software |
| Multi-vendor, multi-currency AP operations | Single vendor, single currency recurring payments | Scheduled bank transfers or standing orders |
| SOX compliance requires documented approval trails | Non-regulated small business with owner-approval | Simple AP workflow in QuickBooks/Xero |
| Need to capture early payment discounts at scale | All vendors on Net 30 with no discount incentives | Standard ERP payment run |
Cross-System Comparison
| Capability | Kofax / ABBYY | Rossum | Coupa | Stampli | Tipalti |
|---|---|---|---|---|---|
| OCR/AI Extraction | Enterprise-grade, 99%+ header accuracy | AI-native, learns per customer | Built-in invoice capture | AI-assisted capture | Basic capture included |
| ERP Connectors | 200+ pre-built | 50+ via marketplace | SAP, Oracle, NetSuite, D365 | 70+ connectors | SAP, NetSuite, D365, Sage |
| 3-Way Matching | Via ERP integration | Via ERP integration | Native (Coupa PO module) | Native with AI assist | Native with PO matching |
| GL Coding AI | ML-based coding available | AI auto-coding | Rule + ML hybrid | AI auto-coding | ML-based coding |
| Payment Execution | Via payment gateway | Via payment gateway | Coupa Pay | Via payment gateway | Native (190+ countries) |
| Virtual Card Support | Via partner | Via partner | Coupa Pay | Via partner | Native |
| Pricing Model | Per-page or per-document | Per-document | Platform license | Per-user | Per-payment + platform fee |
| Best For | High-volume enterprise (>10K/mo) | Mid to enterprise (1K-50K/mo) | Procurement-centric orgs | AP team collaboration | Global payments focus |
Important Caveats
- OCR accuracy statistics (97%+) apply to header fields on machine-generated invoices (PDF, XML) — accuracy on scanned paper invoices and handwritten notes is significantly lower (80-90%). Always plan for a human review queue.
- 3-way match automation rates (80-95% straight-through processing) assume clean master data — poor vendor master, incomplete POs, and delayed goods receipts dramatically reduce auto-match rates.
- Payment gateway fees vary significantly by method: ACH ($0.50-2.00), wire ($15-30), check ($2-5), virtual card (0% — funded by rebates). Total cost optimization requires payment method routing logic.
- Regulatory requirements vary by jurisdiction — EU e-invoicing mandates (ViDA directive, effective 2028) may require structured invoice formats (UBL, CII) that bypass OCR entirely.
- ROI projections ($15-25 manual vs $2-5 automated per invoice) assume full adoption including vendor portal enrollment — partial adoption with paper fallbacks significantly reduces savings.