ml-connector
DATEVCoupa

DATEV and Coupa integration

DATEV is the German accounting and tax backend, with an on-premise Rechnungswesen ledger and a Unternehmen Online cloud layer. Coupa runs procurement, where supplier invoices and purchase orders are approved. Connecting the two means approved Coupa supplier invoices flow into DATEV as finalized bookings and as uploaded invoice documents, without anyone re-keying them into the German books. ml-connector handles the very different shapes on each side and submits the data on a schedule you control. Because DATEV cannot read its own chart of accounts or posted journals back out, the integration is a one-way write into accounting and never tries to pull the ledger.

How DATEV works

Coupa exposes suppliers, purchase orders, and approved supplier invoices through a REST API on a tenant-specific instance URL, authenticated with OAuth 2.0 client credentials and pinned to a dated API version header. Approved invoices are read from /api/invoices filtered to approved status, purchase orders from /api/purchase_orders, and suppliers from /api/suppliers. Coupa also pushes signed webhook events for invoice, purchase order, supplier, expense, and payment activity, each verified with HMAC-SHA256, so new approvals can trigger work as they happen rather than only on a poll.

How Coupa works

DATEV is not a conventional REST API for accounting. It uses OAuth 2.0 Authorization Code with PKCE against the Login mit DATEV identity provider, which requires a real interactive user session, since there is no machine-to-machine flow. A small REST surface lists clients and uploads documents to Unternehmen Online, but actual bookings are submitted as asynchronous file jobs: EXTF CSV files to the on-premise Rechnungswesen engine, or DXSO XML to Unternehmen Online. DATEV sends no webhooks, so every submitted job must be polled until it completes, and finalized bookings are write-only and cannot be read back.

What moves between them

The flow runs one way, from Coupa into DATEV. ml-connector reads approved supplier invoices and their purchase orders from Coupa and submits the resulting booking lines to DATEV as EXTF CSV files against the correct debit and credit accounts, and uploads the invoice PDFs into Unternehmen Online as Rechnungseingang documents. Supplier master detail rides along inside the EXTF file as creditor rows, since DATEV exposes no supplier endpoint. Invoice approvals are picked up from Coupa webhooks where enabled and otherwise on a scheduled poll. Nothing flows back from DATEV: it cannot return its chart of accounts or posted journals, so DATEV is treated as a write-only accounting destination.

How ml-connector handles it

ml-connector stores both credential sets encrypted. On the Coupa side it requests and caches a client-credentials OAuth token per instance and sends the dated API version header on every call. On the DATEV side it runs the Authorization Code flow with PKCE, where the code challenge must use S256 and the state value is at least 20 characters, then holds the resulting access token, which expires in 900 seconds, and refreshes it by sending only the client_id. Coupa invoice and purchase order webhooks are verified with HMAC-SHA256 before processing, and the Coupa invoice id plus a BullMQ job id dedupe a re-read so the same approval is not booked twice. Each approved invoice becomes EXTF CSV rows with Konto, Gegenkonto, Belegfeld 1, Buchungstext, and a tax code, written in UTF-8 with precomposed characters because non-normalized text is silently rejected; filenames are kept deterministic so DATEV's filename-and-document-type duplicate check makes a retry safe. The invoice PDF is pushed to Unternehmen Online with PUT and a stable GUID, after the client-specific document types are fetched first. Because DATEV processes asynchronously and sends no webhook, ml-connector polls each EXTF or DXSO job with exponential backoff and jitter until it reports complete or failed. The big gotcha is account mapping: DATEV will not hand over its chart of accounts, so Coupa GL coding and cost centers are mapped to known DATEV SKR account and Kostenstelle numbers in advance, and every job is replayable if a submission fails.

A real-world example

A mid-sized German manufacturing firm with around 300 staff runs Coupa for procurement across several plants and keeps its statutory books in DATEV through its Steuerberater. Before the integration, an accounts payable clerk exported approved supplier invoices from Coupa each week and typed the booking lines into DATEV by hand, then re-attached the invoice PDFs in Unternehmen Online, which left invoices waiting days and produced posting and tax-code mistakes the tax advisor had to correct at month-end. With DATEV and Coupa connected, each approved Coupa invoice is submitted to DATEV as an EXTF booking and its PDF uploaded to Unternehmen Online within the polling window, coded to the right SKR account and cost center. The manual re-keying is gone and the tax advisor receives clean, complete bookings.

What you can do

  • Post approved Coupa supplier invoices into DATEV as EXTF CSV bookings against the correct accounts and tax codes.
  • Upload Coupa invoice PDFs into DATEV Unternehmen Online as Rechnungseingang documents using a stable GUID.
  • Carry Coupa supplier detail into DATEV as EXTF creditor rows, since DATEV has no supplier endpoint.
  • Bridge Coupa client-credentials OAuth to DATEV Authorization Code login with PKCE and 15-minute token refresh.
  • React to Coupa invoice webhooks, dedupe re-reads, and poll each DATEV job to completion with retries and a full audit trail.

Questions

Which direction does data move between DATEV and Coupa?
The flow is one way, from Coupa into DATEV. Approved supplier invoices and their purchase orders move from Coupa into DATEV as EXTF bookings and uploaded documents. DATEV cannot return its chart of accounts or posted journals through the API, so it is treated as a write-only accounting destination and nothing is pulled back into Coupa.
How does the integration bridge the two different logins?
Coupa uses OAuth 2.0 client credentials, which ml-connector requests and caches per instance. DATEV uses OAuth 2.0 Authorization Code with PKCE against Login mit DATEV and requires a real interactive user session, so a tax advisor or client signs in once to grant access. ml-connector then holds the 900-second DATEV access token and refreshes it automatically, sending only the client_id on refresh as DATEV requires.
How are bookings confirmed if DATEV sends no webhooks?
DATEV processes EXTF and DXSO submissions asynchronously and never calls back. ml-connector submits each file as a job, receives a job id, and polls the job status endpoint with exponential backoff and jitter until it reports complete or failed. Deterministic filenames make a resubmission safe, because DATEV rejects duplicates by filename and document type, and every job can be replayed if a submission fails.

Related integrations

Connect DATEV and Coupa

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

Get started