T&E System-to-ERP Integration Playbook: Concur, Expensify, Navan, Brex & Ramp
How do you integrate Concur/Expensify/Navan with ERP for GL coding and approval workflows?
TL;DR
- Bottom line: T&E-to-ERP integration follows a universal pattern -- receipt capture → expense report → approval → GL coding → journal posting → reimbursement -- but each platform has different API surfaces, export formats, and pre-built ERP connectors that determine integration complexity.
- Key limit: Corporate card feeds arrive 24-48h after transaction; never treat card data as real-time for GL posting. SAP Concur Financial Integration API only returns fully-approved documents.
- Watch out for: Exchange rate timing mismatch -- if the T&E system captures the rate at transaction time but the ERP uses the rate at posting time, you get unexplained variances across hundreds of reports.
- Best for: Organizations with >50 employees submitting expenses, corporate card programs, or multi-entity structures where manual GL coding and reconciliation consume >20 hours/month.
- Authentication: SAP Concur uses OAuth 2.0 (JWT bearer for server-to-server); Expensify uses static API credentials; Brex/Ramp use OAuth 2.0; Navan uses API key + SFTP.
System Profile
This playbook covers the end-to-end integration of five major T&E platforms (SAP Concur, Expensify, Navan, Brex, and Ramp) with enterprise ERP systems. It focuses on the data flow from expense creation through GL journal posting and reimbursement. This card does NOT cover AP invoice processing, procurement-to-pay workflows, or payroll integration for reimbursement via paycheck.
| System | Role | API Surface | Direction |
|---|---|---|---|
| SAP Concur | T&E platform -- expense reports, travel booking, receipt capture | Financial Integration API v4 (REST) | Outbound to ERP |
| Expensify | T&E platform -- expense reports, corporate card, receipt scanning | Integration Server API (REST) | Outbound to ERP |
| Navan | T&E + travel platform -- expense, travel booking, corporate card | Expense API v1, SFTP | Outbound to ERP |
| Brex | Corporate card + expense -- card transactions, receipt matching | Accounting API v1 (REST) | Bidirectional with ERP |
| Ramp | Corporate card + expense -- card transactions, AP automation | Developer API v1 (REST) | Bidirectional with ERP |
| ERP (Target) | Financial system of record -- GL, AP, AR | Varies (REST, OData, SOAP, File Import) | Inbound from T&E |
| iPaaS (Optional) | Integration orchestrator -- Workato, Boomi, MuleSoft, Celigo | Varies | Orchestrator |
API Surfaces & Capabilities
| T&E Platform | Export Method | Format | Real-time? | Pre-built ERP Connectors | Custom API? |
|---|---|---|---|---|---|
| SAP Concur | Financial Integration API v4 | JSON | Near-real-time (poll) | SAP S/4HANA, Oracle, NetSuite, Dynamics 365, Sage Intacct | Yes |
| SAP Concur | Standard Accounting Extract (SAE) | Flat file | Batch (scheduled) | Any ERP with file import | N/A |
| Expensify | Integration Server API | JSON | On-demand | NetSuite, QBO, Xero, Sage Intacct, Oracle | Yes |
| Navan | Direct Integration | JSON | Near-real-time | NetSuite, Sage Intacct, QBO | Limited |
| Navan | SFTP Export | CSV/JSON | Batch (scheduled) | Any ERP with file import | N/A |
| Brex | Accounting API v1 | JSON | Real-time (two-way) | NetSuite, QBO, Xero, Sage Intacct | Yes |
| Ramp | Developer API v1 | JSON | Real-time (sync) | NetSuite, Oracle Fusion, Sage Intacct, QBO, Xero | Yes |
Rate Limits & Quotas
Per-Request Limits
| T&E Platform | Limit Type | Value | Notes |
|---|---|---|---|
| SAP Concur | Max financial documents per fetch | 100 per page | Paginate with nextPage link |
| SAP Concur | Expense Report API v4 query | 100 reports per page | Use start parameter for pagination |
| Expensify | Max report export batch | 1 concurrent request per credential pair | Queue exports sequentially |
| Brex | Max transactions per page | 1,000 | Cursor-based pagination |
| Ramp | Max records per page | 100 | Cursor-based pagination |
| Ramp | Accounting sync batch | 500 GL codes per sync | Split larger code sets |
Rolling / Daily Limits
| T&E Platform | Limit Type | Value | Window | Notes |
|---|---|---|---|---|
| SAP Concur | API calls (OAuth app) | 24,000 requests/hr | Rolling hourly | Shared across all API surfaces |
| Expensify | API requests | No published hard limit | N/A | Subject to fair-use throttling |
| Brex | API calls | Rate-limited per endpoint | Per minute | 429 response with retry-after header |
| Ramp | API calls | Rate-limited per endpoint | Per minute | 429 response with retry-after header |
| Navan | SFTP export frequency | Configurable (min 1hr) | Scheduled | Direct API limits not publicly documented |
Authentication
| T&E Platform | Auth Method | Mechanism | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|---|
| SAP Concur | OAuth 2.0 | JWT bearer (company-level) or Auth Code (user-level) | Access: 1h, Refresh: 6 months | Yes | Company JWT for Financial Integration API |
| Expensify | Static credentials | partnerUserID + partnerUserSecret | Unlimited | No | One pair per integration |
| Navan | API Key | Bearer token in header | Long-lived | No | Issued by Navan support |
| Brex | OAuth 2.0 | Authorization Code flow | Access: 1h | Yes | Scopes: accounting.read, accounting.write |
| Ramp | OAuth 2.0 | Authorization Code flow | Access: 1h | Yes | Scopes: accounting:read, accounting:write |
Authentication Gotchas
- SAP Concur company-level JWT requires a one-time admin grant in the Concur admin UI -- cannot be created programmatically. The refresh token expires after 6 months of non-use. [src1]
- Expensify's partnerUserID is tied to a specific admin account. If that admin leaves the company, the integration breaks. [src3]
- Brex's OAuth scopes changed with the Accounting API launch. Legacy API keys from pre-2025 do NOT work with new endpoints. [src5]
Constraints
- SAP Concur Financial Integration API only returns documents in
approvedstatus. You cannot pull pending, submitted, or recalled reports. - Expensify Integration Server processes one export request at a time per credential pair. Concurrent calls will queue, not parallelize.
- Navan direct ERP integrations only support NetSuite, Sage Intacct, and QuickBooks Online as of March 2026. SAP, Oracle, and Dynamics 365 require custom SFTP or API integration.
- Brex Accounting API mandates bidirectional sync for real-time mode. You must push GL codes FROM the ERP TO Brex before transactions flow back.
- Corporate card transaction data typically arrives 24-48 hours after the transaction posts with the card network.
- Multi-currency expense reports create exchange rate timing risk. Decide which rate governs BEFORE go-live.
- Tax reclaim rules (especially VAT in EU/UK) require the T&E system to capture itemized receipt data and tax codes. Not all platforms support per-line-item tax coding for all jurisdictions.
Integration Pattern Decision Tree
START -- Integrate T&E platform with ERP
|
+-- Which T&E platform?
| +-- SAP Concur
| | +-- SAP S/4HANA or ECC as ERP?
| | | +-- YES --> Use Concur-SAP standard connector (pre-built, certified)
| | | +-- NO --> Use Financial Integration API v4
| | +-- Need pre-approval data?
| | +-- YES --> Reports API v4 (separate from Financial Integration)
| | +-- NO --> Financial Integration API v4 only
| |
| +-- Expensify
| | +-- NetSuite, QBO, Xero, or Sage Intacct?
| | | +-- YES --> Use Expensify native integration
| | | +-- NO --> Use Integration Server API
| | +-- Export format: Journal entries, Vendor bills, or Credit card charges
| |
| +-- Navan
| | +-- NetSuite, Sage Intacct, or QBO?
| | | +-- YES --> Use Navan direct integration
| | | +-- NO --> SFTP export + custom file import to ERP
| |
| +-- Brex
| | +-- Need real-time sync?
| | | +-- YES --> Accounting API (bidirectional)
| | | +-- NO --> CSV batch export
| |
| +-- Ramp
| +-- Oracle Fusion, NetSuite, Sage Intacct, or QBO?
| | +-- YES --> Use Ramp native accounting sync
| | +-- NO --> Developer API v1 + custom mapping
|
+-- Corporate card reconciliation needed?
| +-- YES --> Clearing account pattern (see Quick Reference)
| +-- NO --> Expense report journal posting only
|
+-- Multi-entity / intercompany?
+-- YES --> Configure entity mapping; route journals per entity
+-- NO --> Single-entity standard flow
Quick Reference: End-to-End Integration Flow
| Step | Source | Action | Target | Data Objects | Failure Handling |
|---|---|---|---|---|---|
| 1 | Employee | Capture receipt, create expense line | T&E Platform | Expense entry, receipt image | Auto-retry OCR; manual entry fallback |
| 2 | T&E Platform | Apply policy rules, auto-code GL account | T&E Platform | Expense type → GL mapping | Flag out-of-policy items for review |
| 3 | T&E Platform | Route for approval | T&E Platform | Approval workflow, delegation | Escalation after SLA breach |
| 4 | T&E Platform | Mark report approved, lock for export | T&E Platform | Approved expense report | Reopen if rejected |
| 5 | T&E API | Export approved report as financial document | iPaaS / Custom | Journal lines (debit/credit) | Retry 3x, then DLQ |
| 6 | iPaaS / Custom | Transform T&E data to ERP journal format | ERP | GL journal entry | Validate GL codes before posting |
| 7 | ERP | Post journal entry, create payable | ERP | GL journal, AP voucher | Duplicate check on external ref ID |
| 8 | ERP | Process reimbursement | Bank / Payroll | Payment instruction | Reconcile against posted journal |
| 9 | iPaaS / Custom | Post confirmation back to T&E platform | T&E Platform | Posting status, ERP doc ID | Update status to posted/failed |
| 10 | Card Network | Feed corporate card transactions | T&E Platform | Card transaction data | Match to expenses; flag orphaned |
Step-by-Step Integration Guide
1. Configure GL Account Mapping in the T&E Platform
Map your ERP chart of accounts into the T&E platform. Every T&E system maintains its own expense type list that must map 1:1 to GL natural accounts. [src6, src7]
Verify: Export a test expense report and confirm every line maps to a valid GL code. Zero unmapped lines = ready for production.
2. Set Up Authentication for Your T&E Platform API
Each platform uses a different auth mechanism. SAP Concur uses OAuth 2.0 JWT bearer; Expensify uses static credentials; Brex and Ramp use OAuth 2.0 Authorization Code flow. [src1, src3]
# SAP Concur: Exchange refresh token for access token
curl -X POST "https://us2.api.concursolutions.com/oauth2/v0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN"
Verify: Response includes access_token and token_type: "Bearer".
3. Poll for Approved Expense Reports
The Financial Integration API v4 returns approved documents ready for ERP posting. Poll every 15-60 minutes. [src1, src2]
curl -X GET "https://us2.api.concursolutions.com/financialintegration/fi/v4/companies/transactiontypes/expense/transactions?limit=100" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Verify: Each document has systemId for idempotent posting.
4. Transform T&E Data to ERP Journal Entry Format
Map T&E financial document to ERP journal schema. Core pattern: DEBIT expense accounts, CREDIT AP payable (reimbursable) or clearing account (corporate card). [src2, src6]
Verify: Sum of debits == sum of credits for every journal entry.
5. Post Journal Entry to ERP
Post transformed journal using the T&E document ID as external reference for idempotent duplicate prevention. [src7]
Verify: ERP returns 204 (created) or 409 (duplicate).
6. Send Posting Confirmation Back to T&E Platform
Confirm back so the document is locked and marked as posted. [src1]
Verify: Document no longer appears in future Financial Integration API polls.
7. Configure Corporate Card Feed and Reconciliation
Card reconciliation uses a clearing account pattern: card transactions flow through T&E platform, export as DEBIT expense / CREDIT clearing; monthly settlement clears the clearing account against the bank. [src6]
Verify: At month-end, clearing account balance = sum of approved-but-not-settled card transactions.
Code Examples
Python: Expensify Report Export and GL Transform
# Input: Expensify API credentials, date range
# Output: List of journal entries ready for ERP posting
import requests, json
def export_expensify_reports(partner_id, partner_secret, start_date, end_date):
payload = {
"type": "get",
"credentials": {"partnerUserID": partner_id, "partnerUserSecret": partner_secret},
"inputSettings": {
"type": "combinedReportData",
"filters": {"status": "APPROVED", "startDate": start_date, "endDate": end_date}
}
}
resp = requests.post(
"https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations",
data={"requestJobDescription": json.dumps(payload)}, timeout=60
)
reports = resp.json()
journals = []
for report in reports:
lines = []
for expense in report["expenses"]:
lines.append({
"account": expense["category"],
"debit": float(expense["amount"]) / 100, # Cents to dollars
"description": expense["merchant"],
})
total = sum(l["debit"] for l in lines)
credit_acct = "2100-200" if report.get("hasCompanyCard") else "2100-100"
lines.append({"account": credit_acct, "credit": total})
journals.append({"reference": report["reportID"], "lines": lines})
return journals
JavaScript/Node.js: Ramp Transaction Sync
// Input: Ramp OAuth token, last sync timestamp
// Output: New card transactions with GL coding for ERP posting
const axios = require('axios'); // ^1.7.0
async function syncRampTransactions(accessToken, lastSyncTime) {
const transactions = [];
let cursor = null;
do {
const params = new URLSearchParams({ from_date: lastSyncTime, page_size: '100' });
if (cursor) params.set('start', cursor);
const resp = await axios.get(
`https://demo-api.ramp.com/developer/v1/transactions?${params}`,
{ headers: { Authorization: `Bearer ${accessToken}` }, timeout: 30000 }
);
for (const txn of resp.data.data) {
transactions.push({
reference: txn.id, date: txn.user_transaction_time,
merchant: txn.merchant_name, amount: txn.amount,
gl_account: txn.accounting_field_selections?.find(f => f.type === 'GL_ACCOUNT')?.external_id,
cost_center: txn.accounting_field_selections?.find(f => f.type === 'COST_CENTER')?.external_id,
});
}
cursor = resp.data.page?.next;
} while (cursor);
return transactions;
}
cURL: Brex Accounting API -- Push GL Codes
# Push GL account list from ERP to Brex (required before sync)
curl -X POST "https://platform.brexapis.com/v2/accounting/accounts" \
-H "Authorization: Bearer YOUR_BREX_TOKEN" \
-H "Content-Type: application/json" \
-d '{"accounts": [
{"id": "6200-100", "name": "Travel - Air", "type": "EXPENSE"},
{"id": "6200-200", "name": "Travel - Lodging", "type": "EXPENSE"},
{"id": "2100-200", "name": "Corp Card Clearing", "type": "LIABILITY"}
]}'
# Fetch coded transactions ready for ERP posting
curl -X GET "https://platform.brexapis.com/v2/transactions/card?posted_at_start=2026-03-01T00:00:00Z" \
-H "Authorization: Bearer YOUR_BREX_TOKEN"
Data Mapping
Field Mapping Reference
| T&E Field (Generic) | SAP Concur | Expensify | Ramp/Brex | ERP Target | Gotcha |
|---|---|---|---|---|---|
| Expense amount | journalAmount | amount (cents) | amount (cents) | Debit amount | Expensify/Ramp/Brex use cents; divide by 100 |
| GL account | accountCode | category | gl_account (external_id) | Natural account code | Must match ERP chart exactly |
| Cost center | orgUnit1 | tag | cost_center (external_id) | Cost center dimension | Tag mapping varies by Expensify policy |
| Employee name | employeeName | submitterEmail | card_holder.full_name | Vendor/payee name | Some ERPs require employee as vendor record |
| Transaction date | transactionDate | created | user_transaction_time | Journal date | T&E captures swipe date; ERP may want posting date |
| Currency code | transactionCurrencyCode | currency | currency_code | Transaction currency | Multi-currency requires rate agreement |
| Payment type | paymentType | reimbursable flag | Card-only (always company-paid) | Credit account selection | Determines clearing account vs AP payable |
| Report ID | systemId | reportID | id | External reference | Critical for idempotent duplicate prevention |
Data Type Gotchas
- SAP Concur
journalAmountsign indicates debit (+) or credit (-). Expensify and Ramp always report positive amounts. [src1, src3] - Expensify amounts are in cents (integer). A $42.50 expense is
amount: 4250. Concur uses decimal. Always check the API docs for amount format. [src3, src4] - Date formats differ: Concur uses ISO 8601 with timezone; Expensify uses YYYY-MM-DD; Ramp uses ISO 8601. Normalize before posting. [src1]
- Multi-tag support in Expensify depends on policy config. A "tag" can be a colon-separated multi-level string like
DeptA:ProjectX:TaskY. [src3]
Error Handling & Failure Points
Common Error Codes
| Source | Code | Meaning | Cause | Resolution |
|---|---|---|---|---|
| SAP Concur | 401 | Unauthorized | Expired token or revoked refresh token | Re-authenticate; check refresh token (6 months non-use) |
| SAP Concur | 404 | Document not found | Report recalled or deleted after approval | Skip document; log for audit |
| Expensify | 500 | Server error | Concurrent export on same credentials | Wait 60s and retry; serialize exports |
| Brex/Ramp | 429 | Rate limit exceeded | Too many API calls per minute | Read Retry-After header; exponential backoff |
| ERP (NetSuite) | 409 | Duplicate record | Journal with same externalId already posted | Log as success (idempotent); do not retry |
| ERP (SAP) | BAPI error | GL account invalid | Mapped to inactive or blocked GL code | Update mapping; validate GL codes monthly |
| Any | Timeout | Request timed out | Large batch or ERP under load | Reduce batch size; circuit breaker |
Failure Points in Production
- Stale GL account mappings: Finance closes/renames GL accounts in ERP but forgets to update T&E platform. Fix:
Validate all GL codes against ERP chart of accounts weekly via automated sync.[src6] - Orphaned corporate card transactions: Card transactions arrive but are never matched to expense reports. Fix:
Set policy requiring card transactions reported within 60 days. Auto-create entries for transactions >90 days old.[src6, src7] - Exchange rate mismatch on multi-currency: T&E system captures rate at transaction date; ERP uses rate at posting date. Fix:
Configure both systems to use same rate source and timing. Add FX gain/loss line.[src7] - Approval workflow timeout: Report sits in queue for weeks. Fix:
Auto-delegate after 3 business days; auto-approve under $500 after 7 days with audit flag.[src7] - Duplicate journal postings after retry: Timeout was on response, not request. Fix:
Always use T&E document ID as externalId. Implement upsert-or-skip logic.[src1]
Anti-Patterns
Wrong: Posting Card Transactions Directly to GL Without Expense Report
// BAD -- posts raw card feed to GL, skipping approval, GL coding, and receipt matching
cardFeed.forEach(txn => {
postJournal({ debit: { account: "6200-000", amount: txn.amount },
credit: { account: "2100-200", amount: txn.amount } });
});
Correct: Card Transactions Flow Through T&E Platform First
// GOOD -- only approved, coded, policy-compliant reports export to ERP
const approved = await pollApprovedReports();
for (const report of approved) {
const journal = transformToJournal(report); // Proper GL codes per entry
validateGLCodes(journal);
await postToERP(journal); // externalId for idempotency
await confirmPosting(report.id); // Lock in T&E platform
}
Wrong: Real-Time GL Posting Per Individual Expense Entry
// BAD -- posts a journal for every expense line; thousands of tiny journals
onExpenseCreated(expense => { postJournal(transformSingleExpense(expense)); });
Correct: Batch-Post Approved Reports on a Schedule
// GOOD -- polls for approved reports every 30 min; batches for manageable volume
schedule("*/30 * * * *", async () => {
const approved = await pollApprovedReports();
const journals = approved.map(transformToJournal);
await batchPostToERP(journals.filter(validateGLCodes));
await confirmPostings(journals.map(j => j.reference));
});
Wrong: Hardcoding GL Account Mappings in Integration Code
// BAD -- requires code deployment to change GL mappings
const MAP = { "Airfare": "6200-100", "Hotel": "6200-200" };
function getGL(type) { return MAP[type] || "6999-999"; }
Correct: GL Mappings in Configurable Table Synced from ERP
// GOOD -- finance team can update without code deployment
async function getGL(type) {
const row = await db.query("SELECT gl_code FROM expense_gl_mapping WHERE expense_type = $1 AND active = true", [type]);
if (!row) throw new Error(`No active GL mapping for: ${type}`);
return row.gl_code;
}
Common Pitfalls
- Ignoring the clearing account balance: Should trend toward zero each month. Growing balance = card transactions settled but not matched to approved reports. Fix:
Monitor weekly. Aging > 30 days = investigate. > 90 days = write policy for auto-reporting.[src6] - Not validating GL codes before ERP posting: GL accounts get deactivated during annual chart maintenance, causing batch failures every January. Fix:
Sync GL code list from ERP to T&E platform monthly.[src7] - Treating all T&E amount formats the same: Concur uses decimal (42.50), Expensify/Ramp/Brex use cents (4250). Fix:
Write a normalization layer per platform with unit tests.[src1, src3] - Forgetting partial approvals: Some platforms allow managers to reject specific lines. Fix:
Check both report-level AND line-level approval status before export.[src1] - No idempotency on ERP posting: Network timeouts cause retries and duplicate journals. Fix:
Always set externalId using T&E document/report ID. Implement upsert-or-skip.[src7] - Skipping posting confirmation: Document stays in approved queue and gets re-exported. Fix:
Always close the loop with POST confirmation back to T&E after ERP posting.[src1]
Diagnostic Commands
# SAP Concur: Check for pending financial documents
curl -X GET "https://us2.api.concursolutions.com/financialintegration/fi/v4/companies/transactiontypes/expense/transactions?limit=10" \
-H "Authorization: Bearer YOUR_TOKEN"
# Expected: JSON array of pending docs; empty = all posted
# SAP Concur: Verify OAuth token validity
curl -X GET "https://us2.api.concursolutions.com/profile/v1/me" \
-H "Authorization: Bearer YOUR_TOKEN"
# Expected: 200 with user profile; 401 = token expired
# Expensify: Test credentials and list available reports
curl -X POST "https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations" \
-d 'requestJobDescription={"type":"get","credentials":{"partnerUserID":"YOUR_ID","partnerUserSecret":"YOUR_SECRET"},"inputSettings":{"type":"reportStatus"}}'
# Ramp: Check API and list recent transactions
curl -X GET "https://demo-api.ramp.com/developer/v1/transactions?page_size=5" \
-H "Authorization: Bearer YOUR_TOKEN"
# ERP (NetSuite): Verify GL account exists
curl -X GET "https://YOUR_ACCOUNT.suitetalk.api.netsuite.com/services/rest/record/v1/account?q=acctNumber IS '6200-100'" \
-H "Authorization: Bearer YOUR_TOKEN"
Version History & Compatibility
| Platform | API Version | Release Date | Status | Breaking Changes | Notes |
|---|---|---|---|---|---|
| SAP Concur | Financial Integration API v4 | 2023-06 | Current (GA) | Replaced v3 Extract API | v3 Extract still operational |
| SAP Concur | Expense Report API v4 | 2022-01 | Current (GA) | User v1 Form Fields sunset June 2026 | Migrate before deadline |
| Expensify | Integration Server v1 | 2015 | Current (GA) | None recent | Stable but limited additions |
| Navan | Expense API v1 | 2024 | Current (GA) | N/A | SFTP primary method |
| Brex | Accounting API v1 | 2025-01 | Current (GA) | Legacy API keys incompatible | Re-authenticate with OAuth 2.0 |
| Ramp | Developer API v1 | 2024-06 | Current (GA) | None | Accounting Agent added 2025 |
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Employee expense reports need GL journal posting | Vendor invoices / AP processing | AP Automation Integration playbook |
| Corporate card reconciliation against expense reports | Simple card-to-bank reconciliation without GL coding | Bank reconciliation in your ERP |
| Multi-level approval workflows gate GL posting | Single-person company, no approval needed | Direct card feed import in QBO/Xero |
| >50 employees submitting expenses monthly | <10 employees with occasional expenses | Manual entry (automation ROI is negative) |
| Multi-entity or multi-currency processing | Single entity, single currency, domestic only | Native T&E platform export (no custom integration) |
| Per diem, mileage, or GST/VAT reclaim needed | Standard reimbursement without tax complexity | Simpler T&E native export |
Cross-System Comparison
| Capability | SAP Concur | Expensify | Navan | Brex | Ramp |
|---|---|---|---|---|---|
| ERP Integration API | Financial Integration API v4 | Integration Server API | SFTP + limited API | Accounting API v1 (bidirectional) | Developer API v1 + Accounting Agent |
| Pre-built connectors | SAP, Oracle, NetSuite, D365, Sage | NetSuite, QBO, Xero, Sage | NetSuite, Sage Intacct, QBO | NetSuite, QBO, Xero, Sage | NetSuite, Oracle Fusion, Sage, QBO, Xero |
| Real-time sync | Near-real-time (poll) | On-demand (API call) | Batch (SFTP schedule) | Real-time (two-way) | Real-time (auto-sync) |
| AI auto-coding | SmartExpense (receipt OCR) | SmartScan (receipt OCR) | Limited | AI accounting (Jan 2026) | Accounting Agent (AI) |
| Corporate card | Card feed integration | Expensify Card | Navan Card | Brex Card (native) | Ramp Card (native) |
| Travel booking | Concur Travel (integrated) | No | Navan Travel (core) | No | No |
| Multi-entity | Yes (advanced config) | Yes (policy-level) | Yes | Yes | Yes |
| Multi-currency | Yes | Yes | Yes | Yes | Yes |
| Posting confirmation | Yes (API callback) | No (one-way export) | No (SFTP-based) | Yes (two-way sync) | Yes (sync status) |
| Best for | Large enterprise, SAP shops | SMB, simple workflows | Travel-heavy companies | Card-first, real-time finance | Card-first, AI-driven automation |
Important Caveats
- Brex was acquired by Capital One in January 2026, which may affect the Accounting API roadmap. Monitor vendor announcements.
- SAP Concur's User v1 Form Fields will be decommissioned June 30, 2026. Integrations using v1 form field references must migrate to v4 fields before this date.
- Pre-built ERP connectors handle 80% of use cases but often lack support for custom GL dimensions, project codes, or intercompany allocations. Budget for custom configuration.
- Corporate card reconciliation assumes the T&E platform receives ALL card transactions. Non-expense card spend (team purchases) pollutes the clearing account.
- Rate limits and API capabilities documented here reflect March 2026 status. All five platforms update APIs quarterly. Always verify against current developer documentation.