1. Overview

The server provides three tools and one resource that AI agents can call through the Model Context Protocol.

ScenarioTool / Resource
Check an invoice before sendingvalidate_invoice
Read XML data from a ZUGFeRD PDFextract_xml
Check whether PDF and XML matchcheck_consistency
Query available formats and rule setsvalidation://rules
Every response is structured JSON. No free text — each error contains a rule ID, the affected field, an error message, an actionable fix suggestion and an XPath.

2. Architecture & Data Flow

AI agent │ │ MCP message (JSON-RPC over stdio) ▼ ┌─────────────────────────────────────────┐ │ zugferd-mcp-client (npm, local) │ │ │ │ validate_invoice · extract_xml │ │ check_consistency │ │ │ │ │ HTTPS REST request │ │ ▼ │ │ ZUGFeRD Validator API (hosted) │ │ api.zugferd-validator.de │ │ Auth: X-Api-Key │ │ │ │ │ Base64 → Mustang + KoSIT (parallel) │ │ ▼ │ │ Report parser + fix-suggestions DB │ └─────────────────┬───────────────────────┘ │ Structured JSON ▼ AI agent receives result

Flow for validate_invoice

1
MCP client receives the call — Base64 file + parameters from the AI agent
2
HTTPS request to the API — POST /v1/validate with X-Api-Key header
3
Auth & rate limit — the API checks the key, the weekly quota and the hourly limit; above the hourly limit the call runs in the slow lane
4
Dual validation (parallel) — Mustang CLI (ZUGFeRD/Factur-X) and the KoSIT validation tool (XRechnung + EN 16931 CII) run at the same time (max. 30s)
5
Consolidate results — errors from both engines are merged and deduplicated; fix suggestions are added
6
JSON result — the API responds, the MCP client forwards it to the AI agent

3. Validation Engines

Every request is checked by two engines at once. The results are consolidated, deduplicated and returned.

EngineVersionCovered formats
Mustang Project v2.26.0 ZUGFeRD 1.0–2.4, Factur-X 1.0, XRechnung (general)
KoSIT validation tool v1.6.3 XRechnung 3.0.2 (CII + UBL), EN 16931 CII/UBL (plain), Factur-X 1.07.3 / 1.08 (all profiles)
Consolidated response: errors from both engines appear in the same errors list. Duplicates (same rule ID + message) are removed automatically. KoSIT errors are only included when a matching validation scenario was detected — for unknown GuidelineIDs, Mustang runs alone.

4. Installation & Configuration

1
Get an API key
# Register for free:
https://zugferd-validator.de/portal
2
Install the MCP client
npm install -g zugferd-mcp-client
3
Configure in your MCP client
// mcp_config.json (path varies by client)
{
  "mcpServers": {
    "zugferd": {
      "command": "zugferd-mcp-client",
      "env": {
        "ZUGFERD_API_KEY": "zv_your_key_here"
      }
    }
  }
}

Direct REST API

The API can also be called directly, without the MCP client:

# Example: validation via curl
curl -X POST https://api.zugferd-validator.de/v1/validate \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: zv_your_key_here" \
  -d '{"file_content":"<base64>","file_type":"xml"}'

Environment variables (MCP client)

VariableDescriptionDefault
ZUGFERD_API_KEYAPI key from the portal– (required)
ZUGFERD_API_URLAPI base URLhttps://api.zugferd-validator.de

5. Tools

validate_invoice TOOL

Validates an e-invoice (ZUGFeRD/Factur-X PDF or XRechnung XML) against EN 16931 and the German extensions. Returns structured errors, warnings and fix suggestions.

Input schema

file_content * string Base64-encoded content of the PDF or XML file. Data URL prefixes (data:application/pdf;base64,…) are removed automatically.
file_type * enum "pdf" for ZUGFeRD/Factur-X PDFs · "xml" for plain XRechnung/CII XML files
profile enum? Optional profile hint. If it differs from the detected profile, a warning with rule_id: "PROFILE_MISMATCH" is generated.
Values: minimum · basic_wl · basic · en16931 · extended · xrechnung

Output schema

{
  "valid":    true,
  "format":   "ZUGFeRD 2.x",
  "profile":  "EN16931",
  "container": "pdf",
  "syntax":   "CII",
  "summary":  "No issues found",
  "errors":   [],
  "warnings": [],
  "notices":  [],
  "metadata": {
    "invoice_number": "RE-2026-001",
    "issue_date":     "2026-04-01",
    "seller":         "Musterfirma GmbH",
    "buyer":          "Beispiel AG",
    "total_amount":   "1190.00",
    "currency":       "EUR"
  }
}
{
  "valid":   false,
  "format":  "XRechnung 3.0",
  "profile": "XRECHNUNG",
  "summary": "1 error, 1 warning found",
  "errors": [
    {
      "severity":       "error",
      "rule_id":        "BR-DE-15",
      "field":          "BT-10",
      "message":        "The buyer reference / routing ID (BT-10) must be provided.",
      "fix_suggestion": "Add the buyer reference / routing ID (BT-10) in 'ram:ApplicableHeaderTradeAgreement/ram:BuyerReference'.",
      "xpath":          "//ram:ApplicableHeaderTradeAgreement/ram:BuyerReference"
    }
  ],
  "warnings": [
    {
      "severity":       "warning",
      "rule_id":        "BR-DE-17",
      "field":          "BT-3",
      "message":        "The element \"Invoice type code\" (BT-3) should carry only the following codes from code list UNTDID 1001: 326, 380, 384, 389 …",
      "fix_suggestion": "On a prepayment invoice (386) the warning is acceptable: XRechnung does not recommend the code, but it allows it. To avoid the warning, use 326 (partial invoice).",
      "xpath":          "//rsm:ExchangedDocument/ram:TypeCode"
    }
  ]
}

Output fields

FieldTypeRequiredDescription
validbooleanYestrue when not a single error was found
formatstringNoDetected format: "ZUGFeRD 2.x" or "ZUGFeRD 1.0" for PDF, otherwise e.g. "XRechnung 3.0", "Peppol BIS Billing 3.0" or "EN 16931"; null when no XML can be extracted
profilestringNoDetected profile in uppercase, e.g. "EN16931", "EXTENDED" or "XRECHNUNG"; null when no XML can be extracted
containerstringNopdf (PDF with embedded e-invoice) or xml (XML file)
syntaxstringNoCII or UBL
summarystringYesHuman-readable summary, e.g. "2 errors, 1 warning found"
errorsarrayYesValidation errors — these make the invoice invalid
warningsarrayYesWarnings — the invoice can still be valid
noticesarrayNoInformational notices with no impact on validity
metadataobjectNoExtracted invoice data — present for PDFs when the embedded XML was read successfully, and always for file_type: "xml"
extract_xml TOOL

Extracts the embedded XML document from a ZUGFeRD/Factur-X PDF. Returns the full XML content as a string, together with the detected format and profile.

Input

pdf_content * string Base64-encoded content of the ZUGFeRD/Factur-X PDF file.

Output

{
  "xml_content": "<?xml version='1.0' encoding='UTF-8'?>\n<rsm:CrossIndustryInvoice ...>...</rsm:CrossIndustryInvoice>",
  "format":      "ZUGFeRD 2.x",
  "profile":     "EN16931"
}
The extracted xml_content can be passed directly (after Base64 encoding) to validate_invoice with file_type: "xml".
check_consistency TOOL

Checks whether the human-readable data in the PDF (invoice number, date, total amount) matches the machine data stored in the XML attachment. Reveals tampering or data-entry errors.

Input

pdf_content * string Base64-encoded content of the ZUGFeRD/Factur-X PDF file.

Output

{
  "consistent":    true,
  "discrepancies": []
}
{
  "consistent":    false,
  "discrepancies": [
    {
      "field":     "total_amount",
      "pdf_value": "1190.00",
      "xml_value": "1180.00",
      "message":   "The total amount in the PDF ('1190.00') differs from the one in the XML ('1180.00')."
    }
  ]
}

Checked fields & tolerances

FieldDetection pattern in the PDFTolerance
invoice_numberRechnungsnummer:, Rechnung Nr., Invoice No.Case, spaces and hyphens ignored
issue_dateRechnungsdatum:, Datum:, Invoice Date:None — date formats are normalized to ISO 8601
total_amountGesamtbetrag:, Rechnungsbetrag:, Total:±0.01 EUR (rounding differences)
Text detection relies on the machine-readable PDF text. Scanned PDFs without an OCR layer are not supported.
create_invoice TOOL

Builds a valid e-invoice from structured data, either as a ZUGFeRD PDF (PDF/A-3 with embedded EN 16931 CII XML) or as plain XRechnung XML (CII with the XRechnung 3.0 CIUS and routing ID for B2G). Multiple line items, VAT categories S, Z, E, AE, K and G, disbursements without VAT, allowances and charges, delivery date or service period, amounts already paid, references to earlier invoices, attachments, and payment by transfer or SEPA direct debit. Stateless: nothing is stored, attachments included. REST: POST /v1/create. Amounts in cents, date as YYYY-MM-DD.

Input (excerpt)

format *stringzugferd (PDF) or xrechnung (XML)
document_typestringDocument kind (BT-3), default invoice: credit_note (credit note / cancellation; amounts stay positive, and the PDF then shows no amount due), corrected (corrected invoice), self_billed (self-billing), prepayment (advance or down-payment invoice; XRechnung warns about 386 with BR-DE-17, the invoice stays valid), partial (partial invoice), debit_note (debit note). Raw override via type_code.
preceding_invoicesarrayReferences to earlier invoices (BG-3), such as advance invoices: [{ number, date }], at most 20. The XML carries only the first as BG-3 in both formats, because the EN 16931 schema allows only one; the note (BT-22) lists all others.
preceding_invoiceobjectShort form for a single reference { number, date } (BT-25/26), for example on a credit note. Not together with preceding_invoices.
number, date *stringDocument number and date (YYYY-MM-DD)
sellerobjectSeller (name, street, zip, city, country, vat_id, tax_number, email, phone, contact_name). Required without a sender profile; if seller is missing, we use the sender profile of your account.
seller.tax_numberstringGerman tax number (Steuernummer, BT-32) for sellers without a VAT ID. Without a VAT ID we also write the tax number as the seller identifier (BT-29), because EN 16931 requires such an identifier (BR-CO-26).
buyer *objectBuyer (name, street, zip, city, country, vat_id, email …)
lines *arrayLine items: name, quantity, unit_net_price_cents, vat_category, vat_rate, pass_through. vat_category: default S; Z = 0 %, E = exempt, AE = reverse charge, K = intra-community supply, G = export.
lines[].pass_throughbooleantrue = disbursement without VAT (durchlaufender Posten, § 10 (1) UStG): vat_rate: 0, vat_category omitted or E. In the XML it is category E with reason and code VATEX-EU-79-C. Not together with other category E lines (BR-E-01). In the XML the item counts towards the net total (BT-109) but not towards VAT, as EN 16931 requires. If your own invoice shows the net total without it, the two figures differ; the grand total matches.
delivery_date, billing_periodstring, objectDelivery date (BT-72) or service period (BG-14), the period as { start, end }, end not before start. With a period and no delivery date we set BT-72 to the end of the period. On a prepayment invoice (prepayment) with neither, we leave BT-72 out; the ZUGFeRD check then warns (PEPPOL-EN16931-R008), which is expected. Otherwise the invoice date applies.
prepaid_amount_cents, prepaid_vat_centsintegerAmount already paid, such as an advance payment, gross in cents (BT-113). We deduct it from the amount due (BR-CO-16); it may not exceed the invoice total. We add a note (BT-22) with the deduction and the references; with prepaid_vat_cents it also states the net amount and the VAT of the deduction, as German VAT law (§ 14 (5) UStG) requires for final invoices.
allowances_chargesarrayAllowances/charges at document level
buyer_referencestringRouting ID (BT-10) — required for xrechnung
payment_terms, due_date, payment_meansstring/objectPayment terms, due date, IBAN/BIC
direct_debitobjectSEPA direct debit: mandate_reference (BT-89), creditor_id (BT-90, otherwise taken from the sender profile), debtor_iban (BT-91). Sets payment means code 59. For ZUGFeRD the IBAN may be masked (DE89**************3000), for XRechnung it may not. We then write no payee account (BR-DE-25-b). If you send direct_debit, we need all three; leave out payment_means.type_code or set it to 59.
attachmentsarrayAttachments (BG-24): [{ filename, content_base64, description? }]. Allowed are PDF, PNG, JPEG, CSV, XLSX and ODS (BR-CL-24), at most 10 files, together at most 5 MB. File names must be unique. From two attachments on, the check reports the warning CII-SR-476 (with two descriptions also CII-SR-475); the rule counts all attachments together, and the invoice stays valid. Via MCP, the response for a ZUGFeRD invoice with attachments contains only pdf_base64; the XML is inside the PDF.
seller_logostringLogo Base64 (PNG/JPG) — Automate/Scale tiers only
self_checkbooleanCheck immediately against EN 16931 (slower). Default false.

Output

{
  "format":     "zugferd",
  "profile":    "EN16931",
  "pdf_base64": "JVBERi0xLjc…",
  "xml":        "<?xml version=\"1.0\"…",
  "branding":   "free_footer"
}

xml always comes along, except via MCP for a ZUGFeRD invoice with attachments. Then extract_xml gets it from the PDF.

Attachments

Attachments sit only in the XML of the e-invoice (BT-125), not as an extra attachment of the PDF. Each file may have at most 5 MB, and all together at most 5 MB as well, measured after decoding. We take the type from the extension (.pdf, .png, .jpg, .jpeg, .csv, .xlsx, .ods) and check it against the first bytes of the file; if they do not match, you get 400. A file name has up to 100 characters, without / and \ and without a leading dot; two names that differ only in upper and lower case count as the same. description (BT-123) has at most 200 characters. content_base64 holds plain Base64; we cut off a data:…;base64, prefix. Base64 makes files a third larger: 5 MB of attachments become about 7 MB in the request, and the response carries them again inside the invoice. Via MCP, better send only small attachments.

Sender profile

What the request leaves out, we fill in from the sender profile of your account: the whole seller if seller is missing (its tax number only if the profile has no VAT ID), plus IBAN, BIC, account holder, payment terms, logo and, for a direct debit, the creditor ID. What the request contains always wins. You maintain the profile in your account or via the API: GET /v1/keys/issuer reads it, PUT /v1/keys/issuer saves it, DELETE /v1/keys/issuer removes it. Fields: name, street, zip, city, country, vat_id, tax_number, email, phone, contact_name, iban, bic, account_name, payment_terms, logo_b64, creditor_id. PUT changes only the fields in the request; an empty string or null clears a field. PUT rejects a VAT ID without country code, a tax number in the wrong form and an invalid creditor ID with 400.

Errors

We answer invalid input with 400 and { "error": "Ungültige Eingabe", "details": "…" }. Schema errors name path and reason, several separated by semicolons. Problems with the content, such as a direct debit without mandate reference, come as whole sentences in details (in German), several separated by spaces. A date not in the form YYYY-MM-DD names its field, for example preceding_invoices[0].date (counted from 0). Via MCP, problems with the content and dates come back as the same object. Schema errors are caught earlier by the MCP protocol itself: you get a tool error (isError: true) with the text of the schema check, not an object with error and details.

Self-check is decoupled: by default the invoice comes back immediately. You obtain the conformity proof in a second step via validate_invoice on the returned pdf_base64 or xml, or you set self_check: true for a single round trip. The server determines the branding from the tier (footer on the Check tier, white-label from the Automate tier). Separate quota for creating: Check 1 invoice a month; Automate and Scale unlimited, fair use via the hourly limit.
Sandbox key (zv_test_…, via GET /v1/keys/me): for free integration without using the live quota. Created invoices carry a tiled "DEMO" watermark on every page plus an XML marker — fully testable, but worthless as a real invoice. 100/day, 3/minute; counted separately from the live quota.
POST /v1/view REST

Show an invoice in readable form. Turns an XRechnung (UBL or CII), a CII invoice or a ZUGFeRD/Factur-X PDF into a readable view. It is based on the KoSIT visualisation (Apache 2.0) in the version that comes with Mustang 2.26.0. No key needed; it does not count against the check quota but has its own daily limit: 20 per IP address without an account, 50 on the free plan, 500 on Automate, 2,000 on Scale (otherwise 429). Nothing is stored or logged. As an MCP tool: view_invoice.

Fields

file_content *File as Base64, at most 15 MB (otherwise 413)
file_type *"pdf" or "xml"
languagede, en or fr, default de
checkAlso check the file, default true (false via MCP)

Response: readable, html, format, profile, syntax, attachments (name, type, size, Base64), check (valid, number of errors and warnings, first messages) and limit. The HTML is cleaned: no script, no event handlers, no links with a target, no images or forms, plus a Content Security Policy. Still show it only in an <iframe sandbox> without rights, and save attachments as files instead of opening them in the browser. If the file cannot be shown, readable is false and check says why; a PDF without an e-invoice gives 422.

GET /v1/leitweg/check REST

Check a Leitweg-ID. Checks structure, code (state 01 to 16, federal 99) and check digits (modulo 97-10) according to the KoSIT format specification 2.0.2. If the check digits are wrong or missing, the response gives the matching ID in corrected_id. It does not say whether the ID is assigned; there is no public directory. No key needed, no quota, at most 120 calls per minute (else 429). Nothing is stored. As MCP tool: check_leitweg_id.

Fields

id *The Leitweg-ID, at most 200 characters
languagede, en or fr, default de

Response (200, also for an invalid ID): valid, leitweg_id (normalised), parts (coarse, fine, check_digits), region, expected_check_digits, corrected_id, errors and warnings (each code and message), notice. Have a suggested ID confirmed by the contracting authority before you use it.

POST /v1/stapel REST

Batch check on the server. For many invoices at once, without your program or browser staying connected the whole time: upload files, start, fetch the results later. From the Automate plan. The invoices stay encrypted on our server in Germany until checked and are deleted right after. The results stay for seven days, then we delete them. When everything is done, an email goes to the account address. In a company account the owner can switch this off for the other users (PATCH /v1/org/settings with { "stapel_ablage": false }).

Calls

POST /v1/stapelCreate a batch. Body { sprache?: "de" | "en" | "fr" } (language of the email). Response 201 with { id, max_dateien, max_bytes }. At most 3 open batches per key.
POST /v1/stapel/{id}/dateienOne file: { name, file_type: "pdf" | "xml", file_content } (Base64, at most 15 MB). At most 500 files per batch.
POST /v1/stapel/{id}/startUpload done, the check begins. A batch that is never started is deleted after two hours.
GET /v1/stapel/{id}?seit=…Status: status (hochladen = uploading, wartend = queued, fertig = done, angehalten = stopped), anzahl, fertig, ok, mit_fehlern, neu, grund, ablauf. ergebnisse holds the results as with validate_invoice, with nr and reference (file name). With seit=0 you get all of them and the list namen; after that pass the last stand as seit to get only new ones (the last one may come twice).
GET /v1/stapelYour batches.
DELETE /v1/stapel/{id}Delete now, with all files and results.

Each file counts against the weekly quota. When it is used up, the batch stops (status: "angehalten", grund: "kontingent"); the remaining files stay unchecked and are deleted. The server checks such batches only on slots that are free at the moment; direct checks go first.

validation_report TOOL

Returns the check result as a validation report in PDF/A-3b, ready to file and pass on. The report states the result, every message with rule id and field, the time of the check, the format and profile detected, the SHA-256 checksum of the file checked and the rule set, that is the versions of the tools used. Every call runs a fresh check and counts against the weekly quota like any validation. Available as the MCP tool validation_report and over REST as POST /v1/report.

Input

file_content *stringBase64 content of the invoice
file_type *stringpdf or xml
attach_sourcebooleanAppend the invoice checked so it can be read: for a PDF its pages are added after the report, for an XML the content is printed. The file is also embedded unchanged (PDF/A-3, AFRelationship /Source) so it stays machine readable. Default false.
langstringde, en, fr — default de
file_namestringFile name to show in the report. Defaults to rechnung.pdf or rechnung.xml.

Output

Content-Type: application/pdf
Content-Disposition: attachment; filename="validation-report-invoice.pdf"
X-Validation-Result: valid | invalid

Example

curl -X POST https://api.zugferd-validator.de/v1/report \
  -H "X-Api-Key: $ZV_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"file_content\": \"$(base64 -w0 rechnung.pdf)\", \"file_type\": \"pdf\", \"attach_source\": true}" \
  -o bericht.pdf
Nothing is stored. The file is checked for the call and then discarded; so that report and archived invoice can be matched later, the SHA-256 checksum is printed in the report. The report is not a legally binding certificate. It documents a technical check at a point in time against a named rule set, and it does not replace keeping the invoice in its original format.
From the Automate plan, sandbox keys included. On the free plan the endpoint answers with 403; a watermarked sample report is available without a key at GET /v1/report/sample?lang=de|en|fr.

6. Resource: validation_rules

Returns the server's current rule set: supported formats, profiles and the validation engine in use.

URI: validation://rules

{
  "supported_formats": [
    "ZUGFeRD 1.0", "ZUGFeRD 2.0", "ZUGFeRD 2.1", "ZUGFeRD 2.2",
    "ZUGFeRD 2.3", "ZUGFeRD 2.x / Factur-X 1.0x",
    "XRechnung 1.2", "XRechnung 2.0", "XRechnung 2.3", "XRechnung 3.0"
  ],
  "supported_profiles": [
    "minimum", "basic_wl", "basic", "en16931", "extended", "xrechnung"
  ],
  "validation_engines": [
    "Mustang Project v2.26.0",
    "KoSIT Validierungstool v1.6.3 (XRechnung 3.0.2 + EN 16931 CII)"
  ],
  "last_updated":      "2026-04-08"
}

7. Error Handling

On failure, all tools return a JSON object with an error field. Via MCP there is one exception: if the input does not match the tool's schema (wrong type, missing required field, value not in the list), the MCP protocol itself reports it as a tool error (isError: true) with the text of the schema check, not as a JSON object.

{
  "error":   "Short error category",
  "details": "Detailed description with a concrete hint on how to fix it."
}
errorTrigger
"Fehlender Parameter"Required field not provided
"Ungültiger Parameter"Wrong value for file_type
"Ungültiger Base64-Input"No valid Base64 string provided
"Validierungsfehler"Mustang CLI could not start (e.g. Java missing) or timed out after 30s
"XML-Extraktion fehlgeschlagen"No XML file found in the PDF attachment (for extract / consistency). For validate, a notice with rule_id: "NO_EMBEDDED_XML" is returned instead and valid: true is set
"PDF-Lesefehler"The PDF cannot be read
"XML-Parsing-Fehler"The extracted XML is not valid XML
"Interner Fehler"Unexpected runtime error
Timeout: both validation engines run in parallel and are aborted after 30 seconds each. If Mustang fails, "error": "Validierungsfehler" is returned. A KoSIT timeout is silently ignored — Mustang results are kept.

8. Common Data Types

ValidationIssue

Used in the errors, warnings and notices arrays of validate_invoice.

FieldTypeRequiredDescription
severity"error" | "warning" | "notice"YesSeverity of the finding
rule_idstringYesRule ID, e.g. "BR-DE-1", "BR-CL-10". Value "UNKNOWN" for XSD schema violations (KoSIT) or when no rule ID can be determined
fieldstringNoAffected BT field, e.g. "BT-10"
messagestringYesError message in German
fix_suggestionstringNoConcrete fix hint with an XML example
xpathstringNoXPath to the affected XML element
scope"xrechnung" | "peppol"NoOnly on notices of a recognised invoice that is not an XRechnung (BT-24 without the identifier xrechnung), such as ZUGFeRD or Factur-X. "xrechnung" for BR-DE-… rules: they only apply if your recipient requires an XRechnung, for example a public authority. "peppol" for PEPPOL-… rules: they only apply when sending through the Peppol network. Absent on errors, warnings and all other notices.

InvoiceMetadata

Optional field in the result of validate_invoice (only for file_type: "xml").

FieldTypeDescription
invoice_numberstring?Invoice number (BT-1)
issue_datestring?Invoice date, ISO 8601, e.g. "2026-04-01"
sellerstring?Seller name (BT-27)
buyerstring?Buyer name (BT-44)
total_amountstring?Total amount as a decimal string, e.g. "1190.00"
currencystring?ISO 4217 currency code, e.g. "EUR"

9. Supported Formats & Profiles

Formats

FormatStandardNote
ZUGFeRD 1.0ZUGFeRD 1.0 / GEFEGOlder version, limited profile support
ZUGFeRD 2.0FeRD 2.0First CII-based version
ZUGFeRD 2.1Factur-X 1.0 (FR)Technically identical to Factur-X 1.0
ZUGFeRD 2.2Factur-X 1.0.05Minor corrections
ZUGFeRD 2.3Factur-X 1.0.06Version before 2.4
ZUGFeRD 2.x / Factur-X 1.0xEN 16931 + D16BCurrent main version (2026) — the API returns "ZUGFeRD 2.x"
XRechnung 1.2XRechnung 1.2Older XRechnung version
XRechnung 2.xXRechnung 2.0–2.3Various intermediate versions
XRechnung 3.0XRechnung 3.0Current XRechnung version

Profiles (ZUGFeRD/Factur-X)

Profile IDDisplay nameTypical use
minimumMINIMUMSimple machine processing
basic_wlBASIC-WLWithout line-item detail
basicBASICStandard invoices
en16931EN 16931 (COMFORT)Public authorities, PEPPOL
extendedEXTENDEDComplex B2B processes
xrechnungXRECHNUNGGerman public administration

10. Rule Set & Fix Suggestions

The server contains a database of fix suggestions for 160 rule categories. Errors from both validation engines (Mustang + KoSIT) are consolidated — when a known rule ID is found, the fix suggestion is attached automatically.

rule_id names the rule in upper case and without leading zeros: BR-6 instead of BR-06, BR-DE-25-B instead of BR-DE-25-b. Where XRechnung splits a rule in two, the full identifier is given. Field identifiers such as BT-89 are never a rule ID.

BR-DE rules 32 rules

German extensions, mandatory for XRechnung-compliant invoices. As of XRechnung 3.0.2; warnings and notices do not make the invoice invalid.

Rule IDFieldShort description
BR-DE-1BG-16PAYMENT INSTRUCTIONS (BG-16) missing
BR-DE-2BG-6SELLER CONTACT (BG-6)
BR-DE-3BT-37Seller city (BT-37)
BR-DE-4BT-38Seller post code (BT-38)
BR-DE-5BT-41Seller contact point (BT-41)
BR-DE-6BT-42Seller phone number (BT-42)
BR-DE-7BT-43Seller email (BT-43)
BR-DE-8BT-52Buyer city (BT-52)
BR-DE-9BT-53Buyer post code (BT-53)
BR-DE-10BT-77Deliver-to city (BT-77)
BR-DE-11BT-78Deliver-to post code (BT-78)
BR-DE-12–Removed in the current XRechnung release
BR-DE-13–Removed in the current XRechnung release
BR-DE-14BT-119VAT rate (BT-119)
BR-DE-15BT-10Buyer reference / routing ID (BT-10)
BR-DE-16BT-31Seller VAT ID / tax number
BR-DE-17BT-3Invoice type that XRechnung does not recommend, such as 386 (warning)
BR-DE-18BT-20Payment discount terms (BT-20) in the wrong format
BR-DE-19BT-84Payee IBAN not valid for credit transfer (58) (warning)
BR-DE-20BT-91Debtor IBAN not valid for direct debit (59) (warning)
BR-DE-21BT-24Specification identifier (BT-24) not XRechnung-compliant (warning)
BR-DE-22BT-125Attachment file names not unique
BR-DE-23-ABG-17Credit transfer (30, 58) without account details
BR-DE-23-BBT-81Credit transfer (30, 58) with card or direct debit
BR-DE-24-ABG-18Card payment (48, 54, 55) without card details
BR-DE-24-BBT-81Card payment with account or direct debit
BR-DE-25-ABG-19Direct debit (59) without direct debit details
BR-DE-25-BBT-81Direct debit (59) with payee account or card
BR-DE-26BT-3Corrected invoice (384) without a reference to the earlier invoice (warning)
BR-DE-27BT-42Phone number with fewer than three digits (warning)
BR-DE-28BT-43Email address in the wrong form (warning)
BR-DE-30BT-90Direct debit without creditor ID
BR-DE-31BT-91Direct debit without debtor IBAN
BR-DE-TMP-32BT-72No delivery or service date (notice)

EN 16931 core rules (BR-*) 10 rules

Rule IDFieldShort description
BR-1BT-24Specification identifier (BT-24)
BR-2BT-1Invoice number (BT-1)
BR-3BT-2Invoice issue date (BT-2)
BR-4BT-3Invoice type code (BT-3)
BR-5BT-5Currency code (BT-5)
BR-6BT-27Seller name (BT-27)
BR-7BT-44Buyer name (BT-44)
BR-8BG-5Seller postal address (BG-5)
BR-9BG-5Seller country code (BT-40)
BR-10BG-8Buyer postal address (BG-8)

Calculation rules (BR-CO-*) & code-list rules (BR-CL-*) 8 rules

Rule IDShort description
BR-CO-3Tax point date and tax point date code are mutually exclusive
BR-CO-9Seller VAT ID without a country prefix
BR-CO-10Sum of net line amounts does not match
BR-CO-13Total amount excluding VAT calculated incorrectly
BR-CL-10Invalid schemeID (not from ISO 6523)
BR-CL-16Invalid payment means code (not from UNTDID 4461)
BR-CL-17Invalid VAT category in an allowance or charge at document level (in UBL also in the VAT breakdown)
BR-CL-18Invalid VAT category at line level (in CII also in the VAT breakdown)

Tax category rules (BR-S/Z/E/AE-*) 4 rules

Rule IDShort description
BR-S-1Standard rate (S) without a VAT breakdown of category S
BR-Z-1Zero rate (Z): exactly one VAT breakdown of category Z
BR-E-1Exempt (E): exactly one VAT breakdown of category E, so one reason for 0 % per invoice
BR-AE-1Reverse charge (AE): exactly one VAT breakdown of category AE

11. Usage Examples

Example 1: Validate an XRechnung XML

{
  "tool": "validate_invoice",
  "arguments": {
    "file_content": "PD94bWwgdmVyc2lvbj0iMS4wIj8+...",
    "file_type":   "xml",
    "profile":     "xrechnung"
  }
}
{
  "valid":   true,
  "format":  "XRechnung 3.0",
  "profile": "XRECHNUNG",
  "summary": "No issues found",
  "errors":  [],
  "warnings": [],
  "metadata": {
    "invoice_number": "INV-2026-0042",
    "issue_date":     "2026-04-07",
    "seller":         "Tech GmbH",
    "buyer":          "Bundesbehörde X",
    "total_amount":   "5950.00",
    "currency":       "EUR"
  }
}

Example 2: Faulty invoice with a fix suggestion

{
  "tool": "validate_invoice",
  "arguments": {
    "file_content": "JVBERi0xLjQK...",
    "file_type":   "pdf"
  }
}
{
  "valid":   false,
  "format":  "ZUGFeRD 2.x",
  "profile": "EN16931",
  "summary": "1 error found",
  "errors": [{
    "severity":       "error",
    "rule_id":        "BR-DE-15",
    "field":          "BT-10",
    "message":        "The buyer reference (BT-10) must be provided for XRechnung.",
    "fix_suggestion": "Add 'ram:BuyerReference', e.g. '04011000-1234-34' for public authorities.",
    "xpath":          "//ram:ApplicableHeaderTradeAgreement/ram:BuyerReference"
  }],
  "warnings": []
}

Example 3: Extract and validate XML

// Step 1 — extract the XML from the PDF
{ "tool": "extract_xml", "arguments": { "pdf_content": "JVBERi0x..." } }

// Step 2 — validate the extracted XML (passed Base64-encoded)
{ "tool": "validate_invoice", "arguments": { "file_content": "<base64 of xml_content>", "file_type": "xml" } }

Example 4: Consistency check

{
  "tool": "check_consistency",
  "arguments": { "pdf_content": "JVBERi0xLjQK..." }
}
{
  "consistent":    false,
  "discrepancies": [{
    "field":     "total_amount",
    "pdf_value": "1190.00",
    "xml_value": "1180.00",
    "message":   "The total amount in the PDF ('1190.00') differs from the one in the XML ('1180.00')."
  }]
}

Example 5: Plain PDF without embedded XML

A regular PDF invoice without a ZUGFeRD/Factur-X attachment returns valid: true with an informational notice — not an error, but a hint that no structured data could be checked.

{
  "tool": "validate_invoice",
  "arguments": {
    "file_content": "JVBERi0xLjQK...",
    "file_type":   "pdf"
  }
}
{
  "valid":    true,
  "format":   null,
  "profile":  null,
  "summary":  "2 notices found",
  "errors":   [],
  "warnings": [],
  "notices": [
    {
      "severity": "notice",
      "rule_id":  "UNKNOWN",
      "message":  "XML could not be extracted"
    },
    {
      "severity": "notice",
      "rule_id":  "NO_EMBEDDED_XML",
      "message":  "Kein eingebettetes XML gefunden. Die PDF ist möglicherweise keine ZUGFeRD/Factur-X Rechnung."
    }
  ]
}

12. Limits & Known Constraints

ConstraintDetails
TimeoutMustang CLI is aborted server-side after 30 seconds. Very large or corrupt files can trigger this timeout.
Rate limitValidation: Check 20/week · Automate 500/week · Scale unlimited. Creation (separate quota): Check 1 a month · Automate and Scale unlimited, fair use via the hourly limit. Sandbox creation: 100/day, max. 3/minute. On exceeding the limit: HTTP 429.
ThroughputAutomate runs at full speed up to 60 calls per hour, Scale up to 300. Above that there is no block: further calls run in a slow lane and only get slots that are free at the moment. If the server is full, it answers after waiting up to 20 seconds with HTTP 503, Retry-After: 15 and slow_lane: true. Your program then simply sends the invoice again. The weekly quota still applies.
PDF OCRcheck_consistency only evaluates machine-readable PDF text. Scanned PDFs without an OCR layer are not supported.
Metadata for PDFsvalidate_invoice also extracts metadata for PDFs, provided an embedded XML is present and readable. For plain PDFs without XML, this field is missing.
Consistency checkOnly three fields are compared (number, date, amount). Addresses, tax numbers and line-item details are not reconciled.
Unknown rule IDsIf a rule ID is missing from the fix-suggestions DB, fix_suggestion stays empty. The message is still returned.
File sizeHosted API: a request may be at most 50 MB as JSON, otherwise you get 413. Base64 makes files a third larger, so about 35 MB of files fit into one request. In the batch check (/v1/stapel), each file may be at most 15 MB. The self-hosted MCP server has no fixed limit, but Mustang stops after 30 seconds; very large PDFs may not make it.
AttachmentsWhen creating an invoice, at most 10 attachments, each and all together at most 5 MB, measured after decoding. From two attachments on, the check warns with CII-SR-476; the invoice stays valid.

13. Changelog

November 2026: When a notice applies

  • New: on an invoice that is not an XRechnung, notices for BR-DE-… and PEPPOL-… rules carry the field scope ("xrechnung" or "peppol"). It says when the notice applies. The PDF report and the validator on the website show it below the message. All other fields stay as they are.

October 2026: Tax number, direct debit and attachments

  • New in create_invoice / POST /v1/create: German tax number instead of a VAT ID (seller.tax_number), disbursements without VAT (lines[].pass_through), amounts already paid (prepaid_amount_cents, prepaid_vat_cents), references to earlier invoices (preceding_invoices), SEPA direct debit (direct_debit) and attachments (attachments).
  • New: the sender profile (/v1/keys/issuer) holds the creditor ID (creditor_id). PUT /v1/keys/issuer changes only the fields in the request; an empty string or null clears a field. It rejects a VAT ID without country code and a tax number in the wrong form with 400.
  • Changed: with a service period and no delivery date, the delivery date (BT-72) is now the end of the period, no longer the invoice date. On a prepayment invoice without delivery date and period, BT-72 is left out; the ZUGFeRD check then warns (PEPPOL-EN16931-R008).
  • Changed: input that used to produce an invalid file (neither VAT ID nor tax number, VAT ID without country code, period ending before it starts, direct debit in XRechnung without mandate) now gets a 400.
  • Changed: the PDF of a credit note (credit_note) no longer shows an amount due but says that the amount will be credited.
  • Changed: via MCP, the response for a ZUGFeRD invoice with attachments contains only pdf_base64; the XML is inside the PDF.
  • Changed: rule IDs corrected: no more fake IDs (BT-89, TMP-32, EU-79), KoSIT rules with a letter keep their full name (BR-DE-25-B instead of BR-DE-25), duplicate messages are gone. The fix suggestion for BR-DE-20 (debtor IBAN) no longer says the rule is gone; CII-SR-475 and CII-SR-476 with several attachments are explained as harmless.

October 2026: Format detection

  • format and profile now come from the specification identifier (the guideline field in CII, CustomizationID in UBL), no longer from the first URN in the document. Before, many XRechnung and EN 16931 invoices came back without a format.
  • New fields container (pdf: PDF with embedded e-invoice, xml: XML file) and syntax (CII, UBL).
  • Labels: ZUGFeRD 2.x and ZUGFeRD 1.0 for PDF, otherwise XRechnung 3.0, Peppol BIS Billing 3.0 or EN 16931. New profiles XRECHNUNG and PEPPOL; EXTENDED is no longer reported as EN16931.

June 2026 — Invoice creation

  • New tool create_invoice / POST /v1/create: build valid e-invoices from structured data — as a ZUGFeRD PDF (PDF/A-3) or plain XRechnung XML (with routing ID for B2G).
  • Multiple line items, all VAT categories (S/Z/E/AE), allowances/charges, payment details, due date.
  • Document types: invoice, credit note/cancellation, correction, self-billing, down-payment and partial invoice — with a reference to the original invoice.
  • Tier-dependent branding (footer on the Check tier / white-label from the Automate tier with an optional logo); separate weekly quota, kept apart from validation.
  • Sandbox/test keys (zv_test_…): free integration with a "DEMO" watermark, without using up the live quota.

April 2026 — Initial release

  • Validation against EN 16931 (validate_invoice) for ZUGFeRD/Factur-X PDF and XRechnung XML, with concrete fix suggestions.
  • XML extraction (extract_xml) and PDF↔XML consistency check (check_consistency).
  • MCP client for Claude & other AI agents; Mustang and KoSIT engines.