title: ERP Integrations source_url: /developer-api/v1/erp-integrations summary: A working two-way sync between Ramp and your customer's ERP: chart of accounts mirrored from the ERP into Ramp, and transactions, bills, reimbursements, and payments exported back from Ramp once they're ready. content: A working two-way sync between Ramp and your customer's ERP: chart of accounts mirrored from the ERP into Ramp, and transactions, bills, reimbursements, and payments exported back from Ramp once they're ready. An API-based accounting connection — distinct from Ramp's direct integrations (QuickBooks, NetSuite, etc.) — that gives your platform full control over the data exchange. Your service pushes the customer's chart of accounts into Ramp so users can code spend against it, then pulls ready-to-sync objects (transactions, bills, reimbursements, transfers, cashbacks) back out for posting in the ERP. The connection is one customer to one provider; multi-entity customers scope reads with entity_id. End users keep working in Ramp's UI; coding selections you uploaded show up as standard fields on every transaction. Estimated time: 1–2 days for a minimal connection; 1–2 weeks including tax codes, vendor credits, sync button, and production hardening. Prerequisite knowledge: OAuth 2.0 client credentials, webhooks, your ERP's chart-of-accounts model. Required: Ramp account or sandbox, an OAuth app with accounting:read, accounting:write, vendors:read. Reimbursement and transaction flows additionally need reimbursements:read and transactions:read. Recommended: read Bill Pay for the bill and vendor objects you'll be syncing, and Data relationships for how Vendors, Merchants, and Accounting Vendors connect at coding time. Prerequisites: a remote ERP system you can write to, and a webhook receiver if you want event-driven sync. Which objects to sync. Bills, transactions, reimbursements, transfers, and cashbacks all use the same sync_status model. Start with whichever your customers code most; add the rest later — the wiring is identical. Webhooks vs. polling. Webhooks (*.ready_to_sync) push within seconds. Polling (sync_ready=true) is simpler to operate and good enough at 1–4 hours weekdays / 12 hours weekends. Most integrations begin with polling and add webhooks once volume justifies it. Sync-button opt-in. When enabled, customers can trigger syncs from the Ramp UI and you must respond within 5 minutes — only opt in once your worker can guarantee that latency. New build vs. migration. The Implementation steps below assume a fresh, active connection — the right path for new customers. If you're switching a customer off a direct integration (QuickBooks, NetSuite, etc.), skip ahead to Migration tool instead; it's a complete linear walkthrough that preserves historical codings during cutover. The bidirectional sync lifecycle, end-to-end: Implementation Open a Developer-API accounting connection. The remote_provider_name is what your customer sees in Ramp when picking providers. Check for an existing connection with GET /developer/v1/accounting/all-connections; remove one with DELETE /developer/v1/accounting/connection. Disconnecting unlinks the connection Use this before changing providers. See Migration tool for guidance on preserving data during connection changes. Verify: the response includes an id you can GET back with status: "linked". The connection is now live — move on to step 2. Push the ERP's coding surface into Ramp so users can apply it. All four uploads accept batches up to 500, all-or-nothing per batch. GL accounts Required POST /accounting/accounts Custom fields (e.g. Department) Optional POST /accounting/fields Custom field options POST /accounting/field-options Accounting vendors POST /accounting/vendors Entities (subsidiaries) UI only Read-only via API; reads accept entity_id filter Chart of Accounts cannot be modified for Ramp accounts using a direct accounting connection This applies to GL accounts, custom fields, and vendors — they become read-only via API when a direct connection is active. Custom fields and options Custom fields let you import ERP classifications like Department, Cost Center, or Location. Create the field first, then upload its options using the returned ramp_id. Lifecycle: delete vs. hide To remove a GL account, field option, or vendor from cardholder selection, DELETE it (sets is_active: false; reactivate with PATCH { "reactivate": true }). Use PATCH { "visibility": "HIDDEN" } on a field option to hide it from new coding while still syncing historical values. Use when You never want to sync this item again You want to stop new coding but preserve historical values is_active Set to false Remains true Visible to cardholders No Existing codings preserved Yes, but item cannot be synced Yes, and item can still be synced How to reverse PATCH { "reactivate": true }; for custom fields, POST the same field ID PATCH { "visibility": "VISIBLE" } Applies to Fields, field options, GL accounts, vendors Field options only Prefer hide over delete for field options Hiding is less disruptive because it keeps the option active for sync purposes. Only DELETE when you are certain the item should be fully deactivated. Batch upload limits All batch operations use all-or-nothing processing — if any item in a batch fails validation, the entire batch is rejected. Limits apply per API call, not as totals; for 2,000 field options you'd make four 500-option calls. GL Accounts 500 accounts Accounting Vendors 500 vendors Custom Field Options 500 options Accounting Syncs 5,000 successful syncs or 5,000 failed syncs Multi-entity: entities (subsidiaries) are created and managed directly in the Ramp UI. When fetching objects via the API, scope requests to a specific entity using the entity_id parameter. See Ramp support for multi-entity businesses. Accounting vendors are ERP-side identifiers; Ramp vendors are the merchant records cardholders see. After step 2, link them so transactions and bills carry the right remote ID. 1. Create or update the accounting vendor (already covered in step 2 — repeated here for the full linking flow): 2. Link the Ramp vendor to the accounting vendor via accounting_vendor_remote_id on the Ramp vendor: 3. (Optional) Set a vendor code on the accounting vendor when your ERP expects a separate code distinct from the remote ID: See Data relationships for the full vendor / merchant / accounting-vendor data model. Tax code endpoints are in Beta. Endpoints may receive breaking changes (announced in the Changelog). Tax codes attach tax-rate selections to line items. Setup is strictly ordered — rates, then field, then options. After upload, a Tax Code field appears on transaction coding. Tax selections come back in line-item accounting_field_selections with "type": "TAX_CODE". Vendor credits are in Beta. Endpoints may receive breaking changes (announced in the Changelog). Vendor credits let users apply credit memos against bills. Enable per-connection — once on each connection you operate. Users create credits in the Ramp UI: Vendors → pick a vendor → New → Vendor Credit, then fill in the credit details. Fetch credits via GET /vendors/credits, scope to one vendor with GET /vendors/{vendor_id}/credits, or fetch a single credit with GET /vendors/credits/{credit_id}. Response shape: Objects become eligible for export via different paths — some automatic, some after a user marks them ready in the Ramp UI. Bills Approved (or created, depending on settings) and not yet synced Bill payments Bill is fully paid (status: PAID) Transfers, cashbacks Automatic Transactions, reimbursements User marks ready in Ramp UI Direct accounting connections are blocked by default API sync marking is intended for API-based accounting connections. Direct accounting connections cannot mark objects as synced via API. The sync loop is the same regardless of object type: fetch ready-to-sync objects, post them into the ERP, then mark them synced in Ramp. Fetch endpoints Transactions GET /transactions?sync_status=SYNC_READY Reimbursements GET /reimbursements?sync_status=SYNC_READY Transfers GET /transfers?sync_status=SYNC_READY Cashbacks GET /cashbacks?sync_status=SYNC_READY GET /bills?sync_ready=true Post results to POST /accounting/syncs with an idempotency_key, a sync_type (TRANSACTION_SYNC, BILL_SYNC, BILL_PAYMENT_SYNC, REIMBURSEMENT_SYNC, etc.), and either successful_syncs (with each id + your reference_id) or failed_syncs (with id + error.message). Example: transaction response shape. A GET /transactions?sync_status=SYNC_READY response carries everything you need to post into the ERP — accounting categories, line items, and field selections: Automatic Accounting Vendor creation For API-based connections, you can require your integration to create or map missing ERP vendors before a transaction is reported as synced. Enable this by setting transaction_accounting_vendor_creation_on_sync_enabled: true on the accounting connection settings. When this setting is enabled, transaction responses include requires_accounting_vendor_creation_to_sync: true if the transaction is ready to sync but does not have an Accounting Vendor selection. Treat that as a blocker for successful TRANSACTION_SYNC: Create the vendor in the customer's ERP using the transaction merchant details. Upload the ERP vendor to Ramp with POST /accounting/vendors. Code the transaction to that Accounting Vendor with POST /developer/v1/accounting/codings, using object_type: "TRANSACTION", the transaction ID as object_id, and an accounting_field_selections entry for the Accounting Vendor. Optional: re-fetch the transaction and confirm requires_accounting_vendor_creation_to_sync is no longer true. Mark the transaction as synced in Ramp using the POST /developer/v1/accounting/syncs endpoint. Uploading the Accounting Vendor alone does not attach it to the transaction. Ramp checks the transaction's Accounting Vendor coding before accepting a successful transaction sync. Mark the transaction synced once it lands in the ERP: Bills are two-phase. A bill carries a payment, and each phase syncs separately. sync_ready=true returns anything with outstanding export work; decide what to post using sync_status + the bill's status: true NOT_SYNCED OPEN BILL_SYNC only PAID BILL_SYNC, then BILL_PAYMENT_SYNC BILL_SYNCED BILL_PAYMENT_SYNC only sync_ready is a query filter, not a bill response field. A bill with sync_status=NOT_SYNCED can already be paid, so filtering only on sync_status will miss cases where both phases need to sync. To narrow to payment-only candidates, query GET /developer/v1/bills?sync_status=BILL_SYNCED&sync_ready=true. Errors. Use failed_syncs for anything the user needs to see. Categorize for clarity: User-actionable Title + how to fix "Accounting period closed. Reopen in NetSuite and retry." Transient Title + retry prompt "ERP unavailable. We'll retry shortly." Needs integrator Title + your support contact + reference ID "Unexpected sync error. Contact support@example.com, ref abc123." Failures surface in the Accounting tab as Export Errors: Polling vs. webhooks. Both work; pick based on latency requirements. Polling. Periodically hit the fetch endpoints above. Recommended cadence: every 1–4 hours on weekdays, every 12 hours on weekends. Webhooks. Subscribe to transactions.ready_to_sync, reimbursements.ready_to_sync, and bills.ready_to_sync for push-based sync within seconds. See Webhooks for subscription and signature verification. Block UI exports The Ramp UI's manual Export button marks objects as synced without notifying you. Train customers on API-based connections to never click it. Sync Button is in Alpha. Flow and endpoints may receive breaking changes (announced in the Changelog). Once your worker is fast enough to round-trip syncs in under 5 minutes, opt customers into the Sync Button so they can trigger syncs from Accounting → Transactions or Accounting → Reimbursements. When the customer clicks Sync All, Ramp fires a *.sync_requested webhook to your service, you fetch the requested rows, post them into the ERP, and call back into POST /accounting/syncs — the Ramp UI then renders live progress and a summary. The pattern is identical for transactions and reimbursements — only the event name, settings flag, fetch query, and sync type differ: Webhook event transactions.sync_requested reimbursements.sync_requested Settings flag transaction_sync_button_enabled reimbursement_sync_button_enabled Fetch query GET /transactions?requested_to_sync=true GET /reimbursements?requested_to_sync=true Sync type TRANSACTION_SYNC REIMBURSEMENT_SYNC Setup steps (per flow): Subscribe to the webhook event via POST /webhooks, then verify the challenge at POST /webhooks/{id}/verify. Enable the button on the connection by setting the flag in settings (via POST /accounting/connection for new connections or PATCH /accounting/connection/{id} for existing). Respond. On webhook receipt, grab sync_request_id from object, fetch the requested rows, post them into the ERP, then POST /accounting/syncs including sync_request_id in the body. You can make multiple /accounting/syncs calls for one sync_request_id; the UI updates live. 5-minute response window Anything not posted back within 5 minutes is auto-marked as failed. Don't enable the button until your worker can guarantee that. Migration tool For customers switching off a direct integration (QuickBooks, NetSuite, etc.). The flow creates an inactive connection, preloads the customer's chart of accounts against it, marks it ready, and the customer flips to it from the Ramp UI — preserving historical codings against the new provider on day one. Sync outstanding data. Mark every ready-to-sync object on the old connection (transactions, bills, reimbursements) as synced before cutover — anything left ready won't survive the switch. Document rules and automations. Export or note all accounting rules and automations against the old connection; they must be recreated after cutover. See the "Accounting rules and automation" article in the Ramp support center. Back up the chart of accounts. Fetch and store the customer's current GL accounts, custom fields, and vendors via the API before disconnecting — keep a copy in case rollback is needed. Migrating accounting connections drops historical chart-of-accounts codings Fetch and store the customer's existing GL accounts, custom fields, and vendors before initiating a migration. Multiple accounting connections cannot be active simultaneously. Set is_active: false. Save the returned id — every preload call below scopes to it via accounting_connection_id. Response — verify status: UNLINKED and connection_type: API before continuing: Mirror the customer's existing direct-integration chart of accounts against the inactive connection. The endpoints and body shapes match the main flow's step 2 — POST /accounting/accounts, /accounting/fields, /accounting/field-options, /accounting/vendors — except every body must include accounting_connection_id so writes land on the inactive connection rather than the customer's still-active direct integration. If the customer uses tax codes (step 4) or vendor credits (step 5), configure those against the inactive connection now — both flows accept accounting_connection_id the same way. Preconditions: The accounting_connection_id must refer to an inactive API-based connection. The customer must have a separate active accounting connection to migrate from. The inactive connection must not already be marked ready to migrate. The customer cannot already have another ERP migration in progress. The response returns the accounting connection with is_ready_to_migrate set to true, and the inactive connection becomes selectable in the customer's Ramp UI under Switch Providers. The customer navigates to Accounting → Settings → Danger Zone → Switch Providers, picks the new provider by the remote_provider_name you set above, and maps fields from the old connection onto the new one. Once the customer confirms, the inactive connection becomes the active accounting integration. Begin syncing per step 6. Webhooks: subscribe to *.ready_to_sync for push delivery; verify the challenge and signature per Webhooks. Retries: every POST /accounting/syncs carries an idempotency_key. Reuse it on retry — Ramp dedupes server-side. Rate limits: see Rate limits. Stagger chart-of-accounts uploads across customers if you onboard in bulk. Multi-entity reads: scope every list call with entity_id when a customer has subsidiaries. See Ramp support for multi-entity businesses. Monitoring: alert on backlog of sync_status=SYNC_READY over a threshold (sync worker stuck) and failed_syncs per minute (ERP integration broken). Encryption: all Ramp traffic is HTTPS; add payload-level encryption only if your customer contracts require it. Bill Payments — create and pay bills end-to-end; bills then sync through this pipeline. Procurement Intake — receive intake requests and return external approval decisions; downstream POs and bills sync through the same pipeline as transactions. Bill Pay — bills and bill-payment objects. Data relationships — how vendor identity flows between card transactions, bill-pay vendors, and ERP accounting vendors. Cards — transactions originate here. Spend Controls — funds, spend programs, and approvals that gate the spend before it lands in your GL. Reimbursements — out-of-pocket expense objects. Accounting Connection — understand and manage the current state of an account's accounting connection. Accounting Custom Fields — fields used to code transactions, reimbursements, and bills. Accounting GL Accounts — GL accounts used for coding transactions, reimbursements, bills, and purchase orders. Accounting Vendors — vendor options for coding transactions, reimbursements, bills, and purchase orders. Accounting Sync — mark objects as synced after successful addition to the remote accounting system. Accounting Tax — tax rates, codes, and code options. Entities — view business entities associated with a Ramp account. Transactions — fetch accounting codings and metadata from card transactions. Bills — fetch accounting codings and metadata from bills and payments. Reimbursements — fetch accounting codings and metadata from employee reimbursements. Cashbacks — fetch accounting codings and metadata from cashback earned via Ramp cards. Statements — fetch payments for Ramp card statements. Purchase Orders — fetch and create purchase orders. Vendors — manage Ramp vendors and their accounting-vendor links. Webhooks — subscribe to *.ready_to_sync events. Pagination — paginate list endpoints across multi-entity accounts. Rate limits — stagger bulk chart-of-accounts uploads. Deferred tasks — poll deferred sync tasks to completion. FAQ Yes. POs can be fetched and created via the API regardless of accounting connection type — they're a separate feature available to all users with API access. No. Only one connection can be active at a time; this is the known limitation that motivates the Migration tool flow. No. All historical objects are available via API. Yes. Payment details are nested within the bill object. Query bills with sync_ready=true, then use sync_status and the returned bill status together to decide whether to sync the bill, the payment, or both. Yes. In Ramp, users authorize your app with the necessary scopes. In the remote system, users often need to configure endpoints, set up API access, or install connectors to accept data from Ramp. Yes. All API traffic is HTTPS; data at rest is encrypted in Ramp systems. Add payload-level encryption only if customer contracts require it. No. Apps must run on your own infrastructure and integrate with Ramp via API. Submit a Developer API support ticket and our team will follow up.