ml-connector
DATEVBrex

DATEV and Brex integration

DATEV is the accounting backend used by German tax advisors and their business clients. Brex runs corporate cards, expenses, and bill pay. Connecting the two moves coded card spend into the books without re-keying. Once Brex finishes coding a transaction and marks it ready for export, ml-connector turns that accounting record into a DATEV EXTF booking batch and submits it as a job, mapped to the correct account numbers and cost centers, with the receipt uploaded to DATEV Unternehmen Online. ml-connector handles the very different file-based and event-based APIs on each side and tracks each DATEV job until it finishes posting.

How DATEV works

DATEV is not a conventional REST API. The accounting engine (DATEV Rechnungswesen) is on-premise, with a cloud document layer (DATEV Unternehmen Online). REST products cover listing clients and uploading documents, but finalized bookings are submitted as asynchronous EXTF CSV file jobs, and booking suggestions as DXSO XML jobs, on tenant client IDs looked up first. Authentication is OAuth 2.0 Authorization Code with PKCE against login.datev.de; a real tax advisor login is required, there is no machine-to-machine flow, and access tokens last only 900 seconds. DATEV has no webhooks, so job status is read by polling, and the standard chart of accounts cannot be read back.

How Brex works

Brex is a spend management platform, not an ERP, so it has no journal-posting or chart-of-accounts engine of its own. Its REST API exposes settled card and cash transactions (read-only), expenses with receipts and coding, vendors and transfers, and accounting records that carry a gl_account_id, custom accounting fields, and an export_status that moves from READY_FOR_EXPORT to EXPORTED. Calls use a bearer token, either a single-tenant API token or an OAuth2 access token, list endpoints page by cursor, and POST mutations take an Idempotency-Key. Brex pushes Svix-signed webhooks, including ACCOUNTING_RECORD_READY_FOR_EXPORT, which fires when a transaction is coded and ready to post to the ledger.

What moves between them

The flow runs from Brex into DATEV. When Brex codes a transaction and fires ACCOUNTING_RECORD_READY_FOR_EXPORT, ml-connector fetches that accounting record, writes the expense line into DATEV as an EXTF booking batch with the matching debit and credit account numbers and cost centers, and polls the DATEV import job until it completes. The receipt attached to the Brex expense is uploaded to DATEV Unternehmen Online against the right document type. After DATEV accepts the job, ml-connector marks the Brex record export_status EXPORTED so it is not posted twice. Card and cash transactions in Brex are read-only and finalized DATEV bookings cannot be read back, so ml-connector never writes financial entries back into Brex.

How ml-connector handles it

ml-connector stores both credential sets encrypted. On the Brex side it sends the bearer token on every call and verifies each webhook by recomputing the Svix HMAC-SHA256 signature over the id, timestamp, and raw body before trusting an event. On the DATEV side it runs the interactive PKCE login once with the tax advisor, then refreshes the 900-second token automatically using the client_id alone, and looks up the DATEV client ID before any submission. Each Brex accounting record becomes an EXTF CSV line: amount, debit and credit accounts, document number, posting text, and cost center are written into the Buchungsstapel layout, with account numbers and cost centers mapped first because DATEV's chart of accounts cannot be read back and must be configured in advance. Brex merchant names rarely match DATEV creditor names exactly, so a fuzzy-match and caching layer resolves the vendor before the booking is built. The file is UTF-8 NFC normalized with a whitelisted filename so DATEV does not reject it, and a deterministic filename makes a retry safe since DATEV rejects a duplicate filename plus document-type pair. Because DATEV has no webhooks, ml-connector submits the job and polls the job endpoint with backoff until it posts or fails, then writes export_status EXPORTED back to Brex. A scheduled cursor poll of accounting records backfills anything a missed webhook left behind, Brex 429 responses are retried with backoff, and every record carries a full audit trail and can be replayed if a call fails.

A real-world example

A German software company with around 90 employees keeps its books in DATEV through its Steuerberater and runs all company spend on Brex corporate cards across engineering and sales. Before the integration, an accountant exported the Brex transaction list each month, matched every charge to the right SKR account and Kostenstelle, typed the postings into DATEV by hand, and chased down receipts for the ones that were missing one. With DATEV and Brex connected, each coded Brex transaction is written into DATEV as a booking batch as soon as it is ready, allocated to the cost center for the spending team, and the receipt lands in DATEV Unternehmen Online with it. The manual re-keying and receipt chasing are gone, and the card accounts are already in agreement when month-end close begins.

What you can do

  • Write coded Brex accounting records into DATEV as EXTF booking batches, then poll the job until it posts.
  • Upload the receipt attached to each Brex expense into DATEV Unternehmen Online against the correct client document type.
  • Map Brex merchants, accounting fields, and gl_account_id to DATEV SKR account numbers and Kostenstellen so spend lands on valid dimensions.
  • Authenticate Brex with its bearer token and verify Svix-signed webhooks, while DATEV uses interactive PKCE login plus automatic token refresh.
  • Trigger on the ACCOUNTING_RECORD_READY_FOR_EXPORT webhook with a scheduled cursor backfill, retries, and a full audit trail on every record.

Questions

Which direction does data move between DATEV and Brex?
The flow is Brex into DATEV. Coded card transactions move from Brex into DATEV as booking batches, and the attached receipts are uploaded to DATEV Unternehmen Online. Brex card and cash transactions are read-only and finalized DATEV bookings cannot be read back, so ml-connector does not write financial entries back into Brex beyond marking each record EXPORTED.
How does the integration know when a Brex transaction is ready to post?
Brex fires the ACCOUNTING_RECORD_READY_FOR_EXPORT webhook once it has finished coding a transaction, signed with a Svix HMAC-SHA256 signature that ml-connector verifies before acting. On that event it fetches the accounting record, builds the DATEV booking batch, and submits it. A scheduled cursor poll of accounting records backfills anything a missed webhook left behind.
How does the integration handle DATEV's short token and missing chart of accounts?
DATEV access tokens last only 900 seconds, so ml-connector refreshes them automatically using the client_id alone after the one-time tax advisor login. Because DATEV does not expose its standard chart of accounts over the API, the SKR account numbers and cost centers are configured in advance and Brex values are mapped to them, so every booking line references a valid account.

Related integrations

Connect DATEV and Brex

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

Get started