E-invoice API: create, check and read XRechnung, ZUGFeRD and Peppol BIS

An e-invoice API turns invoice data into a file the buyer’s system can process and a tax administration accepts. Veriport does three things over HTTPS: POST /einvoice/create writes an XRechnung (UBL or CII), a ZUGFeRD / Factur-X PDF or a Peppol BIS Billing 3.0 invoice from one JSON model; POST /einvoice/validate checks any e-invoice against the XML schema and the published CEN, XRechnung and Peppol rules; POST /einvoice/parse reads a received one back into that JSON or a readable PDF. It never sends an invoice and never keeps one.

The three calls

One key for all three; Idempotency-Key on create (a retry within 24 hours returns the same file, rebuilt from the request, and is not charged again); up to 4.5 MB a request; RFC 9457 problem documents for every refusal.

EndpointYou sendYou get backCost
POST /einvoice/createThe invoice as JSON (seller, buyer, lines, VAT, allowances and charges, payment, references) and formatThe file (XML, or the PDF for zugferd) with Veriport-Validation: valid; profile=…; errors=0; warnings=N; ?envelope=json for { file (base64), contentType, validation, defaults }20 calls
POST /einvoice/validateAn XML or a hybrid PDF, raw or multipart{ valid, profile, syntax, errors[{rule, severity, path, message}], warnings[], checks }, rule ids as published5 calls (anonymous allowed)
POST /einvoice/parseAn XML or a hybrid PDFThe invoice in the create model, and notInJson: what the file holds that the JSON does not; ?as=pdf a readable PDF/A-35 calls
invoice.json — the request body (InvoiceInput)
{"format":"xrechnung-ubl","number":"2026-017","issueDate":"2026-10-20","paymentTermsDays":14,"buyerReference":"EK-2026-0345","deliveryDate":"2026-10-15",
 "seller":{"name":"Lena Hartmann — Webdesign","vatId":"DE136695976","address":{"street":"Lindenstraße 14","postalCode":"10969","city":"Berlin","countryCode":"DE"},
           "contact":{"name":"Lena Hartmann","phone":"+49 30 1234567","email":"rechnung@hartmann-webdesign.example"}},
 "buyer":{"name":"Mustermann Maschinenbau GmbH","address":{"street":"Industrieweg 7","postalCode":"70565","city":"Stuttgart","countryCode":"DE"},
          "contact":{"email":"buchhaltung@mustermann.example"}},
 "payment":{"iban":"DE02120300000000202051"},
 "lines":[{"name":"Relaunch Website","quantity":24,"unitCode":"HUR","unitPrice":95,"vat":{"category":"S","rate":19}}]}
200 OK — POST /einvoice/validate on the produced file
{
  "valid": true,
  "profile": "xrechnung",
  "format": "xrechnung-ubl",
  "syntax": "ubl",
  "container": "xml",
  "attachment": null,
  "errors": [],
  "warnings": [],
  "checks": {
    "schema": {
      "status": "passed",
      "name": "UBL 2.1 (OASIS) and UN/CEFACT CII D16B XML schemas, from the KoSIT XRechnung validator configuration v2026-08-31"
    },
    "ruleSets": [
      {
        "id": "cen-ubl",
        "name": "CEN EN 16931 validation artefacts (UBL)",
        "release": "1.3.16",
        "sha256": "bafada015efbc5248bf5e05ad2191e1d9833ef96e9dd5f4bce420a747342da85",
        "fatal": 0,
        "warnings": 0
      },
      {
        "id": "xrechnung-ubl",
        "name": "XRechnung 3.0.2 schematron (UBL)",
        "release": "2.6.0",
        "sha256": "ca5e07afd04e72cd283d581590ffff3a15f4aac9d2f40c993345d78e67ea22b4",
        "fatal": 0,
        "warnings": 0
      }
    ],
    "pdfa": null,
    "veriport": 0
  },
  "ms": 84
}

The same create call in six languages

Send the JSON, save the file, read the header. A refusal is a problem document whose code is stable: einvoice_unsupported (with feature and path) or einvoice_invalid (with violations[]: rule, severity, path, message).

curl
# create: the file comes back; its check is in the Veriport-Validation header
curl -X POST "https://veriport.io/api/v1/einvoice/create" \
  -H "Authorization: Bearer vp_live_YOUR_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: inv-2026-017" \
  --data @invoice.json -o xrechnung-2026-017.xml -D -

# validate any e-invoice (no key needed for 4 checks a day)
curl -X POST "https://veriport.io/api/v1/einvoice/validate" -H "Content-Type: application/xml" --data-binary @received.xml

# parse a received Factur-X into the same JSON, or a readable PDF
curl -X POST "https://veriport.io/api/v1/einvoice/parse" -H "Authorization: Bearer vp_live_YOUR_KEY" \
  -H "Content-Type: application/pdf" --data-binary @facture.pdf

Formats

The format field picks the file; the JSON is the same for all four.

formatStandardSyntaxSeller (v1)File
xrechnung-ublXRechnung 3.0.2 (German CIUS of EN 16931)UBL 2.1 Invoice / CreditNoteGermanyXML
xrechnung-ciiXRechnung 3.0.2UN/CEFACT CII D16BGermanyXML
zugferdZUGFeRD 2.x / Factur-X 1.0, profile EN 16931 (« COMFORT »)CII D16B embedded as factur-x.xmlGermanyPDF/A-3b
peppol-bisPeppol BIS Billing 3.0 with the Belgian rules (enterprise number under 0208, structured communication, franchise mention)UBL 2.1 Invoice / CreditNoteBelgiumXML

Validator evidence: every sample through the official validators

Every sample case was written through the API’s own code path and checked by the reference validators, with the verdict each tool gave (run of 2026-10-28, pnpm validate:official; the raw reports are kept with the samples). 41 cases, 175 files; 12 broken documents (one published rule broken each): KoSIT gave the expected verdict on 12 of 12, with the rule named. The last row compares this API’s own validator (POST /einvoice/validate) with KoSIT rule id by rule id, broken documents included.

  • KoSIT validator: 1.6.3 (SHA-256 799e64befca9…)
  • XRechnung validator configuration (XRechnung 3.0.2, CEN schematron 1.3.16, xrechnung-schematron 2.6.0): v2026-08-31 (SHA-256 2530cd107c41…)
  • Peppol BIS Billing 3.0 rules (PEPPOL-EN16931-UBL.sch): 3.0.21 (SHA-256 9616cfffb418…)
  • SchXslt (schematron to XSLT, run on the Saxon HE inside the KoSIT validator): 1.10.1 (SHA-256 4f4f21edab7b…)
  • veraPDF (greenfield): 1.30.3 (SHA-256 b512bf249455…)
  • Mustang CLI: 2.26.0 (SHA-256 42d7868cb682…)
ValidatorWhat it checksResult
KoSIT validator 1.6.3 + XRechnung configuration v2026-08-31XML schema (UBL 2.1 / CII D16B), CEN EN 16931 rules 1.3.16, XRechnung 3.0.2 rules 2.6.0 (XRechnung UBL and CII); EN 16931 rules on the XML inside the ZUGFeRD PDF93 of 93 XML files accepted
KoSIT validator 1.6.3 + Peppol BIS Billing 3.0.21XML schema (UBL 2.1), CEN EN 16931 rules 1.3.16, PEPPOL-EN16931-UBL 3.0.21 (compiled with SchXslt 1.10.1)10 of 10 Peppol files accepted
veraPDF 1.30.3ISO 19005-3 PDF/A-3B validation profile (ZUGFeRD PDFs and readable copies)72 of 72 PDFs compliant
Mustang 2.26.0ZUGFeRD / Factur-X: PDF/A-3 (veraPDF inside), XMP and attachment, embedded XML against the Factur-X EN 16931 schematron and arithmetic31 of 31 ZUGFeRD PDFs valid
In-process validator (POST /einvoice/validate) against KoSITthe same rule ids, errors and warnings, on every XML file115 of 115 files agree

What the check runs

The XML schema first (UBL 2.1 or CII D16B, the files the KoSIT configuration ships, on libxml2), then the official schematron of the document’s profile, evaluated in-process from the files as published: the CEN EN 16931 rules 1.3.16 always; the XRechnung 3.0.2 rules 2.6.0 for an XRechnung, with the severities of the KoSIT configuration (a unit code outside Recommendation 20 is a warning there, fatal elsewhere); the Peppol BIS Billing 3.0.21 rules for a Peppol document. Rule ids and messages are the published ones: CEN and Peppol in English, the XRechnung BR-DE rules in German.

Veriport’s own checks of national practice come as warnings only, never as a published rule: a German VAT identifier whose check digit fails (VP-DE-001), a Leitweg-ID whose check digits fail (VP-DE-003), a Belgian rate other than 21, 12 or 6 % (VP-BE-004), a structured communication whose mod 97 fails (VP-BE-002)… The engine itself holds 156 rules it checks before writing, 95 of them EN 16931 rules, each one tested on a document that breaks it.

Not checked per request: the PDF/A-3 conformance of a hybrid PDF (veraPDF does it on the sample set; checks.pdfa says not_checked), and the Factur-X MINIMUM, BASIC WL and EXTENDED profiles (refused with 422).

What the engine adds, and tells you

Only where a rule needs a value you did not give, and each one is returned in defaults (and counted in the Veriport-Defaults header):

  • VATEX-EU-AE on a reverse-charge breakdown and VATEX-EU-IC on an intra-Community supply without a reason of your own (BR-AE-10, BR-IC-10).
  • The statutory mention for seller.vatScheme: "small_business": § 19 UStG for a German Kleinunternehmer, the franchise of article 56bis for a Belgian business (BR-E-10).
  • The electronic address: 0208 + the enterprise number for a Belgian party on Peppol, EM + the contact e-mail on XRechnung and ZUGFeRD (BT-34, BT-49 are mandatory there).
  • The Steuernummer repeated as seller identifier (BT-29) when a German seller has no VAT identifier (BR-CO-26), the buyer’s address as deliver-to address of an intra-Community supply (BR-IC-12), the due date from paymentTermsDays.

What v1 does not do, and what it refuses

Nothing is ever dropped silently: a field the writers cannot put in the file is refused with 422 einvoice_unsupported, naming the feature and its path, and nothing is charged. Fields it refuses: attachments (BG-24), prepaid_amount (BT-113), rounding_amount (BT-114), deliver_to_party (BT-80), project_reference (BT-11), receiving_advice (BT-15), despatch_advice (BT-16), tender_reference (BT-17), invoiced_object (BT-18), accounting_reference (BT-19), tax_representative (BG-11), payee (BG-10), tax_point_date (BT-7, BT-8), tax_currency (BT-6, BT-111), payment_means (BG-19), payment_discount (BT-20), line_period (BG-26), line_allowances_charges (BG-28), item_identifiers (BT-158), item_origin_country (BT-159), item_attributes (BG-32), line_note (BT-127), line_references (BT-128), price_details (BT-147, BT-148), line_net_amount (BT-131), seller_identifier (BT-29), buyer_identifier (BT-46), computed_totals (BG-22, BG-23), and any field the model does not know (unknown_field). Values it refuses: a currency other than EUR (currency); VAT categories G (export), O (outside the scope), L, M and B (vat_category_g …); a seller outside Germany (XRechnung, ZUGFeRD) or Belgium (Peppol BIS) (seller_country), a buyer outside the EU (buyer_outside_eu); negative quantities (negative_line: issue a credit note), more than 500 lines (line_count); payment by card or direct debit (payment_means), invoice types other than 380, 381, 384 (document_type); two different exemption reasons for one VAT category and rate (multiple_exemption_reasons: EN 16931 holds one per breakdown).

  • It does not send invoices over Peppol or any other network: that needs a certified access point. Send the file through your access point or a provider such as Recommand; Veriport produces and checks the file.
  • It does not keep documents: the input and the file live in memory for the request; the usage row keeps the endpoint and the format, the log line the size and the timing. Archiving (GoBD in Germany) stays with you.
  • It does not give tax advice: it writes and checks the format from the data you send. The VAT category, the exemption reason and the rate are your decision (or your tax adviser’s).
  • v1 formats only: XRechnung 3.0.2, ZUGFeRD 2.x / Factur-X EN 16931, Peppol BIS Billing 3.0 with the Belgian rules. Later, on demand: XRechnung 4.0 (expected spring 2027), the ZUGFeRD XRECHNUNG profile, Factur-X EXTENDED, the French platform (PA) specifics, sellers outside Germany and Belgium.

Price per 1,000 invoices, against the named alternatives

Read on the providers’ public pricing pages on 2026-10-28. They are access points: their price includes sending the invoice over Peppol, which Veriport does not do; ours is the file and its check. Our prices include the tax of your country, charged by Stripe as merchant of record.

OfferWhat it isPer documentPer 1,000
Recommand 200 plan€29 a month for 200 documents sent or received over Peppol€0.145 (€0.20 above 200)€145
e-invoice.be ProPeppol sending, per invoice€0.25 (Enterprise from €0.18)€250
Veriport create, 10,000-call pack$29 once, 20 calls an invoice$0.058$58.00
Veriport create, 2,000-call pack$9 once$0.090$90.00
Veriport create, monthly plan$15 a month for 5,000 calls = 250 invoices$0.060$60.00
Veriport validate or parse5 calls a document$0.014–$0.022$14.50–$22.50

Free before any of this: 100 calls a month with a key (five created invoices, or twenty checks), 4 checks a day without one. A refused request (400, 413, 422) is never counted.

Questions

Does Veriport send the invoice to my customer?
No. Sending over Peppol needs a certified access point, and e-mailing an XRechnung or a ZUGFeRD PDF is your system’s job. The API returns the file; you send it, or your access point does.
Do you store the invoices?
No. The JSON and the file exist in memory during the request and are gone after it. The usage row keeps the endpoint and the format; the log line keeps the size, the line count and the timing, never the content. An Idempotency-Key replay rebuilds the file from your repeated request instead of storing it.
Is the produced file guaranteed to be accepted?
Every file is checked before it leaves: the XML schema and the official rules of its format run on it, and a fatal finding withholds the file (422 einvoice_invalid). That is the same check the receiving side runs. What no format check can decide is whether the content is right: the VAT category, the rate, the exemption reason are yours.
Which XRechnung version, and what about 4.0?
XRechnung 3.0.2, the version in force (KoSIT configuration v2026-08-31). XRechnung 4.0 is expected in spring 2027; it will be a new format value, announced in the changelog, with its own evidence run.
Can I validate without a key?
Yes: 4 checks a day per address, the free tools use exactly that. With a free key, 100 calls a month (20 checks).
Is this tax advice?
No. The API writes and checks the format from the data you send, and says which published rule a document breaks. Whether a supply is reverse charged or exempt is a tax question for you or your adviser.

100 calls a month, no card: five invoices or twenty checks.

Get a free key

Sources