This guide adds ZUGFeRD and XRechnung to Make (formerly Integromat). You need one scenario, the standard HTTP module and one REST API. No code and no custom app. The invoice data comes from a Google Sheets row, an Airtable record or a webhook. The HTTP module sends it as JSON and receives the finished PDF. The email section lets the API deliver the invoice to the customer. The import section shows how to check supplier invoices and write them into your bookkeeping. If your team works in Zapier, read the Zapier guide instead. It uses the InvoiceXML apps on Zapier.
Why Make needs ZUGFeRD now
German businesses that invoice from Make must send and receive structured e-invoices. Receiving e-invoices has been mandatory for every German business since January 2025. Sending them becomes mandatory in January 2027 for businesses with more than EUR 800,000 turnover in the previous year. From January 2028 it applies to all businesses. A PDF from a document template module is not an e-invoice. The law asks for a hybrid file: a normal PDF for people, with XML inside for software. That file is ZUGFeRD. All dates and formats are on the Germany mandate page.
The scenario stays small. Make has modules for the places your invoice data lives: Google Sheets, Airtable, HubSpot, Shopify, Stripe, a webhook from your own app. It has modules for the places the invoice must go: Google Drive, Dropbox, OneDrive, Gmail, Outlook. The one step in between is an HTTP module. It sends JSON and receives a validated file.
New format versions do not touch the scenario. ZUGFeRD, XRechnung and EN 16931 are updated from time to time. The API switches to each new version on its effective date. The JSON your scenario sends stays the same. You edit nothing and test nothing again.
What you need
- A Make account on any plan. The HTTP module, the Router, the Iterator, the aggregators, the data stores and the Email module are part of every account.
- An invoice source. This guide uses Google Sheets: an
Invoicessheet with one row per invoice and aLinessheet with one row per line item. Airtable with a linked table works the same way. A webhook that carries the whole invoice works too. - A place for the PDF: Google Drive, Dropbox or OneDrive. Each has an Upload a file module.
- A
leitweg_idcolumn on your customer sheet, for public-sector customers only. - An InvoiceXML account. Create one. You get an API key and 100 free credits. No credit card is needed.
How the integration works
A row in the Invoices sheet gets the status Ready. The scenario reads the row and its line items, builds a JSON body in the HTTP module and posts it to one endpoint. The API works out the totals and the VAT, writes the XML, puts it into a PDF/A-3 file, checks the result and sends the PDF back. The next module uploads it to Drive, or the API emails it to the customer. One module, one checked file.
| Customer | Format | Endpoint | Result |
|---|---|---|---|
| German business (B2B) | ZUGFeRD EN 16931 | /v1/create/zugferd | Hybrid PDF/A-3 |
| German business, delivered by the API | ZUGFeRD EN 16931 | /v1/create/zugferd/email | Email with the PDF attached |
| German public buyer (B2G) | XRechnung | /v1/create/xrechnung | XML with Leitweg-ID |
| French business | Factur-X EN 16931 | /v1/create/facturx | Hybrid PDF/A-3 |
All endpoints take the same JSON body. You build two scenarios. Both are made from standard modules, in this order:
- Scenario "Create ZUGFeRD": Google Sheets Watch rows (1), Set multiple variables (2), Google Sheets Search rows (3), Text aggregator (4), HTTP Make a request (5), Google Drive Upload a file (6), Google Sheets Update a row (7).
- Scenario "Import supplier invoices": Email Watch emails (1), Set multiple variables (2), Iterator (3), HTTP Make a request (4), Router (5), Email Send an email (6) on the invalid route, HTTP Make a request (7), Parse JSON (8) and Google Sheets Add a row (9) on the valid route.
The numbers in brackets are the module numbers. The mapping expressions in this guide use them, for example {{1.invoice_number}} for a column of the trigger row. Everything else is standard Make: the mapping panel, filters, the Router, error handlers and a data store.
Step 1: Store the API key and set up the HTTP module
Put the API key in one place. Add a Tools: Set multiple variables module right after the trigger and create a variable apiKey with the key as its value. Every HTTP module in the scenario then uses {{2.apiKey}}. When you rotate the key, you change one module. If your Make organization has custom variables, store the key there instead and map it the same way. Do not paste the key into every HTTP module.
Add an HTTP: Make a request module and set it up like this:
Module 2: Tools > Set multiple variables
Variables
Name apiKey
Value (your InvoiceXML API key)
Variable lifetime One cycle
Module 5: HTTP > Make a request
URL https://api.invoicexml.com/v1/create/zugferd
Method POST
Headers
Name Authorization
Value Bearer {{2.apiKey}}
Body type Raw
Content type JSON (application/json)
Request content (the JSON body from step 2)
Parse response No
Timeout 60
Output of module 5
Status code 200 on success
Data the ZUGFeRD PDF
Headers an array of name and value pairs
Parse response stays on No for the create endpoints, because the response is a PDF, not JSON. The PDF arrives in the output field Data. Every file module in Make reads from a field like this. For the upload endpoints later in this guide, the body type changes to Multipart/form-data. The header stays the same.
Step 2: Map sheet columns to JSON
The API takes an EN 16931 invoice as JSON. The invoice data comes from the Invoices sheet, the line items from the Lines sheet. The seller data (your own company) is typed into the body once. This is the mapping:
| Sheet column | JSON path | Note |
|---|---|---|
Invoices.invoice_number | invoice.invoiceNumber | BT-1, as text |
Invoices.issue_date, due_date | invoice.issueDate, dueDate | formatDate(...; "YYYY-MM-DD") |
Invoices.currency | invoice.currency | ISO code, EUR |
Invoices.order_reference | invoice.purchaseOrderReference | The customer's order number, BT-13 |
| Typed into the body | invoice.seller | Name, VAT id, address, IBAN of your company |
Invoices.customer_name, customer_vat_id, customer_street, customer_post_code, customer_city, customer_country | invoice.buyer | Country as ISO code (DE, AT, FR) |
Lines.description | lines[].item.name | Per line |
Lines.quantity, unit_price | lines[].quantity, lines[].priceDetails.netPrice | Net price per unit. parseNumber(...; ",") for German decimals |
Lines.unit | lines[].unitCode | UN/ECE code: H87 for pieces, HUR for hours, C62 for units |
Lines.vat_rate | lines[].vatInformation.rate | 19, 7 or 0 |
Customers.leitweg_id | invoice.buyerReference | XRechnung only |
The body has no totals and no VAT amounts. The API computes them from the lines. When the numbers do not add up, you get an error and no file. Rounding and discount mistakes are caught before the customer sees the invoice.
Write the body in the Request content field of the HTTP module. The mapping panel shows the columns of module 1 by their header names. Click a column to insert it. Dates and numbers from a sheet are text, so wrap them in formatDate and parseNumber:
{
"invoice": {
"invoiceNumber": "{{1.invoice_number}}",
"issueDate": "{{formatDate(1.issue_date; "YYYY-MM-DD")}}",
"dueDate": "{{formatDate(1.due_date; "YYYY-MM-DD")}}",
"currency": "{{1.currency}}",
"purchaseOrderReference": "{{1.order_reference}}",
"seller": {
"name": "Firma GmbH",
"vatIdentifier": "DE123456789",
"postalAddress": { "line1": "Musterstraße 1", "city": "Berlin", "postCode": "10115", "country": "DE" }
},
"buyer": {
"name": "{{1.customer_name}}",
"vatIdentifier": "{{1.customer_vat_id}}",
"postalAddress": {
"line1": "{{1.customer_street}}",
"city": "{{1.customer_city}}",
"postCode": "{{1.customer_post_code}}",
"country": "{{1.customer_country}}"
}
},
"paymentDetails": { "paymentAccountIdentifier": "DE02120300000000202051" },
"lines": [{{4.text}}]
}
}
{{4.text}} is the output of the Text aggregator from the next section. It holds all line objects of the invoice, separated by commas. Empty fields are left out by the API, so a missing order reference does not break the call. Double quotes inside a customer name break the JSON. Keep names free of them, or build the body with Make's JSON: Create JSON module, which escapes them for you.
Line items from a second sheet
An invoice has several lines. In Make, each line is a bundle. Two modules turn the bundles into one JSON array.
First, add Google Sheets: Search rows (module 3) on the Lines sheet. Set the filter to invoice_number equal to {{1.invoice_number}}. The module returns one bundle per line. Then add a Text aggregator (module 4). Set Source module to module 3 and Row separator to Other with a single comma. In the Text field, write one line object:
{
"quantity": {{parseNumber(3.quantity; ",")}},
"unitCode": "{{if(3.unit = "Std"; "HUR"; if(3.unit = "Stk"; "H87"; "C62"))}}",
"item": { "name": "{{3.description}}" },
"priceDetails": { "netPrice": {{parseNumber(3.unit_price; ",")}} },
"vatInformation": { "rate": {{parseNumber(3.vat_rate; ",")}} }
}
The aggregator runs once per invoice and outputs one text value. The HTTP module from step 1 sits after it and puts the text between square brackets. Put every module after the aggregator inside the same flow. Make runs them once per invoice, not once per line. If you prefer Make's Array aggregator, aggregate the same fields and add a JSON: Create JSON module with a data structure for the body. The result is the same JSON.
If your invoice source is a webhook, the lines usually arrive as an array in the webhook body. Then you need no Search rows module. Add an Iterator on the array and the same Text aggregator after it.
If your invoice source is Airtable, the layout is the same: an Invoices table, a Lines table with a link field to the invoice, and a Customers table. Use Airtable: Watch records as the trigger and Airtable: Search records with a formula on the link field for the lines. Airtable number fields arrive as numbers, so parseNumber is not needed there.
Step 3: Create the ZUGFeRD invoice
The HTTP module from step 1 now has its body. Run the scenario once with Run once and a test row. The module output shows status code 200 and a Data field with the PDF.
Module 5: HTTP > Make a request (complete settings)
URL https://api.invoicexml.com/v1/create/zugferd
Method POST
Headers Authorization: Bearer {{2.apiKey}}
Body type Raw
Content type JSON (application/json)
Request content { "invoice": { ... "lines": [{{4.text}}] } }
Parse response No
Error from the API
Status 400 the module stops with an error
Error message JSON with an "errors" array:
one entry per violated rule, with the rule id
and the JSON path of the field
A status 400 means the invoice data violated a rule. The module fails and the error message shows the API's JSON. The errors array names each rule and the field path, for example invoice.buyer.postalAddress.country. Fix the sheet row and run again. The operations section shows how an error handler reports this without stopping the scenario.
For French customers, change the URL to /v1/create/facturx. Factur-X is the French name for the same standard. An if expression on the customer country can pick the URL. The French side is covered in the Factur-X API toolkit.
Step 4: Store the PDF and run the scenario
Store the PDF where your team keeps invoices. The Google Drive: Upload a file module (6) takes the file from the HTTP module. Dropbox and OneDrive have the same module with the same two fields. Then a Google Sheets: Update a row module (7) marks the row as done:
Module 1: Google Sheets > Watch rows
Spreadsheet Invoices
Sheet Invoices
Table contains headers Yes
Limit 10
Filter after module 1 "Ready rows only"
Condition {{1.status}} Text operators: Equal to Ready
Module 6: Google Drive > Upload a file
Folder Rechnungen / {{formatDate(now; "YYYY")}}
File name {{1.invoice_number}}.pdf
Data {{5.data}}
Module 7: Google Sheets > Update a row
Spreadsheet Invoices
Row number {{1.`__ROW_NUMBER__`}}
status Issued
pdf_link {{6.webViewLink}}
Alternative trigger: Webhooks > Custom webhook
Webhook invoicexml-create (copy the URL into your billing system)
Data structure determined from the first request
Body example { "invoice_number": "2026-0117", "issue_date": "2026-11-06",
"customer": { ... }, "lines": [ { ... }, { ... } ] }
When to run it. The Google Sheets: Watch rows trigger (1) polls the sheet on the scenario schedule. Every 15 minutes is a good default. Add a filter after the trigger: status equal to Ready. Rows with another status are skipped and cost no operation after the filter. If invoices are created by another system, replace the trigger with a Webhooks: Custom webhook module. The scenario then runs the moment the webhook receives the invoice. Scheduling is covered in the operations section.
Sending from Make. If your scenario emails invoices today, attach {{5.data}} in the Gmail, Outlook or Email module instead of the old PDF. The hybrid PDF is the readable invoice and the XML in one file. The customer opens it like any PDF, and their accounting software reads the XML.
Send the invoice by email from the API
You can skip the email module. One call creates the invoice, checks it and emails it to the customer. The file never passes through the scenario. For Germany this is a complete delivery: B2B e-invoices have no central platform there, so an email with the ZUGFeRD PDF is all the law needs.
First verify your sending domain in the dashboard. Add the domain and publish the three CNAME records the dashboard shows. After that, any address on the domain can be the sender. The customer sees your address and can reply to it. Change the URL of module 5 to /v1/create/zugferd/email and add an email object to the body. The recipient comes from the customer_email column:
{
"invoice": { ... the same invoice object as in step 2 ... },
"email": {
"from": "[email protected]",
"to": "{{1.customer_email}}",
"subject": "Rechnung {{1.invoice_number}}",
"message": "Guten Tag,\n\nanbei erhalten Sie unsere Rechnung {{1.invoice_number}} als ZUGFeRD-PDF.\n\nMit freundlichen Grüßen"
}
}
A status 200 means: created, validated and handed to delivery. The response has no file, so module 6 is not needed on this route. Module 7 still marks the row. Use this route when the API should send. Use the plain endpoint from step 3 when Make should store the file and send it itself. XRechnung (/v1/create/xrechnung/email) and Factur-X have the same email route.
Import incoming supplier invoices
German companies have had to accept e-invoices since January 2025. A growing share of supplier invoices now comes in as ZUGFeRD PDFs or XRechnung XML. The second scenario processes them in three steps: check the file, send the report back when it fails, read the data when it passes. An Email: Watch emails module (1) on your invoice mailbox starts it. An Iterator (3) on {{1.attachments}} gives one bundle per attachment.
- Validate. Post the file to
POST /v1/validate/zugferd/report(PDF) or/v1/validate/xrechnung/report(XML). The API runs the EN 16931 and German rule sets over it. The body of the response is a PDF validation report. The headerX-Invoice-Validistrueorfalse. The form fieldvalidationLanguagepicks the language of the report,dein this guide. If you only want the findings as JSON, use/v1/validate/zugferdwithout/reportand set Parse response to Yes. - Reply with the report. When the header says
false, send the PDF report back to the sender and do nothing else. The report names each broken rule, the field and a message. The supplier can correct the invoice without calling your accounting team. - Extract. When the header says
true, post the file toPOST /v1/extract/json. The API reads the XML inside the PDF, or the XRechnung XML itself, and returns JSON. The shape is the same as the create body.
Module 1: Email > Watch emails
Folder Rechnungseingang
Criteria Unread emails, with attachments
Mark as read Yes
Module 3: Flow control > Iterator
Array {{1.attachments}}
Module 4: HTTP > Make a request
URL {{if(endsWith(lower(3.fileName); ".xml");
"https://api.invoicexml.com/v1/validate/xrechnung/report";
"https://api.invoicexml.com/v1/validate/zugferd/report")}}
Method POST
Headers Authorization: Bearer {{2.apiKey}}
Body type Multipart/form-data
Fields
Key file Type: File
File name {{3.fileName}}
Data {{3.data}}
Key validationLanguage Type: Text
Value de
Parse response No
Module 5: Flow control > Router
Route "Invalid" filter: {{get(map(4.headers; "value"; "name"; "x-invoice-valid"); 1)}}
Text operators: Equal to false
Route "Valid" filter: the same expression Equal to true
Module 6 (route "Invalid"): Email > Send an email
To {{1.from.address}}
Subject Ihre Rechnung {{3.fileName}} konnte nicht verarbeitet werden
Content Die angehängte Prüfung zeigt die Fehler.
Bitte senden Sie eine korrigierte Rechnung.
Attachments
File name Pruefbericht-{{3.fileName}}.pdf
Data {{4.data}}
Module 7 (route "Valid"): HTTP > Make a request
URL https://api.invoicexml.com/v1/extract/json
Method POST
Headers Authorization: Bearer {{2.apiKey}}
Body type Multipart/form-data
Fields Key: file Type: File {{3.fileName}} {{3.data}}
Parse response No
Module 8 (route "Valid"): JSON > Parse JSON
JSON string {{7.data}}
The upload endpoints take a multipart form with one file field named file. The HTTP module builds the form from the Fields list. The response headers of module 4 are an array of name and value pairs, so the filter uses map to find the header by name and get to read its value. Check the name in the output bundle of module 4 after a test run and use it exactly as shown there. Both routes of the Router have a filter, so a bundle takes exactly one route.
Plain PDFs: not every supplier sends XML yet. Send those to POST /v1/parse/json with the same multipart body. AI reads the PDF, including scans and photos, and returns the same JSON plus a confidence object. Each score is between 0.0 and 1.0. Add a filter on the score: high scores go to the bookkeeping row, the rest to a review sheet. A file that is not an invoice is rejected with error code 4008.
Create the bookkeeping row from the JSON
The JSON from both endpoints has the same shape. The Parse JSON module (8) turns it into mapped fields. Add a Google Sheets: Search rows module on your Vendors sheet with vat_id equal to {{8.invoice.seller.vatIdentifier}}, so the row gets your vendor number. Then add a Google Sheets: Add a row module (9) on your Purchases sheet. Nothing is posted without a look: accounts payable works from this sheet.
Module 9: Google Sheets > Add a row
Spreadsheet Purchases
invoice_number {{8.invoice.invoiceNumber}}
issue_date {{8.invoice.issueDate}}
due_date {{8.invoice.dueDate}}
vendor_name {{8.invoice.seller.name}}
vendor_vat_id {{8.invoice.seller.vatIdentifier}}
currency {{8.invoice.currency}}
grand_total {{8.invoice.totals.grandTotalAmount}}
amount_due {{8.invoice.totals.duePayableAmount}}
line_count {{length(8.invoice.lines)}}
file_name {{3.fileName}}
status To review
Module 10: Flow control > Iterator
Array {{8.invoice.lines}}
Module 11: Google Sheets > Add a row
Spreadsheet Purchase lines
invoice_number {{8.invoice.invoiceNumber}}
description {{10.item.name}}
quantity {{10.quantity}}
unit {{10.unitCode}}
net_price {{10.priceDetails.netPrice}}
vat_rate {{10.vatInformation.rate}}
If your accounting tool has a Make app with a Create a bill or Create a purchase invoice module, use it instead of the sheet and map the same fields. If it has only a REST API, add an HTTP module with a Raw JSON body and the same mapped fields. The line items are in {{8.invoice.lines}} as an array. The Iterator (10) and the second Add a row module (11) store them one per row. The totals from the invoice go into the header row, so the clerk can compare them with the lines.
Store the original file as well. A Google Drive Upload a file module with {{3.data}} and {{3.fileName}} keeps the supplier's PDF or XML next to the row. German record-keeping rules want the received file kept as it arrived, so keep it unchanged.
XRechnung for German public buyers
German public buyers (federal, state and municipal) take XRechnung, not ZUGFeRD. XRechnung is XML only, without a PDF. The portals ZRE and OZG-RE check it against the KoSIT rules. The same scenario produces it with a different URL and a few extra fields in the body:
{
"invoice": {
"invoiceNumber": "{{1.invoice_number}}",
"buyerReference": "{{3.leitweg_id}}",
"seller": {
"name": "Firma GmbH",
"vatIdentifier": "DE123456789",
"postalAddress": { "line1": "Musterstraße 1", "city": "Berlin", "postCode": "10115", "country": "DE" },
"contact": { "name": "Buchhaltung", "phone": "+49 30 1234567", "email": "[email protected]" },
"electronicAddress": { "identifier": "DE123456789", "schemeId": "9930" }
},
"buyer": {
"name": "{{1.customer_name}}",
"vatIdentifier": "{{1.customer_vat_id}}",
"postalAddress": { "line1": "{{1.customer_street}}", "city": "{{1.customer_city}}", "postCode": "{{1.customer_post_code}}", "country": "DE" },
"electronicAddress": { "identifier": "{{1.customer_vat_id}}", "schemeId": "9930" }
},
"paymentDetails": { "paymentAccountIdentifier": "DE02120300000000202051" },
"lines": [{{5.text}}]
}
}
The Leitweg-ID is the routing id the public buyer gives you when you register as a supplier. Without it, rule BR-DE-15 rejects the invoice. Store it in the leitweg_id column of your Customers sheet and read it with a Google Sheets: Search rows module on customer_vat_id. In this variant that lookup is module 3, so the Lines search becomes module 4 and the Text aggregator module 5. Keep the column as plain text. Leitweg-IDs have leading zeros and dashes, and a number format would remove them. The German rules also require a seller contact (name, phone, email) and electronic addresses for seller and buyer. Scheme 9930 stands for a German VAT id. For delivery over Peppol, put the Peppol participant id and its scheme there instead.
Change the URL of the HTTP module to /v1/create/xrechnung. The response is the XML, already checked against the KoSIT rules. Upload it as {{1.invoice_number}}.xml. Then deliver it the way the buyer asks for: ZRE or OZG-RE upload, Peppol, or email if the buyer accepts that. For a readable preview, add a second HTTP module: POST /v1/render/xrechnung/to/pdf with a Multipart/form-data body and the XML as the file field. The output is a PDF for your own records or an approval email. XRechnung is allowed for B2B invoices as well, so the /email route applies to it too. More on the format in the XRechnung API toolkit.
Scheduling and operations
A scenario that runs invoices needs a few settings so it runs without attention. These are the ones that matter.
Scheduling. Open the scenario settings with the clock icon. For the Google Sheets trigger, pick At regular intervals and 15 minutes. For a month-end batch, pick Once a day at a fixed time. A Custom webhook trigger runs Immediately when data arrives. Each module run is one Make operation. A filter right after the trigger keeps rows that are not ready from using operations.
Processed invoices in a data store. A row can trigger twice, for example after an edit. A Make data store keeps the scenario from creating the same invoice twice. Create a data store ProcessedInvoices with the invoice number as the key. Add a Data store: Add/replace a record module before the HTTP module:
Module 2b: Data store > Add/replace a record
Data store ProcessedInvoices
Key {{1.invoice_number}}
Overwrite an existing record No
Record
created_at {{now}}
status started
Error handler on module 2b Ignore
(an existing key raises an error; Ignore drops the bundle and the
scenario goes on with the next invoice)
Module 7b: Data store > Add/replace a record
Data store ProcessedInvoices
Key {{1.invoice_number}}
Overwrite an existing record Yes
Record
created_at {{now}}
status issued
pdf_link {{6.webViewLink}}
Scenario settings
Allow storing of incomplete executions Yes
Sequential processing Yes
Number of consecutive errors 3
Enable data loss No
With Overwrite set to No, the module fails when the key exists. The Ignore handler drops that bundle. The invoice is not created a second time. After module 7, the second Add/replace a record module (7b) with Overwrite set to Yes writes the final status and the Drive link. The data store then doubles as a log. A Data store: Search records module in a third scenario can list what was issued this month.
Error handlers. Right-click a module and add an error handler. Make offers five directives:
- Resume: the module output is replaced with values you set, and the route continues. Use it on the HTTP module to continue with an empty file and let a later filter skip the upload.
- Ignore: the bundle is dropped, the error is not logged, the scenario goes on. Use it on the data store module above.
- Break: the execution is stored as an incomplete execution and the scenario goes on. Use it on the HTTP create module for a status 400. The incomplete execution keeps the API's error message with the rule ids and the field paths. Fix the sheet row and retry the execution from the Incomplete executions list. Break can also retry on its own a few times with a set interval. That covers short network problems.
- Rollback: stops the scenario and rolls back modules that support transactions.
- Commit: stops the scenario and commits what ran.
Enable Allow storing of incomplete executions in the scenario settings. Break needs it. Set Sequential processing to Yes, so a stored execution is retried before new runs start. The Number of consecutive errors setting deactivates the scenario after too many failed runs in a row. Add an Email or Slack module to the error route so someone learns about it. Retrying unchanged invoice data gives the same error, so fix the data first and retry after.
Endpoint reference
| Purpose | Endpoint | Input | Output |
|---|---|---|---|
| Create ZUGFeRD | POST /v1/create/zugferd | Invoice JSON | Hybrid PDF/A-3 |
| Create and email ZUGFeRD | POST /v1/create/zugferd/email | Invoice JSON + email object | Email sent from your domain |
| Create XRechnung | POST /v1/create/xrechnung | Invoice JSON with Leitweg-ID | XRechnung XML |
| Create and email XRechnung | POST /v1/create/xrechnung/email | Invoice JSON + email object | Email sent from your domain |
| Create Factur-X | POST /v1/create/facturx | Invoice JSON | Hybrid PDF/A-3 |
| Validate with PDF report | POST /v1/validate/zugferd/report, /xrechnung/report | PDF or XML file | PDF report, X-Invoice-Valid header |
| Validate as JSON | POST /v1/validate/zugferd, /xrechnung | PDF or XML file | Findings per rule |
| Extract structured data | POST /v1/extract/json | Hybrid PDF or XML file | Invoice JSON |
| Parse plain PDF | POST /v1/parse/json | Any PDF or image | Invoice JSON + confidence |
| Extract attachments | POST /v1/extract/attachments | Hybrid PDF or XML file | ZIP of embedded files |
| Render XRechnung | POST /v1/render/xrechnung/to/pdf | XRechnung XML | Readable PDF |
Get started
The whole integration is the two scenarios in this guide: read the row, build the JSON in the HTTP module, store or email the file, check and import what comes in. No code, no custom app. No format logic in your scenario. When the formats change, the scenario stays as it is. This guide is part of the integrations hub, which also covers Zapier, n8n, Power Automate, Google Sheets and other platforms.
Related guides:
- Zapier e-invoicing: the same flows with the InvoiceXML apps on Zapier, no HTTP step.
- n8n e-invoicing: the same integration with the HTTP Request node, cloud or self-hosted.
- Google Sheets e-invoicing: the same sheet layout with Apps Script instead of Make.
- ZUGFeRD API toolkit: every ZUGFeRD endpoint and the format itself.