ml-connector
DATEVRamp

DATEV and Ramp integration

DATEV is the accounting system of record. Ramp runs corporate cards, bill pay, and expense management. Connecting the two means approved bills, paid bills, and settled card transactions captured in Ramp flow into DATEV as finished bookings without re-keying, and the invoice PDFs land in DATEV Unternehmen Online alongside them. ml-connector handles the very different mechanics on each side, building DATEV EXTF CSV files from Ramp records and submitting them as asynchronous jobs. Because DATEV bookings are write-only and its chart of accounts is not readable over the API, the account mapping is configured once and the ledger stays in DATEV.

How DATEV works

DATEV is not a conventional REST API. A REST surface exposes the client list through accounting:clients and document upload through accounting:documents, but finalized bookings are submitted as asynchronous file jobs: EXTF CSV files to the on-premise DATEV Rechnungswesen engine, or DXSO XML jobs to DATEV Unternehmen Online. Authentication is OAuth 2.0 Authorization Code with PKCE against login.datev.de, which means a tax advisor or their client must approve access interactively; there is no machine-to-machine flow, and access tokens expire after 15 minutes. DATEV has no webhooks, so every submitted job is tracked by polling its status endpoint. The chart of accounts cannot be read back over the API, and posted journal entries are write-only.

How Ramp works

Ramp exposes bills and invoices, vendors, purchase orders, card transactions, GL accounts, reimbursements, and users through its REST Developer API under https://api.ramp.com/developer/v1, with JSON bodies and cursor-based pagination. It authenticates with OAuth 2.0, and the Client Credentials flow is the right fit here because it is server-to-server and its access token lasts ten days. Ramp also pushes events by webhook, signed with HMAC-SHA256, for bill and transaction lifecycle changes such as bills.approved, bills.paid, and transactions.cleared. The bill and transaction sync_status fields track whether a record has been exported to the accounting system, and an attachment endpoint returns the original invoice PDF.

What moves between them

The flow runs from Ramp into DATEV. ml-connector reads approved and paid Ramp bills, and settled card transactions, and assembles them into DATEV EXTF CSV booking batches that are submitted as asynchronous jobs to the accounting engine, with each invoice PDF uploaded to DATEV Unternehmen Online through the documents API. Ramp vendors map to DATEV creditor accounts and Ramp GL codes map to DATEV account numbers, so every booking line carries a valid debit and credit account. The direction is one-way for bookings, since DATEV postings are write-only and cannot be read back; ml-connector instead writes the DATEV job result onto the Ramp record by marking its sync_status. Reference data such as the DATEV client ID and the account map is configured up front because the DATEV chart of accounts is not retrievable over the API.

How ml-connector handles it

ml-connector stores both credential sets encrypted. On the Ramp side it requests a Client Credentials token with bills, vendors, transactions, and accounting scopes and refreshes it well before the ten-day expiry. The DATEV side is harder: the Authorization Code with PKCE flow needs S256, a state value of at least twenty characters, and a nonce, and the consent screen is shown to the tax advisor, so the connector runs that interactive grant once and then silently refreshes the 15-minute access token using only the client_id. Ramp webhooks for bills.approved, bills.paid, and transactions.cleared act as triggers, but ml-connector always re-fetches the object over the GET endpoint because Ramp webhooks are non-authoritative, then it batches records into an EXTF CSV. The file must be UTF-8 with precomposed (NFC) characters or DATEV silently rejects it, and the connector generates a stable deterministic filename so a retry is treated as the same job rather than the duplicate error DATEV returns on a repeated filename. After submission it polls the DATEV job with backoff because there is no push, and it dedupes on the Ramp bill or transaction id and a BullMQ jobId so a re-read record is never booked twice. Document types are fetched per client before each upload rather than hardcoded, and Ramp AI-autocoded transactions are passed through as coded so a posting is not overwritten without review.

A real-world example

A 90-person engineering services firm in Munich keeps its books in DATEV through its Steuerberater and runs all card spend and supplier bills through Ramp. Before the integration, a bookkeeper exported approved bills and the month's card transactions from Ramp, re-typed each one into a DATEV booking batch, and uploaded the invoice PDFs by hand, which left postings days behind and the VAT codes inconsistent. With DATEV and Ramp connected, every approved bill and cleared card transaction is assembled into an EXTF batch and submitted to DATEV automatically, with the invoice PDF filed in DATEV Unternehmen Online and the Ramp record marked as synced. The bookkeeper reviews exceptions instead of re-keying, and the tax advisor sees current bookings without waiting for a month-end handoff.

What you can do

  • Post approved and paid Ramp bills into DATEV as EXTF booking batches mapped to the right creditor and GL accounts.
  • Book settled Ramp card transactions into DATEV with their accounting coding preserved.
  • Upload each Ramp invoice PDF into DATEV Unternehmen Online and fetch client-specific document types first.
  • Bridge Ramp server-to-server OAuth and the interactive DATEV login with PKCE that a tax advisor approves.
  • Submit asynchronous DATEV jobs, poll them to completion, and dedupe with stable filenames and a BullMQ jobId.

Questions

Which direction does data move between DATEV and Ramp?
The flow is Ramp into DATEV. Approved bills, paid bills, and settled card transactions move from Ramp into DATEV as EXTF booking batches, and the invoice PDFs are uploaded to DATEV Unternehmen Online. DATEV bookings are write-only and the chart of accounts is not readable over the API, so ml-connector does not read postings back; it instead marks each Ramp record as synced once the DATEV job completes.
How does the integration handle DATEV's interactive login when Ramp runs server to server?
Ramp uses the Client Credentials flow with no user present, but DATEV requires Authorization Code with PKCE and shows a consent screen to the tax advisor or their client. ml-connector runs the DATEV grant once interactively, capturing the refresh token, and afterwards renews the 15-minute access token silently using only the client_id. The two credential sets are stored encrypted and used independently on each call.
DATEV has no webhooks, so how does ml-connector know a booking succeeded?
DATEV booking imports are asynchronous: you submit an EXTF or DXSO job, receive a job ID, and there is no push notification. ml-connector polls the DATEV job status endpoint with exponential backoff until it reports complete or failed, and only then marks the Ramp record as synced. Ramp webhooks such as bills.paid are used on the other side purely as triggers to start a sync, not as confirmation.

Related integrations

Connect DATEV and Ramp

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

Get started