# Validate E-Invoice

Checks an e-invoice file against the official rule sets and lists every error and warning with its rule id: XML well-formedness, the XML schema, the EN 16931 business rules and the national XRechnung rules. For a ZUGFeRD / Factur-X PDF it also checks PDF/A-3 conformance and the embedded invoice file. Format, syntax, profile and version are detected from the file.

Technical validation, not tax advice

The result says whether the file conforms to the rule sets named in ruleset_version. It is not tax or legal advice, and it does not check whether the invoice's content is right for the underlying transaction.

Request Method: POST
Request URL: https://api.vatcheckapi.com/v2/einvoice/validate

# Sending the Invoice

Send one invoice file, XML or PDF, at most 10 MB, in one of three ways:

Way Content-Type Body
Raw body application/xml (or text/xml) for an XML file, application/pdf for a PDF The file itself
File upload multipart/form-data The file in the field file
JSON application/json {"content_base64": "...", "filename": "invoice.pdf"}: the whole file, base64-encoded. filename is optional. A data URL (data:application/pdf;base64,...) is accepted too

The MCP server (opens new window) uses the JSON way. Whether the file is XML or a PDF is detected from its content, not from Content-Type or the file name.

# Request Parameters

Parameter Type Mandatory Description
apikey string ️ Your API Key
format string The format you expect: xrechnung, zugferd or en16931. The invoice is always validated against the rules its own specification identifier (BT-24) names; if it declares another format, the response gets the error FORMAT_MISMATCH. XRechnung and ZUGFeRD / Factur-X invoices also count as en16931, and an EN 16931 invoice in CII syntax also counts as zugferd. Default auto: no check
language string en (default) or de: the language of the API's own messages and of the disclaimer. Rule set messages are not translated, see Message language

Send format and language in the query string. With a file upload they may also be form fields, with JSON also properties of the JSON object.

# Sample Requests

# cURL: XML file as the request body

curl "https://api.vatcheckapi.com/v2/einvoice/validate" \
    -H "apikey: YOUR-APIKEY" \
    -H "Content-Type: application/xml" \
    --data-binary @invoice.xml

# cURL: PDF as the request body, German messages

curl "https://api.vatcheckapi.com/v2/einvoice/validate?language=de" \
    -H "apikey: YOUR-APIKEY" \
    -H "Content-Type: application/pdf" \
    --data-binary @invoice.pdf

# cURL: file upload

curl "https://api.vatcheckapi.com/v2/einvoice/validate" \
    -H "apikey: YOUR-APIKEY" \
    -F "file=@invoice.pdf" \
    -F "format=zugferd"

# JavaScript: base64 in JSON

import { readFile } from 'node:fs/promises';

const file = await readFile('invoice.pdf');

const response = await fetch('https://api.vatcheckapi.com/v2/einvoice/validate?language=de', {
    method: 'POST',
    headers: { 'apikey': 'YOUR-APIKEY', 'Content-Type': 'application/json' },
    body: JSON.stringify({ content_base64: file.toString('base64'), filename: 'invoice.pdf' }),
});

const result = await response.json();
console.log(result.valid, result.errors);

# Python: base64 in JSON

import base64
import requests

with open("invoice.xml", "rb") as f:
    content = base64.b64encode(f.read()).decode("ascii")

resp = requests.post(
    "https://api.vatcheckapi.com/v2/einvoice/validate",
    params={"language": "de"},
    headers={"apikey": "YOUR-APIKEY"},
    json={"content_base64": content, "filename": "invoice.xml"},
)

result = resp.json()
print(result["valid"])
for error in result["errors"]:
    print(error["rule_id"], error["message"])

# PHP: XML file as the request body

$curl = curl_init("https://api.vatcheckapi.com/v2/einvoice/validate");

curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ["apikey: YOUR-APIKEY", "Content-Type: application/xml"],
    CURLOPT_POSTFIELDS => file_get_contents("invoice.xml"),
    CURLOPT_RETURNTRANSFER => true,
]);

$result = json_decode(curl_exec($curl), true);
var_dump($result["valid"], $result["errors"]);

# Sample Response

An XRechnung 3.0 invoice in UBL syntax without the buyer reference (BT-10). An invalid invoice is a normal result: the status is 200 and valid is false.

{
    "valid": false,
    "format": "xrechnung",
    "syntax": "UBL",
    "profile": "XRECHNUNG",
    "version": "3.0",
    "container": "xml",
    "specification_identifier": "urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0",
    "scenario": "EN16931 XRechnung (UBL Invoice)",
    "ruleset_version": "xrechnung-3.0.2+cfg-2026-08-31;en16931-1.3.16;xrechnung-schematron-2.6.0;kosit-1.6.3;verapdf-1.30.2",
    "pdfa": null,
    "errors": [
        {
            "rule_id": "BR-DE-15",
            "severity": "error",
            "message": "Das Element \"Buyer reference\" (BT-10) muss übermittelt werden.",
            "message_language": "de",
            "source": "business_rules",
            "location": "/ubl:Invoice[1]",
            "line": null,
            "column": null
        }
    ],
    "warnings": [
        {
            "rule_id": "BR-DE-TMP-32",
            "severity": "information",
            "message": "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.",
            "message_language": "de",
            "source": "business_rules",
            "location": "/ubl:Invoice[1]",
            "line": null,
            "column": null
        }
    ],
    "summary": {
        "invoice_number": "123456XX",
        "issue_date": "2016-04-04",
        "type_code": "380",
        "currency": "EUR",
        "seller": { "name": "[Seller name]", "vat_id": "DE 123456789" },
        "buyer": { "name": "[Buyer name]" },
        "totals": { "net_amount": "314.86", "tax_amount": "22.04", "gross_amount": "336.9", "payable_amount": "336.9" }
    },
    "language": "en",
    "disclaimer": "Technical validation against the rule sets listed in ruleset_version. It is not tax or legal advice."
}

A PDF without an embedded invoice XML gets "container": "pdf", "pdfa": {"compliant": null, "profile": null} and the error NO_EINVOICE_XML ("No e-invoice XML found"). A ZUGFeRD / Factur-X PDF that passes gets "valid": true, "format": "zugferd" and "pdfa": {"compliant": true, "profile": "PDF/A-3B"}.

# Response Fields

Every field is always present; fields that do not apply are null.

Field Description
valid true when the rule sets accepted the invoice and no check reported an error; for a PDF, PDF/A conformance is required too. Warnings and information messages do not make an invoice invalid
format xrechnung, zugferd (ZUGFeRD and Factur-X, including a PDF whose invoice has the EN 16931 or XRECHNUNG profile), en16931 (plain EN 16931 or another EN 16931 specification), peppol-bis, or null when the file is not an e-invoice the API recognises
syntax UBL or CII
profile XRECHNUNG, XRECHNUNG-EXTENSION, XRECHNUNG-CVD, EN 16931, MINIMUM, BASIC WL, BASIC, EXTENDED, EXTENDED-CTC-FR, BILLING (Peppol BIS), or BASIC / COMFORT / EXTENDED for ZUGFeRD 1.0
version The version the invoice declares, e.g. 3.0 for XRechnung 3.0 or 2017 for EN 16931
container xml or pdf: what you sent
specification_identifier The specification identifier (BT-24) exactly as the invoice declares it
scenario The validation scenario (rule set) that was applied; null when no rule set ran
ruleset_version The pinned versions of the rule sets and engines that ran; null when no rule set ran, sandbox for sandbox keys
pdfa null for an XML file. For a PDF: compliant (null when PDF/A was not checked) and the PDF/A profile checked against
errors Findings of severity error
warnings Findings of severity warning and information
summary Key facts read from the invoice XML, exactly as written there: invoice_number (BT-1), issue_date (BT-2; CII dates are given as YYYY-MM-DD), type_code (BT-3), currency (BT-5), seller.name, seller.vat_id (BT-31), buyer.name, and totals as strings in the invoice currency: net_amount (BT-109), tax_amount (BT-110), gross_amount (BT-112), payable_amount (BT-115). null when no UBL or CII invoice could be read
language The language of the request
disclaimer The note that this is technical validation, not tax or legal advice

Each finding in errors and warnings has these fields:

Field Description
rule_id The rule id: from a rule set (e.g. BR-DE-15, BR-CO-10, the XML schema's cvc-complex-type.2.4.a, the PDF/A rule PDFA-6.6.2.1-1) or one of the API's own codes
severity error, warning or information
message What is wrong
message_language en or de: the language of message
source Which check reported it: input (the request), xml (well-formedness), schema (XML schema), business_rules (EN 16931 and XRechnung rules), container (the PDF's embedded file and XMP metadata) or pdfa (PDF/A conformance)
location XPath of the element in the invoice XML (e.g. /ubl:Invoice[1]/cac:LegalMonetaryTotal[1]), or the PDF object path for PDF/A findings
line, column Position in the invoice XML, for well-formedness and schema errors

When the XML schema check fails, the business rules are not run; the warning BUSINESS_RULES_SKIPPED says so. Fix the schema errors and validate again.

# Message Language

Rule set messages keep the language their rule set is published in, and message_language says which:

  • the national XRechnung rules (BR-DE-*, BR-DEX-*, BR-TMP-*) are German;
  • the EN 16931 business rules (e.g. BR-CO-10), the XML schema messages and the PDF/A rules are English.

language=de or language=en switches only the API's own messages (for example NO_EINVOICE_XML) and the disclaimer. Rule set messages are not translated.

# Supported Formats

Validated against the rule sets:

Format Syntax Checks
XRechnung 3.0, also the Extension and CVD specifications UBL (Invoice, CreditNote), CII XML, schema, EN 16931 and XRechnung rules
EN 16931 (urn:cen.eu:en16931:2017) UBL (Invoice, CreditNote), CII XML, schema, EN 16931 rules
ZUGFeRD / Factur-X PDF with the profile EN 16931 (COMFORT) or XRECHNUNG CII, embedded in a PDF The above for the embedded XML, plus PDF/A-3 and the embedded file (name, relationship, MIME type, XMP metadata)

Recognised, but answered with the error UNSUPPORTED_PROFILE and not validated, free of charge:

  • ZUGFeRD / Factur-X MINIMUM and BASIC WL (with the warning PROFILE_NOT_EINVOICE_DE: in Germany these profiles do not count as e-invoices), BASIC and EXTENDED, and Factur-X EXTENDED-CTC-FR. Their profile-specific rules are not included yet.
  • ZUGFeRD 1.0.
  • XRechnung versions other than 3.0, and an XRechnung 3.0 Extension credit note in UBL syntax.
  • Peppol BIS Billing 3.0, other EN 16931 specifications and unknown specification identifiers.

An XML file that is not a UBL Invoice, UBL CreditNote or CII invoice gets UNSUPPORTED_FORMAT; an invoice without a specification identifier gets BR-01.

# The API's Own Rule IDs

Besides the rule ids of the rule sets, the API reports these codes:

rule_id Severity Meaning
NOT_WELL_FORMED error The XML cannot be parsed (line and column are given)
DOCTYPE_NOT_ALLOWED error The XML has a document type declaration (DOCTYPE); it is not processed
UNSUPPORTED_FORMAT error The XML is not a UBL Invoice, UBL CreditNote or CII invoice
BR-01 error The invoice has no specification identifier (BT-24), so it does not say which rules apply
UNSUPPORTED_PROFILE error A recognised format, profile or version that is not validated, see Supported Formats
PROFILE_NOT_EINVOICE_DE warning MINIMUM and BASIC WL do not count as e-invoices in Germany
FORMAT_MISMATCH error The invoice declares another format than the format you asked for
BUSINESS_RULES_SKIPPED warning The XML schema check failed, so the business rules were not run
INVOICE_TOO_COMPLEX error More than 50 VAT breakdowns (BG-23) or more than 1,000 document level allowances and charges (BG-20, BG-21); not validated
NO_EINVOICE_XML error The PDF has no embedded ZUGFeRD, Factur-X or XRechnung XML
EMBEDDED_XML_TOO_LARGE error The invoice XML in the PDF is larger than 10 MB; not validated
PDF_UNREADABLE error The PDF is damaged or uses a structure the reader does not support
PDF_ENCRYPTED error The PDF is encrypted, which PDF/A-3 does not allow
PDF_TOO_COMPLEX error Reading the PDF needs more memory or time than the API allows for one file
HYBRID_ATTACHMENT_NAME error The embedded invoice file has a name its profile does not allow (usually it must be factur-x.xml; the message names the expected name)
HYBRID_AF_RELATIONSHIP error The embedded file's AFRelationship is missing or not Data, Source or Alternative
HYBRID_NOT_ASSOCIATED error The embedded file is missing from the document catalog's AF array
HYBRID_MIME_TYPE warning The embedded file's MIME type is not text/xml
XMP_MISSING, XMP_UNREADABLE, XMP_FACTURX_MISSING error The PDF's XMP metadata is missing, broken, or lacks the ZUGFeRD / Factur-X properties
XMP_TOO_LARGE warning The XMP metadata is larger than 2 MB, so its properties were not checked
XMP_DOCUMENT_FILE_NAME, XMP_CONFORMANCE_LEVEL error The XMP metadata names another file or another profile than the embedded XML
PDFA_NOT_DECLARED, PDFA_NOT_PDFA3 error The PDF declares no PDF/A conformance, or not PDF/A-3
PDFA_UNREADABLE error The PDF/A checker could not parse the file

Failed PDF/A rules are reported as PDFA-<clause>-<test>, e.g. PDFA-6.6.2.1-1.

# Costs

Result Requests counted
An XML file that was validated, or that is not well-formed (NOT_WELL_FORMED, DOCTYPE_NOT_ALLOWED), not an e-invoice (UNSUPPORTED_FORMAT) or has no specification identifier (BR-01) 1
A ZUGFeRD / Factur-X PDF whose invoice was validated (XML rules and PDF/A) 2
A PDF without readable invoice XML (NO_EINVOICE_XML, PDF_UNREADABLE, PDF_ENCRYPTED, PDF_TOO_COMPLEX), or whose embedded XML is not well-formed or not an invoice 1
UNSUPPORTED_PROFILE, EMBEDDED_XML_TOO_LARGE, INVOICE_TOO_COMPLEX 0
Any request with a sandbox key 0

The X-Cost response header states the cost of each call. Requests rejected with 422, 403 or 429, and 503 answers, are not counted.

Before any work, the API checks that your remaining quota can pay for the request: 1 request for an XML file, 2 for a PDF, also when the result later costs less. If it cannot, the API answers 429 with quota.required set to that amount, and validates nothing. The error code names the quota that runs short: quota_exceeded, grace_quota_exceeded or overage_quota_exceeded. As for every endpoint, the request that uses the last unit of your quota is still served.

On the free plan the cost also counts against the minute rate limit (opens new window) of 10 requests: a PDF uses 2 of them, a free result none.

# Limits

  • At most 10 MB per file (after base64 decoding for JSON). The limit also applies to the invoice XML inside a PDF (EMBEDDED_XML_TOO_LARGE).
  • Invoices with more than 50 VAT breakdowns or more than 1,000 document level allowances and charges are not validated (INVOICE_TOO_COMPLEX).
  • If the validation service is unavailable, busy or does not finish in time, the API answers 503 (not counted). Retry the request later.

# Sandbox

With a sandbox API key (opens new window) the API reads the file, detects the format, extracts the summary and checks the PDF container as with a live key, but it does not run the rule sets:

  • the rule set result is fixed: the invoice is accepted, unless its invoice number (BT-1) is SANDBOX-INVALID, which gets the error BR-DE-15;
  • a PDF whose invoice XML reaches the rule sets (well-formed, an invoice with a supported profile, within the limits) gets "pdfa": {"compliant": true, "profile": "PDF/A-3B"}; any other PDF gets "compliant": null, as with a live key;
  • scenario is Sandbox and ruleset_version is sandbox;
  • the call costs nothing.

# Privacy

Invoices are processed transiently and never persisted: the API keeps no copy of the file, writes none of its content to logs, and does not store the result. Large uploads can be buffered to temporary files while the request runs; these are deleted when it ends.

# Rule Sets and Licences

The validation runs on these open-source projects, unmodified. ruleset_version in each response names the exact versions used.