ml-connector
Oracle NetSuiteBrex

Oracle NetSuite and Brex integration

Oracle NetSuite runs your accounting and ERP. Brex runs corporate cards, expenses, and bill pay. Connecting the two gets coded spend out of Brex and into your ledger without manual entry. After Brex finishes coding a card or cash transaction, that record posts into Oracle NetSuite as a vendor bill or GL transaction against the right account and accounting dimension. ml-connector bridges the two very different authentication models and keeps the chart of accounts and dimensions in step so every Brex charge lands on a valid NetSuite account.

How Oracle NetSuite works

Oracle NetSuite exposes vendors, vendor bills, purchase orders, vendor payments, GL accounts, and the department, class, and location dimensions through SuiteTalk REST Web Services on an account-specific URL, so there is no shared API host. The recommended auth is OAuth 2.0 Client Credentials (M2M), a certificate-based flow where the connector signs a JWT with a private key and the customer uploads the matching public certificate; there is no client secret. SuiteQL, a SQL-like query endpoint, is used for bulk and filtered reads because the record collection API is slower. NetSuite also offers native Event Subscriptions that push record changes, though they carry no built-in signature header.

How Brex works

Brex exposes card and cash transactions as read-only, plus expenses, vendors, transfers, and coded accounting records as read and write, through its REST Developer API. Most installs authenticate with a single static bearer token created in the Brex dashboard; a multi-tenant OAuth 2.0 mode also exists. The Accounting API is the ERP-export surface: each record carries a gl_account_id, accounting_fields for dimensions, and an export_status that moves from READY_FOR_EXPORT to EXPORTED. Brex sends webhooks signed with Svix, including ACCOUNTING_RECORD_READY_FOR_EXPORT, which fires once a transaction is fully coded. The Fields API holds the custom GL dimensions that map to ERP segments.

What moves between them

The main flow runs from Brex into Oracle NetSuite. When Brex finishes coding a card or cash charge, ml-connector reads the accounting record and posts it into NetSuite as a vendor bill or GL transaction, mapped to the gl_account_id and the department, class, or location dimension, then sets the Brex record to EXPORTED so it is not posted twice. Reference data moves the other way: the NetSuite chart of accounts and the department, class, and location lists are pushed into Brex Fields so coders only pick valid segments. Vendor records sync from NetSuite into the Brex vendor directory to support bill pay. Settled NetSuite ledger entries are never written back into Brex transactions, which are read-only.

How ml-connector handles it

ml-connector stores both credential sets encrypted. For NetSuite it signs a JWT with the customer private key, requests a 60-minute OAuth token from the account-specific URL, and refreshes when a call returns 401; it accepts the full account ID per customer because NetSuite has no shared host. For Brex it sends the bearer token and watches the 90-day inactivity expiry. The preferred trigger is the Brex ACCOUNTING_RECORD_READY_FOR_EXPORT webhook, verified with the Svix signing secret; where webhooks are not enabled it polls the accounting records endpoint on a schedule. Brex gl_account_id maps to a NetSuite GL account, and accounting_fields map to department, class, or location, so dimensions are aligned before any posting runs. NetSuite needs externalId for idempotent upserts and a subsidiary on multi-subsidiary accounts, and Brex transfers require an Idempotency-Key, so duplicate deliveries post only once. Both sides return HTTP 429 under load, so ml-connector backs off with jitter and retries, and merchant-to-vendor matching is treated as fuzzy because Brex merchant names rarely equal NetSuite vendor names.

A real-world example

A venture-backed software company of about 150 people runs Oracle NetSuite for accounting and gives every team a Brex corporate card for software, travel, and ad spend. Before the integration, an accountant exported coded transactions from Brex each month and keyed them into NetSuite one vendor bill at a time, guessing at the right expense account and department for each charge, which dragged out month-end close and produced miscoded entries. With Oracle NetSuite and Brex connected, each coded charge posts into the ledger as it is finished, already tagged to the correct GL account and department, and the chart of accounts the team picks from in Brex matches NetSuite exactly. Close starts with spend already booked, and the manual re-keying step is gone.

What you can do

  • Post Brex coded card and cash charges into Oracle NetSuite as vendor bills or GL transactions on the right account.
  • Map Brex accounting_fields to NetSuite department, class, and location so every charge lands on a valid dimension.
  • Sync the NetSuite chart of accounts and dimension lists into Brex Fields so coders only pick segments that exist.
  • Bridge the Brex bearer token and the NetSuite OAuth 2.0 client-credentials certificate flow in one connection.
  • Trigger on the Brex ready-for-export webhook or poll on a schedule, then mark records exported to prevent duplicates.

Questions

Which direction does data move between Oracle NetSuite and Brex?
The main flow is Brex into Oracle NetSuite. Coded card and cash charges post from Brex into NetSuite as vendor bills or GL transactions, while the chart of accounts, dimensions, and vendor list sync from NetSuite into Brex. Brex card and cash transactions are read-only, so ml-connector never writes ledger entries back into Brex spend records.
How does ml-connector know when a Brex charge is ready to post?
Brex sends an ACCOUNTING_RECORD_READY_FOR_EXPORT webhook, signed with Svix, once a transaction is fully coded. ml-connector verifies that signature, reads the matching accounting record, and posts it into NetSuite. Where webhooks are not enabled it polls the Brex accounting records endpoint on the schedule you set instead.
How are duplicate postings prevented?
After ml-connector posts a record into NetSuite it sets the Brex export_status to EXPORTED, so the same charge is not exported again. On the NetSuite side it posts with an externalId so a retried write upserts the existing record rather than creating a second one. Both systems can return HTTP 429 under load, so failed calls are retried with exponential backoff and replayed from the audit trail if needed.

Related integrations

Connect Oracle NetSuite and Brex

Free to use. Add your credentials, ping your real systems, and see if we fit.

Get started