T&E System-to-ERP Integration Playbook: Concur, Expensify, Navan, Brex & Ramp

Type: ERP Integration System: SAP Concur, Expensify, Navan, Brex, Ramp Confidence: 0.86 Sources: 8 Verified: 2026-03-03 Freshness: 2026-03-03

TL;DR

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.

SystemRoleAPI SurfaceDirection
SAP ConcurT&E platform -- expense reports, travel booking, receipt captureFinancial Integration API v4 (REST)Outbound to ERP
ExpensifyT&E platform -- expense reports, corporate card, receipt scanningIntegration Server API (REST)Outbound to ERP
NavanT&E + travel platform -- expense, travel booking, corporate cardExpense API v1, SFTPOutbound to ERP
BrexCorporate card + expense -- card transactions, receipt matchingAccounting API v1 (REST)Bidirectional with ERP
RampCorporate card + expense -- card transactions, AP automationDeveloper API v1 (REST)Bidirectional with ERP
ERP (Target)Financial system of record -- GL, AP, ARVaries (REST, OData, SOAP, File Import)Inbound from T&E
iPaaS (Optional)Integration orchestrator -- Workato, Boomi, MuleSoft, CeligoVariesOrchestrator

API Surfaces & Capabilities

T&E PlatformExport MethodFormatReal-time?Pre-built ERP ConnectorsCustom API?
SAP ConcurFinancial Integration API v4JSONNear-real-time (poll)SAP S/4HANA, Oracle, NetSuite, Dynamics 365, Sage IntacctYes
SAP ConcurStandard Accounting Extract (SAE)Flat fileBatch (scheduled)Any ERP with file importN/A
ExpensifyIntegration Server APIJSONOn-demandNetSuite, QBO, Xero, Sage Intacct, OracleYes
NavanDirect IntegrationJSONNear-real-timeNetSuite, Sage Intacct, QBOLimited
NavanSFTP ExportCSV/JSONBatch (scheduled)Any ERP with file importN/A
BrexAccounting API v1JSONReal-time (two-way)NetSuite, QBO, Xero, Sage IntacctYes
RampDeveloper API v1JSONReal-time (sync)NetSuite, Oracle Fusion, Sage Intacct, QBO, XeroYes

Rate Limits & Quotas

Per-Request Limits

T&E PlatformLimit TypeValueNotes
SAP ConcurMax financial documents per fetch100 per pagePaginate with nextPage link
SAP ConcurExpense Report API v4 query100 reports per pageUse start parameter for pagination
ExpensifyMax report export batch1 concurrent request per credential pairQueue exports sequentially
BrexMax transactions per page1,000Cursor-based pagination
RampMax records per page100Cursor-based pagination
RampAccounting sync batch500 GL codes per syncSplit larger code sets

Rolling / Daily Limits

T&E PlatformLimit TypeValueWindowNotes
SAP ConcurAPI calls (OAuth app)24,000 requests/hrRolling hourlyShared across all API surfaces
ExpensifyAPI requestsNo published hard limitN/ASubject to fair-use throttling
BrexAPI callsRate-limited per endpointPer minute429 response with retry-after header
RampAPI callsRate-limited per endpointPer minute429 response with retry-after header
NavanSFTP export frequencyConfigurable (min 1hr)ScheduledDirect API limits not publicly documented

Authentication

T&E PlatformAuth MethodMechanismToken LifetimeRefresh?Notes
SAP ConcurOAuth 2.0JWT bearer (company-level) or Auth Code (user-level)Access: 1h, Refresh: 6 monthsYesCompany JWT for Financial Integration API
ExpensifyStatic credentialspartnerUserID + partnerUserSecretUnlimitedNoOne pair per integration
NavanAPI KeyBearer token in headerLong-livedNoIssued by Navan support
BrexOAuth 2.0Authorization Code flowAccess: 1hYesScopes: accounting.read, accounting.write
RampOAuth 2.0Authorization Code flowAccess: 1hYesScopes: accounting:read, accounting:write

Authentication Gotchas

Constraints

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

StepSourceActionTargetData ObjectsFailure Handling
1EmployeeCapture receipt, create expense lineT&E PlatformExpense entry, receipt imageAuto-retry OCR; manual entry fallback
2T&E PlatformApply policy rules, auto-code GL accountT&E PlatformExpense type → GL mappingFlag out-of-policy items for review
3T&E PlatformRoute for approvalT&E PlatformApproval workflow, delegationEscalation after SLA breach
4T&E PlatformMark report approved, lock for exportT&E PlatformApproved expense reportReopen if rejected
5T&E APIExport approved report as financial documentiPaaS / CustomJournal lines (debit/credit)Retry 3x, then DLQ
6iPaaS / CustomTransform T&E data to ERP journal formatERPGL journal entryValidate GL codes before posting
7ERPPost journal entry, create payableERPGL journal, AP voucherDuplicate check on external ref ID
8ERPProcess reimbursementBank / PayrollPayment instructionReconcile against posted journal
9iPaaS / CustomPost confirmation back to T&E platformT&E PlatformPosting status, ERP doc IDUpdate status to posted/failed
10Card NetworkFeed corporate card transactionsT&E PlatformCard transaction dataMatch 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 ConcurExpensifyRamp/BrexERP TargetGotcha
Expense amountjournalAmountamount (cents)amount (cents)Debit amountExpensify/Ramp/Brex use cents; divide by 100
GL accountaccountCodecategorygl_account (external_id)Natural account codeMust match ERP chart exactly
Cost centerorgUnit1tagcost_center (external_id)Cost center dimensionTag mapping varies by Expensify policy
Employee nameemployeeNamesubmitterEmailcard_holder.full_nameVendor/payee nameSome ERPs require employee as vendor record
Transaction datetransactionDatecreateduser_transaction_timeJournal dateT&E captures swipe date; ERP may want posting date
Currency codetransactionCurrencyCodecurrencycurrency_codeTransaction currencyMulti-currency requires rate agreement
Payment typepaymentTypereimbursable flagCard-only (always company-paid)Credit account selectionDetermines clearing account vs AP payable
Report IDsystemIdreportIDidExternal referenceCritical for idempotent duplicate prevention

Data Type Gotchas

Error Handling & Failure Points

Common Error Codes

SourceCodeMeaningCauseResolution
SAP Concur401UnauthorizedExpired token or revoked refresh tokenRe-authenticate; check refresh token (6 months non-use)
SAP Concur404Document not foundReport recalled or deleted after approvalSkip document; log for audit
Expensify500Server errorConcurrent export on same credentialsWait 60s and retry; serialize exports
Brex/Ramp429Rate limit exceededToo many API calls per minuteRead Retry-After header; exponential backoff
ERP (NetSuite)409Duplicate recordJournal with same externalId already postedLog as success (idempotent); do not retry
ERP (SAP)BAPI errorGL account invalidMapped to inactive or blocked GL codeUpdate mapping; validate GL codes monthly
AnyTimeoutRequest timed outLarge batch or ERP under loadReduce batch size; circuit breaker

Failure Points in Production

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

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

PlatformAPI VersionRelease DateStatusBreaking ChangesNotes
SAP ConcurFinancial Integration API v42023-06Current (GA)Replaced v3 Extract APIv3 Extract still operational
SAP ConcurExpense Report API v42022-01Current (GA)User v1 Form Fields sunset June 2026Migrate before deadline
ExpensifyIntegration Server v12015Current (GA)None recentStable but limited additions
NavanExpense API v12024Current (GA)N/ASFTP primary method
BrexAccounting API v12025-01Current (GA)Legacy API keys incompatibleRe-authenticate with OAuth 2.0
RampDeveloper API v12024-06Current (GA)NoneAccounting Agent added 2025

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Employee expense reports need GL journal postingVendor invoices / AP processingAP Automation Integration playbook
Corporate card reconciliation against expense reportsSimple card-to-bank reconciliation without GL codingBank reconciliation in your ERP
Multi-level approval workflows gate GL postingSingle-person company, no approval neededDirect card feed import in QBO/Xero
>50 employees submitting expenses monthly<10 employees with occasional expensesManual entry (automation ROI is negative)
Multi-entity or multi-currency processingSingle entity, single currency, domestic onlyNative T&E platform export (no custom integration)
Per diem, mileage, or GST/VAT reclaim neededStandard reimbursement without tax complexitySimpler T&E native export

Cross-System Comparison

CapabilitySAP ConcurExpensifyNavanBrexRamp
ERP Integration APIFinancial Integration API v4Integration Server APISFTP + limited APIAccounting API v1 (bidirectional)Developer API v1 + Accounting Agent
Pre-built connectorsSAP, Oracle, NetSuite, D365, SageNetSuite, QBO, Xero, SageNetSuite, Sage Intacct, QBONetSuite, QBO, Xero, SageNetSuite, Oracle Fusion, Sage, QBO, Xero
Real-time syncNear-real-time (poll)On-demand (API call)Batch (SFTP schedule)Real-time (two-way)Real-time (auto-sync)
AI auto-codingSmartExpense (receipt OCR)SmartScan (receipt OCR)LimitedAI accounting (Jan 2026)Accounting Agent (AI)
Corporate cardCard feed integrationExpensify CardNavan CardBrex Card (native)Ramp Card (native)
Travel bookingConcur Travel (integrated)NoNavan Travel (core)NoNo
Multi-entityYes (advanced config)Yes (policy-level)YesYesYes
Multi-currencyYesYesYesYesYes
Posting confirmationYes (API callback)No (one-way export)No (SFTP-based)Yes (two-way sync)Yes (sync status)
Best forLarge enterprise, SAP shopsSMB, simple workflowsTravel-heavy companiesCard-first, real-time financeCard-first, AI-driven automation

Important Caveats

Related Units