Automation MCP Server Blog Pricing
Automation

ZUGFeRD and XRechnung in Power Automate: A Custom Connector for SharePoint, Outlook and Microsoft 365

Many German companies keep their invoice data in SharePoint lists or Excel Online. E-invoices are mandatory for them now. This guide builds one Power Automate custom connector from a short OpenAPI file. Business users then drag ready-made actions into their flows: create a ZUGFeRD PDF, email it, check supplier invoices from Outlook and read their data. No Dynamics 365 is needed.

This guide adds ZUGFeRD and XRechnung to Power Automate for companies that run on Microsoft 365. Your invoice data stays where it is: in SharePoint lists or in an Excel Online table. You import one short OpenAPI file as a custom connector. After that, every flow has ready-made actions such as Create ZUGFeRD invoice and Validate with PDF report. Business users drag them into flows like any SharePoint or Outlook action. In the email section, the API delivers the invoice itself. In the import section, supplier invoices from an Outlook mailbox are checked and stored as SharePoint items. If your invoices live in Dynamics 365, follow the Dynamics 365 route with the HTTP action instead.


Why Power Automate flows need ZUGFeRD now

German companies must send and receive structured e-invoices. Since January 2025, every German business must accept e-invoices. From January 2027, businesses with more than EUR 800,000 turnover in the previous year must issue them. From January 2028, all other businesses follow. The Germany mandate page lists the dates and formats.

A PDF from a flow is not an e-invoice. Many Microsoft 365 flows fill a Word template and convert it to PDF. The result is a plain PDF for people. The mandate asks for data that software can read. ZUGFeRD solves this: it is a PDF/A-3 file with the invoice XML inside. The customer opens the PDF, and their accounting software reads the XML.

Format updates do not touch your flows. ZUGFeRD, XRechnung and the EN 16931 rules get new versions regularly. The API switches to each new version on its effective date. Your connector and your flows send the same JSON before and after. You change nothing.


What you need

  • Power Automate with access to custom connectors. Microsoft classifies custom connectors as premium. The details are in the licensing section.
  • A Power Platform environment with Dataverse. Microsoft now creates custom connectors inside solutions, and solutions need Dataverse. Your security role must allow you to create connectors.
  • A SharePoint site for the lists and the document library. An Excel Online table works too, see step 2.
  • An Outlook mailbox for incoming supplier invoices, for example [email protected].
  • An InvoiceXML account. Create one. You get an API key and 100 free credits. No credit card is needed.

How the integration works

The custom connector describes the InvoiceXML API to Power Automate. Each endpoint becomes an action with named fields. A flow reads an invoice from SharePoint, fills the fields and runs the action. The API calculates the totals and the VAT, builds the XML, embeds it in a PDF/A-3 file, checks it and returns the PDF. The flow saves the file in a document library. Or the API emails it to the customer.

CustomerFormatEndpointResult
German business (B2B)ZUGFeRD EN 16931/v1/create/zugferdHybrid PDF/A-3
German business, delivered by the APIZUGFeRD EN 16931/v1/create/zugferd/emailEmail with the PDF attached
German public buyer (B2G)XRechnung/v1/create/xrechnungXML with Leitweg-ID
French businessFactur-X EN 16931/v1/create/facturxHybrid PDF/A-3

All create endpoints take the same JSON body. You build these parts:

  • The custom connector "InvoiceXML" with four actions: CreateZugferd, CreateZugferdEmail, ValidateZugferdReport and ExtractJson.
  • SharePoint lists: Kunden (customers), Rechnungen (invoices), Rechnungspositionen (invoice lines), Lieferanten (suppliers) and Eingangsrechnungen (incoming invoices). Plus one document library E-Rechnungen.
  • Four flows: create and store, create and email, import from Outlook, and one flow for a Power Apps button.

Everything else is standard: the SharePoint and Office 365 Outlook connectors, the Data Operations actions Compose, Select and Parse JSON, the Control actions Condition and Apply to each, and the Power Apps (V2) trigger.


Step 1: Create the custom connector and store the API key

The InvoiceXML API publishes its own OpenAPI file at https://api.invoicexml.com/v1/openapi. That file uses OpenAPI 3.1. Power Platform custom connectors import OpenAPI 2.0 (Swagger) files only. So this guide gives you a short OpenAPI 2.0 file, written for the connector. Save it as invoicexml.swagger.json:

{
  "swagger": "2.0",
  "info": { "title": "InvoiceXML", "description": "ZUGFeRD and XRechnung e-invoices", "version": "1.0" },
  "host": "api.invoicexml.com",
  "basePath": "/",
  "schemes": ["https"],
  "securityDefinitions": {
    "api_key": { "type": "apiKey", "in": "header", "name": "Authorization" }
  },
  "security": [{ "api_key": [] }],
  "paths": {
    "/v1/create/zugferd": {
      "post": {
        "operationId": "CreateZugferd",
        "summary": "Create ZUGFeRD invoice",
        "consumes": ["application/json"],
        "produces": ["application/pdf"],
        "parameters": [{ "name": "body", "in": "body", "required": true,
                         "schema": { "$ref": "#/definitions/InvoiceRequest" } }],
        "responses": {
          "200": { "description": "ZUGFeRD PDF", "schema": { "type": "string", "format": "binary" } }
        }
      }
    },
    "/v1/create/zugferd/email": {
      "post": {
        "operationId": "CreateZugferdEmail",
        "summary": "Create and email ZUGFeRD invoice",
        "consumes": ["application/json"],
        "produces": ["application/json"],
        "parameters": [{ "name": "body", "in": "body", "required": true,
                         "schema": { "$ref": "#/definitions/EmailRequest" } }],
        "responses": { "200": { "description": "Created, validated and handed to delivery" } }
      }
    },
    "/v1/validate/zugferd/report": {
      "post": {
        "operationId": "ValidateZugferdReport",
        "summary": "Validate ZUGFeRD invoice with PDF report",
        "consumes": ["multipart/form-data"],
        "produces": ["application/pdf"],
        "parameters": [
          { "name": "file", "in": "formData", "type": "file", "required": true, "x-ms-summary": "Invoice file" },
          { "name": "validationLanguage", "in": "formData", "type": "string", "default": "de",
            "enum": ["de", "en", "fr"], "x-ms-summary": "Report language" }
        ],
        "responses": {
          "200": {
            "description": "PDF validation report",
            "schema": { "type": "string", "format": "binary" },
            "headers": { "X-Invoice-Valid": { "type": "string", "description": "true or false" } }
          }
        }
      }
    },
    "/v1/extract/json": {
      "post": {
        "operationId": "ExtractJson",
        "summary": "Extract invoice data as JSON",
        "consumes": ["multipart/form-data"],
        "produces": ["application/json"],
        "parameters": [
          { "name": "file", "in": "formData", "type": "file", "required": true, "x-ms-summary": "Invoice file" }
        ],
        "responses": { "200": { "description": "Invoice JSON", "schema": { "type": "object" } } }
      }
    }
  },
  "definitions": {
    "InvoiceRequest": { "type": "object", "properties": { "invoice": { "$ref": "#/definitions/Invoice" } } },
    "EmailRequest": {
      "type": "object",
      "properties": {
        "invoice": { "$ref": "#/definitions/Invoice" },
        "email": { "type": "object", "properties": {
          "from": { "type": "string" }, "fromName": { "type": "string" }, "to": { "type": "string" },
          "subject": { "type": "string" }, "message": { "type": "string" } } }
      }
    },
    "Invoice": {
      "type": "object",
      "properties": {
        "invoiceNumber": { "type": "string" }, "issueDate": { "type": "string" },
        "dueDate": { "type": "string" }, "currency": { "type": "string" },
        "buyerReference": { "type": "string" }, "purchaseOrderReference": { "type": "string" },
        "seller": { "$ref": "#/definitions/Party" }, "buyer": { "$ref": "#/definitions/Party" },
        "paymentDetails": { "type": "object", "properties": { "paymentAccountIdentifier": { "type": "string" } } },
        "lines": { "type": "array", "items": { "$ref": "#/definitions/Line" } }
      }
    },
    "Party": {
      "type": "object",
      "properties": {
        "name": { "type": "string" }, "vatIdentifier": { "type": "string" },
        "postalAddress": { "type": "object", "properties": {
          "line1": { "type": "string" }, "city": { "type": "string" },
          "postCode": { "type": "string" }, "country": { "type": "string" } } },
        "contact": { "type": "object", "properties": {
          "name": { "type": "string" }, "phone": { "type": "string" }, "email": { "type": "string" } } },
        "electronicAddress": { "type": "object", "properties": {
          "identifier": { "type": "string" }, "schemeId": { "type": "string" } } }
      }
    },
    "Line": {
      "type": "object",
      "properties": {
        "quantity": { "type": "number" }, "unitCode": { "type": "string" },
        "item": { "type": "object", "properties": { "name": { "type": "string" } } },
        "priceDetails": { "type": "object", "properties": { "netPrice": { "type": "number" } } },
        "vatInformation": { "type": "object", "properties": { "rate": { "type": "number" } } }
      }
    }
  }
}

The invoice schema holds only the fields this guide uses. The API accepts more EN 16931 fields. Add them to Invoice, Party or Line when you need them, and update the connector.

Import the file. Open Power Automate and select Solutions. Open an unmanaged solution or create one. Select New, Automation, Custom connector, then Import an OpenAPI file. Enter the name InvoiceXML and pick the file. Power Automate fills the General, Security and Definition tabs from it. Check the Validation area on the Definition tab, then select Create connector. If you prefer to click the connector together yourself, Create from blank on the same menu builds the same actions in the wizard.

Store the API key. The key does not go into any flow. It goes into the connection. On the Test tab, select New connection. Enter Bearer, one space and your API key, for example Bearer YOUR_API_KEY. The connector sends this value in the Authorization header. Wait a few minutes after creating the connector before you test it. Then run CreateZugferd on the Test tab with a small sample invoice.


Step 2: Map SharePoint columns to JSON

Create the lists with these column names. Use no spaces and no umlauts. Then the internal names equal the display names, and the expressions below work as written. The Title column of each list keeps its internal name, even if you rename it.

List and columnConnector fieldNote
Rechnungen.TitleinvoiceNumberBT-1. Display name "Rechnungsnummer"
Rechnungsdatum, FaelligkeitsdatumissueDate, dueDateDate only. Formatted as yyyy-MM-dd
BestellnummerpurchaseOrderReferenceThe customer's order number, BT-13
Kunde (lookup to Kunden)buyerThe flow loads the customer item by its ID
Kunden.Title, UStIdNrbuyer.name, buyer.vatIdentifierVAT id such as DE123456789
Kunden.Strasse, PLZ, Ort, Landbuyer.postalAddressLand holds the ISO code: DE, AT, FR
Kunden.LeitwegIDbuyerReferenceXRechnung only
Rechnungspositionen.Titlelines[].item.nameOne list item per invoice line
Menge, Einzelpreislines[].quantity, lines[].priceDetails.netPriceNumber columns. Net price per unit
Einheit (choice)lines[].unitCodeUN/ECE codes as choices: H87 pieces, HUR hours, C62 unit
MwStSatzlines[].vatInformation.rateA Number column with 19 or 7. Not a percent column, which stores 0.19
Rechnung (lookup to Rechnungen)noneLinks each line to its invoice

Rechnungen also gets a choice column Status (Entwurf, Freigegeben, Erstellt, Fehler) and two text columns, ERechnungLink and Fehlertext. You do not send totals or VAT amounts. The API calculates both from the lines. If the numbers do not add up, it returns an error instead of a file. So a wrong line in the list never reaches the customer.

The seller data is the same on every invoice. Keep it in one Compose action at the top of the flow. The flow then loads the customer and the lines and turns the lines into the lines array with a Select action:

Compose (rename to: Verkaeufer)
  Inputs:
  {
    "name": "Beispiel GmbH",
    "vatIdentifier": "DE123456789",
    "line1": "Hauptstrasse 1",
    "postCode": "10115",
    "city": "Berlin",
    "country": "DE",
    "iban": "DE89370400440532013000"
  }

Get item (SharePoint)
  List Name:    Kunden
  Id:           triggerOutputs()?['body/Kunde/Id']

Get items (SharePoint)
  List Name:    Rechnungspositionen
  Filter Query: Rechnung/Id eq @{triggerOutputs()?['body/ID']}

Select (rename to: Positionen)
  From: body('Get_items')?['value']
  Map (switch to text mode; every value is an expression from the fx editor):
  {
    "quantity":       item()?['Menge'],
    "unitCode":       item()?['Einheit']?['Value'],
    "item":           { "name": item()?['Title'] },
    "priceDetails":   { "netPrice": item()?['Einzelpreis'] },
    "vatInformation": { "rate": item()?['MwStSatz'] }
  }

Select creates one JSON object per list item. Its output is exactly the lines array the API expects. For a choice column, SharePoint returns an object, so the unit code sits under Value. If several environments use the flow, move the seller data into an environment variable of type JSON instead of the Compose action.

Excel Online instead of SharePoint. Keep the lines in an Excel table in OneDrive or SharePoint. Use List rows present in a table from the Excel Online (Business) connector with a filter on the invoice number. Then point the Select action at body('List_rows_present_in_a_table')?['value'] and use the column headers of the table in the map. The rest of the flow stays the same.


Step 3: Create the ZUGFeRD invoice in Power Automate

The flow starts when an invoice is released. A trigger on "created" alone runs too early, because the lines are entered after the header. So the flow uses "created or modified" with a trigger condition on the status. Add the condition under the trigger's Settings, Trigger conditions.

Flow "E-Rechnung erstellen" (automated cloud flow)

1. When an item is created or modified (SharePoint)
     Site Address: https://firma.sharepoint.com/sites/Buchhaltung
     List Name:    Rechnungen
     Trigger condition:
       @equals(triggerOutputs()?['body/Status/Value'], 'Freigegeben')

2. Compose: Verkaeufer         (step 2)
3. Get item: Kunden            (step 2)
4. Get items: Rechnungspositionen (step 2)
5. Select: Positionen          (step 2)

6. InvoiceXML: Create ZUGFeRD invoice (rename to: CreateZugferd)
     invoiceNumber:          triggerOutputs()?['body/Title']
     issueDate:              formatDateTime(triggerOutputs()?['body/Rechnungsdatum'], 'yyyy-MM-dd')
     dueDate:                formatDateTime(triggerOutputs()?['body/Faelligkeitsdatum'], 'yyyy-MM-dd')
     currency:               EUR
     purchaseOrderReference: triggerOutputs()?['body/Bestellnummer']
     seller name:            outputs('Verkaeufer')?['name']
     seller vatIdentifier:   outputs('Verkaeufer')?['vatIdentifier']
     seller line1, postCode, city, country: outputs('Verkaeufer')?['line1'] (and so on)
     buyer name:             body('Get_item')?['Title']
     buyer vatIdentifier:    body('Get_item')?['UStIdNr']
     buyer line1:            body('Get_item')?['Strasse']
     buyer postCode:         body('Get_item')?['PLZ']
     buyer city:             body('Get_item')?['Ort']
     buyer country:          body('Get_item')?['Land']
     paymentAccountIdentifier: outputs('Verkaeufer')?['iban']
     lines:                  body('Positionen')   (select "Switch to input entire array" first)

7. Create file, 8. Get file properties, 9. Update item   (step 4)

The connector shows every field of the schema as its own input box. Leave the XRechnung fields (buyerReference, contact, electronicAddress) empty for B2B invoices. The API ignores empty fields. The output of the action is the ZUGFeRD PDF as a binary file. For French customers, add a CreateFacturx operation with the path /v1/create/facturx. It takes the same body. Pick the action with a Condition on body('Get_item')?['Land'].

The expressions use the action names from this guide. Rename your actions to match: open the action menu and select Rename. Power Automate replaces spaces with underscores in expressions. So "Get file properties" becomes Get_file_properties.


Step 4: Store the PDF in a document library

The SharePoint action Create file takes binary content. The output of CreateZugferd goes straight into it. Get file properties then returns the link to the file, and Update item writes it back to the invoice. A second branch handles errors:

7. Create file (SharePoint)
     Site Address: https://firma.sharepoint.com/sites/Buchhaltung
     Folder Path:  /E-Rechnungen/@{formatDateTime(triggerOutputs()?['body/Rechnungsdatum'], 'yyyy')}
     File Name:    @{triggerOutputs()?['body/Title']}.pdf
     File Content: body('CreateZugferd')

8. Get file properties (SharePoint)
     Library Name: E-Rechnungen
     Id:           body('Create_file')?['ItemId']

9. Update item (SharePoint)
     List Name:     Rechnungen
     Id:            triggerOutputs()?['body/ID']
     Title:         triggerOutputs()?['body/Title']
     Status Value:  Erstellt
     ERechnungLink: body('Get_file_properties')?['{Link}']

Error branch (parallel to step 7, "Configure run after": has failed)
   Update item (SharePoint)
     Id:            triggerOutputs()?['body/ID']
     Title:         triggerOutputs()?['body/Title']
     Status Value:  Fehler
     Fehlertext:    string(body('CreateZugferd')?['errors'])

Why the flow does not loop. Update item changes the invoice, which fires the trigger again. The trigger condition only accepts the status Freigegeben. After the update, the status is Erstellt or Fehler, so the flow does not start a second time.

When the API rejects an invoice. The API answers with status 400 and a JSON body. Its errors array has one entry per violated rule, with the rule id and the JSON path of the field. The connector action fails, and the error branch writes the list into Fehlertext. The user fixes the item and sets the status back to Freigegeben. The flow then runs again.

OneDrive instead of SharePoint. The OneDrive for Business connector also has a Create file action with Folder Path, File Name and File Content. Pass the same body('CreateZugferd'). To send the PDF from Outlook, use Send an email (V2) and put body('CreateZugferd') into the attachment content. The ZUGFeRD PDF is also the readable invoice. The customer gets one file.


Send the invoice by email from the API

The flow does not have to send the invoice. With CreateZugferdEmail, the API creates the invoice, validates it and emails it to the customer in one call. No file passes through the flow. In Germany this is a complete delivery. There is no central platform for B2B e-invoices, and an email with a ZUGFeRD PDF fulfils the mandate.

First, verify your sending domain in the InvoiceXML dashboard. Add the domain and publish the three CNAME records the dashboard shows. After that, any address on that domain can be the sender. The customer sees your address, and replies reach your mailbox. Add a text column EMail to the Kunden list. Then copy the flow from step 3 and replace steps 6 to 9:

Flow "E-Rechnung per E-Mail"

6. InvoiceXML: Create and email ZUGFeRD invoice (rename to: CreateZugferdEmail)
     invoice fields: the same values as CreateZugferd in step 3
     email from:     [email protected]          (an address on your verified domain)
     email fromName: Beispiel GmbH Buchhaltung
     email to:       body('Get_item')?['EMail']
     email subject:  Rechnung @{triggerOutputs()?['body/Title']}
     email message:  Guten Tag,

                     anbei erhalten Sie unsere Rechnung @{triggerOutputs()?['body/Title']}
                     als ZUGFeRD-PDF.

                     Freundliche Grüße

7. Update item (SharePoint)
     Id:            triggerOutputs()?['body/ID']
     Title:         triggerOutputs()?['body/Title']
     Status Value:  Versendet

Status 200 means the invoice was created, validated and handed to delivery. The response does not contain the file. Add the choice Versendet to the Status column, and keep the error branch from step 4. Use this route when the API should send. Use the plain endpoint when Power Automate should store the file and send it itself. XRechnung and Factur-X have the same email route: /v1/create/xrechnung/email and /v1/create/facturx/email.


Import incoming supplier invoices

Since January 2025, German businesses must accept e-invoices. Suppliers send ZUGFeRD PDFs and XRechnung XML files, mostly by email. The import flow watches one Outlook mailbox and handles each attachment in three steps:

  1. Validate. Upload the file to POST /v1/validate/zugferd/report (PDF) or /v1/validate/xrechnung/report (XML). The form field validationLanguage=de gives a German report. The response body is a PDF report. The header X-Invoice-Valid is true or false. For the findings as JSON instead, use /v1/validate/zugferd without /report.
  2. Send the report back. If the invoice is invalid, reply to the sender with the PDF report and stop. The report lists every violated rule with the field and a message.
  3. Extract. If the invoice is valid, upload it to POST /v1/extract/json. The API returns the invoice as JSON, in the same shape as the create request, plus totals.
Flow "Rechnungseingang" (automated cloud flow)

1. When a new email arrives (V3) (Office 365 Outlook)
     Folder:                Inbox
     Only with Attachments: Yes
     Include Attachments:   Yes

2. Apply to each (rename to: Apply_to_each)
     Select an output: triggerOutputs()?['body/attachments']

3.   Condition: endsWith(toLower(items('Apply_to_each')?['name']), '.pdf')  is equal to  true

4.   InvoiceXML: Validate ZUGFeRD invoice with PDF report (rename to: ValidateZugferdReport)
       Invoice file:    base64ToBinary(items('Apply_to_each')?['contentBytes'])
       Report language: de

5.   Condition (rename to: Gueltig)
       outputs('ValidateZugferdReport')?['headers']?['X-Invoice-Valid']  is equal to  true

     If no:
       Reply to email (V3) (Office 365 Outlook)
         Message Id:          triggerOutputs()?['body/id']
         Body:                Ihre Rechnung hat die Prüfung nicht bestanden. Der Bericht
                              im Anhang zeigt die Fehler. Bitte senden Sie eine
                              korrigierte Rechnung.
         Attachments Name:    Pruefbericht-@{items('Apply_to_each')?['name']}
         Attachments Content: body('ValidateZugferdReport')

     If yes:
       InvoiceXML: Extract invoice data as JSON (rename to: ExtractJson)
         Invoice file: base64ToBinary(items('Apply_to_each')?['contentBytes'])
       Parse JSON
         Content: body('ExtractJson')
         Schema:  "Generate from sample" with one ExtractJson response
       (continue in the next section)

The connector definition lists X-Invoice-Valid as a response header. The Condition reads it from the raw outputs of the action with outputs(...)?['headers']. The header value is text, so compare it with the text true. The Outlook trigger delivers attachments as base64 text. base64ToBinary turns them into the file the connector uploads as the form field file.

XML attachments. Add a ValidateXrechnungReport operation to the connector. Copy the ValidateZugferdReport block and change the path to /v1/validate/xrechnung/report. In the flow, a second Condition sends .xml attachments there. ExtractJson reads both PDF and XML. Shared mailbox: use the trigger When a new email arrives in a shared mailbox (V2) instead. Busy mailboxes: Microsoft recommends setting Include Attachments to No and loading each file with Get Attachment (V2).

Plain PDFs: a PDF without XML inside goes to POST /v1/parse/json. Add it as a ParseJson operation, with the same form field as ExtractJson. The API reads the PDF with AI. Typed, scanned and photographed invoices all work. The response is the same JSON plus a confidence object with scores from 0.0 to 1.0. Store the item when the score is high. Mark it for review when it is low. A file that is not an invoice comes back with error code 4008.


Create the SharePoint item from the JSON

The JSON from ExtractJson and ParseJson has the same shape. So one set of actions creates the item in Eingangsrechnungen. The flow finds the supplier in the Lieferanten list by the VAT id from the invoice. It uses Get items with an OData filter on the column UStIdNr:

(inside "If yes", after Parse JSON)

6. Get items (SharePoint) (rename to: Lieferant_suchen)
     List Name:    Lieferanten
     Filter Query: UStIdNr eq '@{body('Parse_JSON')?['invoice']?['seller']?['vatIdentifier']}'
     Top Count:    1

7. Condition: empty(body('Lieferant_suchen')?['value'])  is equal to  true
     If yes: Send an email (V2) to the accounts payable team:
             "Unknown supplier, VAT id @{body('Parse_JSON')?['invoice']?['seller']?['vatIdentifier']}"
     If no:  continue with step 8

8. Create file (SharePoint)
     Folder Path:  /Eingangsrechnungen-Dateien
     File Name:    @{items('Apply_to_each')?['name']}
     File Content: base64ToBinary(items('Apply_to_each')?['contentBytes'])

9. Create item (SharePoint)
     List Name:         Eingangsrechnungen
     Title:             body('Parse_JSON')?['invoice']?['invoiceNumber']
     Lieferant Id:      first(body('Lieferant_suchen')?['value'])?['ID']
     Rechnungsdatum:    body('Parse_JSON')?['invoice']?['issueDate']
     Faelligkeitsdatum: body('Parse_JSON')?['invoice']?['dueDate']
     Bruttobetrag:      body('Parse_JSON')?['invoice']?['totals']?['grandTotalAmount']
     Zahlbetrag:        body('Parse_JSON')?['invoice']?['totals']?['duePayableAmount']
     Bestellnummer:     body('Parse_JSON')?['invoice']?['purchaseOrderReference']
     Status Value:      Zu pruefen
     Datei:             body('Create_file')?['Path']

Lieferant is a lookup column to the Lieferanten list, and Datei is a text column for the file path. The item starts with the status Zu pruefen. Accounts payable checks it and approves it. Nothing is paid without a look. To keep the lines too, add an Apply to each over body('Parse_JSON')?['invoice']?['lines']. It creates one item per line in a child list, like Rechnungspositionen on the sales side. Fields that are empty in the invoice are not in the JSON. The Parse JSON schema should therefore mark only invoice as required.


XRechnung for German public buyers

German public buyers (federal, state and municipal) need XRechnung, not ZUGFeRD. XRechnung is XML only, without a PDF. The portals ZRE and OZG-RE check every file against the KoSIT rules. Add a LeitwegID text column to the Kunden list. Then add one more operation to the connector, between the other paths:

"/v1/create/xrechnung": {
  "post": {
    "operationId": "CreateXrechnung",
    "summary": "Create XRechnung invoice",
    "consumes": ["application/json"],
    "produces": ["application/xml"],
    "parameters": [{ "name": "body", "in": "body", "required": true,
                     "schema": { "$ref": "#/definitions/InvoiceRequest" } }],
    "responses": {
      "200": { "description": "XRechnung XML", "schema": { "type": "string", "format": "binary" } }
    }
  }
}

In the flow from step 3, add a Condition before step 6: empty(body('Get_item')?['LeitwegID']) is equal to true. The "yes" branch keeps CreateZugferd. The "no" branch calls CreateXrechnung with the same fields plus these:

  • buyerReference: body('Get_item')?['LeitwegID']. The public buyer gives you the Leitweg-ID when you register as a supplier. Rule BR-DE-15 rejects an XRechnung without it.
  • Seller contact: name, phone and email of your accounts team. Add them to the Verkaeufer Compose action.
  • Seller and buyer electronicAddress: the VAT id with schemeId 9930 (German VAT id). For a buyer without a VAT id, use the Leitweg-ID with scheme 0204. For Peppol delivery, use the Peppol participant id and its scheme.

The action returns the XML, already checked against the KoSIT rules. Save it with Create file as .xml. Deliver it the way the buyer requires: upload to ZRE or OZG-RE, send it over Peppol, or email it if the buyer accepts that. For a readable copy in your library, /v1/render/xrechnung/to/pdf turns the XML into a PDF. The XRechnung API toolkit explains the format in detail.


Run the flow from a Power Apps button

Some teams want to create an e-invoice by hand, for example for a corrected invoice. A canvas app on top of the Rechnungen list gets a button for that. The flow uses the Power Apps (V2) trigger with one number input, the item ID. It returns the file link to the app:

Flow "ERechnungErstellen" (instant cloud flow)

1. Power Apps (V2)
     Input: Number, name "RechnungId"
2. Get item (SharePoint), rename to: Rechnung
     List Name: Rechnungen
     Id:        triggerBody()?['number']
3. The actions Verkaeufer to Get file properties from steps 2 to 4.
   Read the invoice fields from body('Rechnung'), not from triggerOutputs().
   Example: body('Rechnung')?['Title'] instead of triggerOutputs()?['body/Title'].
4. Respond to a Power App or flow
     Output: Text, name "link", value body('Get_file_properties')?['{Link}']

Power Fx, OnSelect of the button in the canvas app:
  Set(varErgebnis, ERechnungErstellen.Run(BrowseGallery1.Selected.ID));
  Launch(varErgebnis.link)

Add the flow to the app under Power Automate in the app's left pane. Power Apps then knows the input and the output by name. The key of the first number input in the trigger body is number. Check it in the flow's run history if you add more inputs. The same flow also serves a manual run from the Power Automate mobile app.


Sharing the connector, environments and licensing facts

Share the connector. By default, only you can use a new custom connector. In Power Automate, select More, Discover all, and under Data, Custom connectors. Open the menu of the connector and select Invite another user. Give users or groups Can view to use it or Can edit to change it. Each user creates their own connection with the API key. Sharing an app or a team flow that uses the connector shares the connector too.

Move it with a solution. Keep the connector, the flows and the environment variables in one solution. Export it from development and import it into test and production. In the flows, use connection references instead of direct connections. On import, you pick an existing InvoiceXML connection in the target environment. The API key stays in the connection and never travels in the solution file. Microsoft asks you to import custom connectors before the flows that use them. If an import fails, import a solution with only the connector first. Put the SharePoint site address and the library name into environment variables. Then each environment points at its own site.

Keep the definition in Git. The paconn CLI downloads and uploads custom connectors. It needs Python and a properties file next to the OpenAPI file. The properties file defines the API key prompt:

pip install paconn
paconn login
paconn validate --api-def apiDefinition.swagger.json
paconn create --api-prop apiProperties.json --api-def apiDefinition.swagger.json

apiProperties.json:
{
  "properties": {
    "connectionParameters": {
      "api_key": {
        "type": "securestring",
        "uiDefinition": {
          "displayName": "API key",
          "description": "Bearer, one space, then your InvoiceXML API key",
          "tooltip": "Bearer <your key>",
          "constraints": { "required": "true", "clearText": false }
        }
      }
    }
  }
}

paconn create prints the new connector ID. Use paconn update with that ID for later changes. Microsoft also notes that a changed field needs the connection to be added again in apps that use the connector.

Data policies. Admins control connectors with data policies in the Power Platform admin center. A flow can only combine connectors from the same group. Put the InvoiceXML connector in the same group as SharePoint and Office 365 Outlook, usually Business. Tenant admins classify custom connectors by host URL pattern, here https://api.invoicexml.com. Environment admins can classify them by name.

Licensing facts. Microsoft classifies custom connectors as premium. The Power Automate access included in Microsoft 365 licenses covers standard connectors. A flow with a custom connector needs a Power Automate Premium license or a Process license on the flow. For automated and scheduled flows, such as the SharePoint and Outlook flows in this guide, the owner's license counts. For instant flows, such as the Power Apps button, each user who runs the flow needs the license, or the flow has a Process license. The HTTP action is premium as well. Microsoft's licensing pages have the current details.


Endpoint reference

PurposeEndpointInputOutput
Create ZUGFeRDPOST /v1/create/zugferdInvoice JSONHybrid PDF/A-3
Create and email ZUGFeRDPOST /v1/create/zugferd/emailInvoice JSON + email objectEmail sent from your domain
Create XRechnungPOST /v1/create/xrechnungInvoice JSON with Leitweg-IDXRechnung XML
Create and email XRechnungPOST /v1/create/xrechnung/emailInvoice JSON + email objectEmail sent from your domain
Create Factur-XPOST /v1/create/facturxInvoice JSONHybrid PDF/A-3
Validate with PDF reportPOST /v1/validate/zugferd/report, /xrechnung/reportPDF or XML filePDF report, X-Invoice-Valid header
Validate as JSONPOST /v1/validate/zugferd, /xrechnungPDF or XML fileFindings per rule
Extract structured dataPOST /v1/extract/jsonHybrid PDF or XML fileInvoice JSON
Parse plain PDFPOST /v1/parse/jsonAny PDF or imageInvoice JSON + confidence
Extract attachmentsPOST /v1/extract/attachmentsHybrid PDF or XML fileZIP of embedded files
Render XRechnungPOST /v1/render/xrechnung/to/pdfXRechnung XMLReadable PDF

Get started

The whole integration is one OpenAPI file and a few flows. Import the connector, enter the key once, map the SharePoint columns, and create, store or email the invoice. Incoming invoices are checked and stored the same way. Business users build new flows with the same actions. When the formats change, nothing in your flows changes. This guide is part of the integrations hub, which covers more platforms.

Related guides:

Start free today

Ready to automate your invoices?

Validate, convert and embed compliant e-invoices through one API. Start your 30-day free trial. No credit card required.

GDPR Compliant No credit card required Setup in minutes
Peppol UBL
Factur-X
EN 16931
142 / 142 passed
Compliant
PDF/A-3 embedded