Skip to content
xmoo.app

API · E-invoicing

xmoo E-Invoice API

Validate, read, convert and render European e-invoices – XRechnung, ZUGFeRD, Factur-X and any EN 16931 invoice in UBL or CII – with one REST call.

What it does

Invoices are processed in memory and never stored. The API runs on a server in Helsinki, Finland (EU). It uses the same engine as the free browser tools on this site, the e-invoice viewer and the e-invoice converter (both in German).

Access

The API is offered through RapidAPI. There you subscribe to a plan, get your key and find the host name for your requests; billing also runs through RapidAPI: xmoo E-Invoice API on RapidAPI. Questions: julian@haag-automation.com.

Every request carries two headers from your RapidAPI account:

x-rapidapi-key: <your key>
x-rapidapi-host: <host name from the listing>

The examples below expect them in the shell variables RAPIDAPI_KEY and RAPIDAPI_HOST.

Endpoints

EndpointPurposeAnswer
POST /v1/detectFormat, syntax, specification and profile; which rule sets applyJSON
POST /v1/validateValidation report with all messages and a totals check (BR-CO-10 to 16)JSON
POST /v1/parseAll EN 16931 business terms as JSONJSON
POST /v1/convert?to=…Convert to xrechnung-ubl, xrechnung-cii, zugferd (XML) or zugferd-pdf (PDF/A-3)JSON with the file as Base64
POST /v1/renderReadable A4 PDF of the invoicePDF
GET /v1/formatsSupported formats, rule sets and targetsJSON

The machine-readable description is at api.xmoo.app/openapi.json (OpenAPI 3.0).

Sending an invoice

Send a JSON body with the file (XML or PDF) as Base64 in document. This is the recommended way and works for every endpoint:

base64 -w0 invoice.xml | jq -Rs '{document: .}' |
curl -s -X POST "https://$RAPIDAPI_HOST/v1/validate" \
  -H "x-rapidapi-key: $RAPIDAPI_KEY" \
  -H "x-rapidapi-host: $RAPIDAPI_HOST" \
  -H "Content-Type: application/json" \
  --data-binary @-

The same in Python:

import base64, os, requests

with open("invoice.pdf", "rb") as f:
    body = {"document": base64.b64encode(f.read()).decode()}

r = requests.post(f"https://{os.environ['RAPIDAPI_HOST']}/v1/validate", json=body, headers={
    "x-rapidapi-key": os.environ["RAPIDAPI_KEY"],
    "x-rapidapi-host": os.environ["RAPIDAPI_HOST"],
})
report = r.json()
print(report["valid"], report["summary"])

The API also accepts the raw file as the body (Content-Type: application/xml or application/pdf); options then go into the query string. Further fields of the JSON body: to (target format for /v1/convert), additions (see below) and lang.

Language

Field names are always English. Descriptive texts – changes, losses, notes and error messages – are English by default; lang=de returns them in German. Rule texts come verbatim from the official artefacts: those of EN 16931 and ZUGFeRD / Factur-X are English, those of the KoSIT XRechnung rules (BR-DE-…) are German.

Examples

The answers below are real answers of the API (shortened where marked). Test files: the KoSIT XRechnung test suite and the Mustang project's ZUGFeRD samples. A test on our side sends exactly these requests and checks that the answers still match.

Detect a hybrid PDF

POST /v1/detect with a ZUGFeRD PDF, profile EN 16931:

{
  "source": "pdf",
  "syntax": "CII",
  "documentType": "invoice",
  "specification": "urn:cen.eu:en16931:2017",
  "profile": "EN 16931 (for ZUGFeRD: profile EN 16931)",
  "rulesets": [
    "schema-facturx-en16931",
    "facturx-en16931"
  ]
}

Validate an XRechnung

POST /v1/validate with an XRechnung 3.0 in UBL. The invoice is valid; the KoSIT rules add one note (severity info). Shortened to the first two totals checks:

{
  "source": "xml",
  "syntax": "UBL",
  "documentType": "invoice",
  "specification": "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0",
  "profile": "XRechnung 3.0",
  "rulesets": [
    "schema-ubl",
    "en16931-ubl",
    "xrechnung-ubl"
  ],
  "valid": true,
  "summary": {
    "errors": 0,
    "warnings": 0,
    "infos": 1
  },
  "reports": [
    {
      "ruleset": "schema-ubl",
      "title": "XML Schema UBL 2.1",
      "messages": []
    },
    {
      "ruleset": "en16931-ubl",
      "title": "EN 16931 (validation artefacts 1.3.17, UBL)",
      "messages": []
    },
    {
      "ruleset": "xrechnung-ubl",
      "title": "XRechnung 3.0 (KoSIT-Schematron 2.6.0, UBL)",
      "messages": [
        {
          "rule": "BR-DE-TMP-32",
          "severity": "info",
          "text": "Eine Rechnung sollte zur Angabe des Liefer-/Leistungsdatums entweder BT-72 \"Actual delivery date\", BG-14 \"Invoicing period\" oder in jeder Rechnungsposition BG-26 \"Invoice line period\" enthalten.",
          "location": "/Invoice[1]"
        }
      ]
    }
  ],
  "note": null,
  "totalsCheck": [
    {
      "rule": "BR-CO-10",
      "text": "Sum of invoice line net amount (BT-106) = sum of all line net amounts",
      "expected": 314.86,
      "actual": 314.86,
      "ok": true
    },
    {
      "rule": "BR-CO-13",
      "text": "Invoice total amount without VAT (BT-109) = sum of lines − allowances + charges",
      "expected": 314.86,
      "actual": 314.86,
      "ok": true
    }
  ]
}

valid is true when no message has the severity error. The answer is 200 for invalid invoices too – a failed validation is a result, not an error.

Read it as JSON

POST /v1/parse, same invoice, shortened to a few fields. Amounts and dates stay strings exactly as written in the file, so nothing is lost by rounding:

{
  "source": "xml",
  "syntax": "UBL",
  "profile": "XRechnung 3.0",
  "invoice": {
    "number": "123456XX",
    "issueDate": "2016-04-04",
    "typeCode": "380",
    "currencyCode": "EUR",
    "buyerReference": "04011000-12345-03",
    "seller": {
      "name": "[Seller name]",
      "vatIdentifier": "DE 123456789",
      "address": {
        "city": "[Seller city]",
        "postCode": "12345",
        "countryCode": "DE"
      }
    },
    "totals": {
      "totalWithoutVat": "314.86",
      "totalVatAmount": "22.04",
      "totalWithVat": "336.9",
      "amountDueForPayment": "336.9"
    },
    "lines": [
      {
        "quantity": "1",
        "unitCode": "XPP",
        "netAmount": "288.79",
        "vatRate": "7",
        "itemName": "Zeitschrift [...]",
        "description": "Zeitschrift Inland"
      }
    ]
  },
  "unmapped": []
}

Convert XRechnung to ZUGFeRD

POST /v1/convert?to=zugferd with the same XRechnung. The answer lists every change and every loss and carries the validation of the result; the file itself is in document.base64 (left out here):

{
  "target": "zugferd",
  "changes": [
    "Specification identifier (BT-24): urn:cen.eu:en16931:2017 instead of urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0"
  ],
  "losses": [],
  "ignoredAdditions": [],
  "validation": {
    "valid": true,
    "summary": {
      "errors": 0,
      "warnings": 1,
      "infos": 0
    }
  },
  "document": {
    "contentType": "application/xml",
    "filename": "invoice_zugferd.xml"
  }
}

When data is missing

An XRechnung needs data that a ZUGFeRD invoice may not contain, such as the buyer reference (Leitweg-ID), electronic addresses and a seller contact. POST /v1/convert?to=xrechnung-ubl with the ZUGFeRD PDF from above answers 422 and lists what is needed (shortened to the first two of nine fields):

{
  "error": {
    "code": "additions_required",
    "message": "The target format requires data the document does not contain. Nothing is invented: send it as \"additions\".",
    "missing": [
      {
        "path": "buyerReference",
        "name": "Buyer reference / Leitweg-ID",
        "rule": "BR-DE-15",
        "help": "For invoices to German public authorities the Leitweg-ID, otherwise the reference your customer gave you.",
        "optional": false,
        "provided": false
      },
      {
        "path": "seller.electronicAddress",
        "name": "Seller email address for invoices",
        "rule": "PEPPOL-EN16931-R020",
        "help": null,
        "optional": false,
        "provided": false
      }
    ]
  }
}

Send the values as additions, keyed by path, and repeat the request. Existing data is never overwritten; additions the invoice does not need are listed in ignoredAdditions.

{
  "document": "<Base64>",
  "to": "xrechnung-ubl",
  "additions": {
    "buyerReference": "04011000-12345-03",
    "seller.electronicAddress": "invoices@example.com",
    "payment.iban": "DE02120300000000202051"
  }
}

Errors

Errors come as JSON with a stable code and a readable message:

{"error": {"code": "not_an_e_invoice", "message": "…"}}
StatusCodeMeaning
400empty, bad_json, missing_documentNo file, invalid JSON or no document field
400invalid_target, invalid_lang, invalid_additionsUnknown target format or language, additions not an object of strings
401unauthorizedRequest did not come through RapidAPI
404, 405not_found, method_not_allowedUnknown endpoint or wrong HTTP method
413too_largeRequest larger than 50 MB
422not_an_e_invoice, not_readableNeither UBL nor CII, or a PDF without embedded invoice
422doctype_not_allowedXML with DOCTYPE or ENTITY declarations is rejected for security
422additions_requiredThe target format needs data – see missing
422not_convertible, already_target_formatConversion not possible or not needed
422result_not_validThe converted invoice fails the official validation, so it is not returned (report in validation)
503, 504busy, timeoutToo many requests at once, or processing took longer than 30 s – retry later
500internal_errorOur fault; under RapidAPI's billing rules, answers with status 500 or higher are not counted

Rule sets and versions

Rule setVersion
XML SchemaUBL 2.1, UN/CEFACT CII D16B
EN 16931 validation artefacts (CEN)1.3.17 (UBL and CII)
XRechnung (KoSIT Schematron)XRechnung 3.0, Schematron 2.6.0
ZUGFeRD / Factur-XZUGFeRD 2.5.2 / Factur-X 1.09.2, profiles MINIMUM, BASIC WL, BASIC, EN 16931, EXTENDED

Peppol BIS Billing 3.0 invoices are read and validated against EN 16931. When a new version of a rule set is published, the API is updated and this table with it.

Limits

Use with AI agents (MCP)

RapidAPI offers its APIs as tools for AI agents through the Model Context Protocol (MCP). With your RapidAPI key, an agent can call each endpoint as a tool; the plan you subscribed to applies.

Privacy and terms

Invoices contain personal data. We process them only to answer your request, in memory, and keep nothing. Details: API privacy notice and data processing agreement (Art. 28 GDPR). The API terms of use apply in addition to RapidAPI's terms.

Licences

The validation uses the official rule artefacts and open-source components under their licences: the CEN EN 16931 artefacts and KoSIT XRechnung rules (EUPL 1.2, Apache 2.0), the Factur-X rules (licence), SaxonJS (copyright notice and disclaimer), xmllint-wasm, pdf-lib, fontkit, big.js and the DejaVu fonts.

Provider

Haag Automation, Julian Haag, Mühlstraße 35, 74360 Ilsfeld, Germany · julian@haag-automation.com · xmoo is a brand of Haag Automation. All APIs