ZUGFeRD & XRechnung
Validation API (REST + MCP)
MCP server for automated validation of e-invoices in the ZUGFeRD, Factur-X and XRechnung formats. Returns structured JSON results with concrete fix suggestions.
1. Overview
The server provides three tools and one resource that AI agents can call through the Model Context Protocol.
| Scenario | Tool / Resource |
|---|---|
| Check an invoice before sending | validate_invoice |
| Read XML data from a ZUGFeRD PDF | extract_xml |
| Check whether PDF and XML match | check_consistency |
| Query available formats and rule sets | validation://rules |
2. Architecture & Data Flow
Flow for validate_invoice
3. Validation Engines
Every request is checked by two engines at once. The results are consolidated, deduplicated and returned.
| Engine | Version | Covered 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) |
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
# Register for free:
https://zugferd-validator.de/portal
npm install -g zugferd-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)
| Variable | Description | Default |
|---|---|---|
ZUGFERD_API_KEY | API key from the portal | – (required) |
ZUGFERD_API_URL | API base URL | https://api.zugferd-validator.de |
5. Tools
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
data:application/pdf;base64,…) are removed automatically.
"pdf" for ZUGFeRD/Factur-X PDFs · "xml" for plain XRechnung/CII XML files
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
| Field | Type | Required | Description |
|---|---|---|---|
valid | boolean | Yes | true when not a single error was found |
format | string | No | Detected 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 |
profile | string | No | Detected profile in uppercase, e.g. "EN16931", "EXTENDED" or "XRECHNUNG"; null when no XML can be extracted |
container | string | No | pdf (PDF with embedded e-invoice) or xml (XML file) |
syntax | string | No | CII or UBL |
summary | string | Yes | Human-readable summary, e.g. "2 errors, 1 warning found" |
errors | array | Yes | Validation errors — these make the invoice invalid |
warnings | array | Yes | Warnings — the invoice can still be valid |
notices | array | No | Informational notices with no impact on validity |
metadata | object | No | Extracted invoice data — present for PDFs when the embedded XML was read successfully, and always for file_type: "xml" |
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
Output
{
"xml_content": "<?xml version='1.0' encoding='UTF-8'?>\n<rsm:CrossIndustryInvoice ...>...</rsm:CrossIndustryInvoice>",
"format": "ZUGFeRD 2.x",
"profile": "EN16931"
}
xml_content can be passed directly (after Base64 encoding) to validate_invoice with file_type: "xml".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
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
| Field | Detection pattern in the PDF | Tolerance |
|---|---|---|
invoice_number | Rechnungsnummer:, Rechnung Nr., Invoice No. | Case, spaces and hyphens ignored |
issue_date | Rechnungsdatum:, Datum:, Invoice Date: | None — date formats are normalized to ISO 8601 |
total_amount | Gesamtbetrag:, Rechnungsbetrag:, Total: | ±0.01 EUR (rounding differences) |
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)
zugferd (PDF) or xrechnung (XML)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.[{ 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.{ number, date } (BT-25/26), for example on a credit note. Not together with preceding_invoices.seller is missing, we use the sender profile of your account.vat_category: default S; Z = 0 %, E = exempt, AE = reverse charge, K = intra-community supply, G = export.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.{ 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_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.xrechnungmandate_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.[{ 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.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.
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.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.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
413)"pdf" or "xml"de, en or fr, default detrue (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.
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
de, en or fr, default deResponse (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.
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
{ sprache?: "de" | "en" | "fr" } (language of the email). Response 201 with { id, max_dateien, max_bytes }. At most 3 open batches per key.{ name, file_type: "pdf" | "xml", file_content } (Base64, at most 15 MB). At most 500 files per batch.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).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.
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
pdf or xmlAFRelationship /Source) so it stays machine readable. Default false.de, en, fr — default derechnung.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
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."
}
| error | Trigger |
|---|---|
"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 |
"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.
| Field | Type | Required | Description |
|---|---|---|---|
severity | "error" | "warning" | "notice" | Yes | Severity of the finding |
rule_id | string | Yes | Rule ID, e.g. "BR-DE-1", "BR-CL-10". Value "UNKNOWN" for XSD schema violations (KoSIT) or when no rule ID can be determined |
field | string | No | Affected BT field, e.g. "BT-10" |
message | string | Yes | Error message in German |
fix_suggestion | string | No | Concrete fix hint with an XML example |
xpath | string | No | XPath to the affected XML element |
scope | "xrechnung" | "peppol" | No | Only 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").
| Field | Type | Description |
|---|---|---|
invoice_number | string? | Invoice number (BT-1) |
issue_date | string? | Invoice date, ISO 8601, e.g. "2026-04-01" |
seller | string? | Seller name (BT-27) |
buyer | string? | Buyer name (BT-44) |
total_amount | string? | Total amount as a decimal string, e.g. "1190.00" |
currency | string? | ISO 4217 currency code, e.g. "EUR" |
9. Supported Formats & Profiles
Formats
| Format | Standard | Note |
|---|---|---|
| ZUGFeRD 1.0 | ZUGFeRD 1.0 / GEFEG | Older version, limited profile support |
| ZUGFeRD 2.0 | FeRD 2.0 | First CII-based version |
| ZUGFeRD 2.1 | Factur-X 1.0 (FR) | Technically identical to Factur-X 1.0 |
| ZUGFeRD 2.2 | Factur-X 1.0.05 | Minor corrections |
| ZUGFeRD 2.3 | Factur-X 1.0.06 | Version before 2.4 |
| ZUGFeRD 2.x / Factur-X 1.0x | EN 16931 + D16B | Current main version (2026) — the API returns "ZUGFeRD 2.x" |
| XRechnung 1.2 | XRechnung 1.2 | Older XRechnung version |
| XRechnung 2.x | XRechnung 2.0–2.3 | Various intermediate versions |
| XRechnung 3.0 | XRechnung 3.0 | Current XRechnung version |
Profiles (ZUGFeRD/Factur-X)
| Profile ID | Display name | Typical use |
|---|---|---|
minimum | MINIMUM | Simple machine processing |
basic_wl | BASIC-WL | Without line-item detail |
basic | BASIC | Standard invoices |
en16931 | EN 16931 (COMFORT) | Public authorities, PEPPOL |
extended | EXTENDED | Complex B2B processes |
xrechnung | XRECHNUNG | German 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 ID | Field | Short description |
|---|---|---|
BR-DE-1 | BG-16 | PAYMENT INSTRUCTIONS (BG-16) missing |
BR-DE-2 | BG-6 | SELLER CONTACT (BG-6) |
BR-DE-3 | BT-37 | Seller city (BT-37) |
BR-DE-4 | BT-38 | Seller post code (BT-38) |
BR-DE-5 | BT-41 | Seller contact point (BT-41) |
BR-DE-6 | BT-42 | Seller phone number (BT-42) |
BR-DE-7 | BT-43 | Seller email (BT-43) |
BR-DE-8 | BT-52 | Buyer city (BT-52) |
BR-DE-9 | BT-53 | Buyer post code (BT-53) |
BR-DE-10 | BT-77 | Deliver-to city (BT-77) |
BR-DE-11 | BT-78 | Deliver-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-14 | BT-119 | VAT rate (BT-119) |
BR-DE-15 | BT-10 | Buyer reference / routing ID (BT-10) |
BR-DE-16 | BT-31 | Seller VAT ID / tax number |
BR-DE-17 | BT-3 | Invoice type that XRechnung does not recommend, such as 386 (warning) |
BR-DE-18 | BT-20 | Payment discount terms (BT-20) in the wrong format |
BR-DE-19 | BT-84 | Payee IBAN not valid for credit transfer (58) (warning) |
BR-DE-20 | BT-91 | Debtor IBAN not valid for direct debit (59) (warning) |
BR-DE-21 | BT-24 | Specification identifier (BT-24) not XRechnung-compliant (warning) |
BR-DE-22 | BT-125 | Attachment file names not unique |
BR-DE-23-A | BG-17 | Credit transfer (30, 58) without account details |
BR-DE-23-B | BT-81 | Credit transfer (30, 58) with card or direct debit |
BR-DE-24-A | BG-18 | Card payment (48, 54, 55) without card details |
BR-DE-24-B | BT-81 | Card payment with account or direct debit |
BR-DE-25-A | BG-19 | Direct debit (59) without direct debit details |
BR-DE-25-B | BT-81 | Direct debit (59) with payee account or card |
BR-DE-26 | BT-3 | Corrected invoice (384) without a reference to the earlier invoice (warning) |
BR-DE-27 | BT-42 | Phone number with fewer than three digits (warning) |
BR-DE-28 | BT-43 | Email address in the wrong form (warning) |
BR-DE-30 | BT-90 | Direct debit without creditor ID |
BR-DE-31 | BT-91 | Direct debit without debtor IBAN |
BR-DE-TMP-32 | BT-72 | No delivery or service date (notice) |
EN 16931 core rules (BR-*) 10 rules
| Rule ID | Field | Short description |
|---|---|---|
BR-1 | BT-24 | Specification identifier (BT-24) |
BR-2 | BT-1 | Invoice number (BT-1) |
BR-3 | BT-2 | Invoice issue date (BT-2) |
BR-4 | BT-3 | Invoice type code (BT-3) |
BR-5 | BT-5 | Currency code (BT-5) |
BR-6 | BT-27 | Seller name (BT-27) |
BR-7 | BT-44 | Buyer name (BT-44) |
BR-8 | BG-5 | Seller postal address (BG-5) |
BR-9 | BG-5 | Seller country code (BT-40) |
BR-10 | BG-8 | Buyer postal address (BG-8) |
Calculation rules (BR-CO-*) & code-list rules (BR-CL-*) 8 rules
| Rule ID | Short description |
|---|---|
BR-CO-3 | Tax point date and tax point date code are mutually exclusive |
BR-CO-9 | Seller VAT ID without a country prefix |
BR-CO-10 | Sum of net line amounts does not match |
BR-CO-13 | Total amount excluding VAT calculated incorrectly |
BR-CL-10 | Invalid schemeID (not from ISO 6523) |
BR-CL-16 | Invalid payment means code (not from UNTDID 4461) |
BR-CL-17 | Invalid VAT category in an allowance or charge at document level (in UBL also in the VAT breakdown) |
BR-CL-18 | Invalid VAT category at line level (in CII also in the VAT breakdown) |
Tax category rules (BR-S/Z/E/AE-*) 4 rules
| Rule ID | Short description |
|---|---|
BR-S-1 | Standard rate (S) without a VAT breakdown of category S |
BR-Z-1 | Zero rate (Z): exactly one VAT breakdown of category Z |
BR-E-1 | Exempt (E): exactly one VAT breakdown of category E, so one reason for 0 % per invoice |
BR-AE-1 | Reverse 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
| Constraint | Details |
|---|---|
| Timeout | Mustang CLI is aborted server-side after 30 seconds. Very large or corrupt files can trigger this timeout. |
| Rate limit | Validation: 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. |
| Throughput | Automate 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 OCR | check_consistency only evaluates machine-readable PDF text. Scanned PDFs without an OCR layer are not supported. |
| Metadata for PDFs | validate_invoice also extracts metadata for PDFs, provided an embedded XML is present and readable. For plain PDFs without XML, this field is missing. |
| Consistency check | Only three fields are compared (number, date, amount). Addresses, tax numbers and line-item details are not reconciled. |
| Unknown rule IDs | If a rule ID is missing from the fix-suggestions DB, fix_suggestion stays empty. The message is still returned. |
| File size | Hosted 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. |
| Attachments | When 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-…andPEPPOL-…rules carry the fieldscope("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/issuerchanges only the fields in the request; an empty string ornullclears a field. It rejects a VAT ID without country code and a tax number in the wrong form with400. - 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
formatandprofilenow come from the specification identifier (the guideline field in CII,CustomizationIDin 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) andsyntax(CII,UBL). - Labels:
ZUGFeRD 2.xandZUGFeRD 1.0for PDF, otherwiseXRechnung 3.0,Peppol BIS Billing 3.0orEN 16931. New profilesXRECHNUNGandPEPPOL;EXTENDEDis no longer reported asEN16931.
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.