Back to blogTechnical Guides

E-Invoice Schematron Rules Explained: What ERP Developers Need to Know

A practical guide to Schematron validation in EU e-invoicing. Understand the three validation layers, read real rule examples, debug common errors, and see how EN16931, Peppol, and country-specific rules work together.

Invoice NavigatorMarch 20, 202612 min read

E-Invoice Schematron Rules Explained: What ERP Developers Need to Know

You're integrating EU e-invoicing into your ERP pipeline. Your XML passes schema validation. Then the validator fires back a wall of cryptic errors — BR-16, PEPPOL-EN16931-R004, BR-DE-1 — and your invoice gets rejected.

Welcome to Schematron. It's the reason your structurally valid invoice is semantically wrong. And if you're building e-invoicing infrastructure, understanding Schematron isn't optional.

This guide explains what Schematron is, why EU e-invoicing relies on it, how the three validation layers work together, and how to read and debug the rules you'll hit most often.

What is Schematron?

Schematron is an ISO standard (ISO/IEC 19757-3) for rule-based XML validation. Unlike XSD (XML Schema Definition), which checks document structure — "is this element present? is this value a date?" — Schematron checks document meaning: relationships between elements, conditional requirements, calculation correctness.

The distinction matters because an e-invoice can be structurally perfect XML and still be completely wrong. Consider: an invoice with a VAT breakdown that doesn't add up to the invoice total. XSD can't catch that. Schematron can.

XSD vs. Schematron: The Difference That Matters

XSD (Schema)Schematron
ChecksStructure, data types, element presenceBusiness logic, calculations, cross-field conditions
Example"IssueDate must be a date""DueDate must not be before IssueDate"
LanguageDeclarative XML schemaXPath assertions in XML
Error outputGeneric parse errorsHuman-readable messages with rule IDs
When it runsFirst (syntax check)Second (semantic check)

In EU e-invoicing, both run in sequence. Your invoice must pass XSD validation before Schematron rules even execute. If you're seeing Schematron errors, your XML structure is already fine — the problem is in your data, your logic, or your field mappings.

The Three Validation Layers

Every EU e-invoice gets validated against three stacked Schematron rule sets. Think of them as filters — each layer adds more specific requirements on top of the previous one.

Layer 1: EN16931 Core Rules (BR-*)

The European standard EN 16931 defines the semantic data model for all EU e-invoices. Its Schematron rules — prefixed with BR- — enforce the absolute minimum: every e-invoice in Europe must pass these.

There are roughly 65 core rules covering:

  • Mandatory fields (BR-01 through BR-08): specification identifier, invoice number, issue date, invoice type code, currency, seller name, buyer name, invoice line
  • Calculation rules (BR-12 through BR-15): line extension amounts, allowance/charge totals, tax-exclusive/inclusive amounts
  • VAT rules (BR-16 through BR-53): tax subtotal consistency, category-specific requirements (standard rate, zero rate, exempt, reverse charge)
  • Co-occurrence rules (BR-CO-*): if field A is present, field B must also be present

Example — BR-16 (Amount due for payment):

Every invoice must have an amount due. In UBL, the Schematron assertion looks like this:

<rule context="/ubl:Invoice">
  <assert id="BR-16"
    test="exists(cac:LegalMonetaryTotal/cbc:PayableAmount)"
    flag="fatal">
    [BR-16] An Invoice shall have the Amount due for payment (BT-115).
  </assert>
</rule>

The XPath expression exists(cac:LegalMonetaryTotal/cbc:PayableAmount) checks that the PayableAmount element exists within LegalMonetaryTotal. If it doesn't, the rule fires and the invoice is rejected with the message shown.

Example — BR-CO-10 (Sum of allowances):

<assert id="BR-CO-10"
  test="(xs:decimal(cac:LegalMonetaryTotal/cbc:AllowanceTotalAmount) =
    round(sum(cac:AllowanceCharge[cbc:ChargeIndicator='false']/cbc:Amount) * 10 * 10) div 100)
    or not(cac:LegalMonetaryTotal/cbc:AllowanceTotalAmount)"
  flag="fatal">
  [BR-CO-10] Sum of allowances on document level (BT-107) =
  Σ Document level allowance amount (BT-92).
</assert>

This rule does math: it sums all allowance amounts on the invoice and checks that they equal the declared total. This is where many ERP integrations break — rounding errors, missing allowances, or inconsistent decimal precision.

Layer 2: Peppol BIS Rules (PEPPOL-EN16931-R*)

If your invoice travels over the Peppol network (increasingly common for B2G and cross-border B2B), it must also pass the Peppol BIS 3.0 Schematron rules. These are prefixed with PEPPOL-EN16931-R and add network-specific constraints on top of EN16931.

There are roughly 80 Peppol-specific rules. They enforce things EN16931 doesn't:

  • Electronic address requirements: buyer and seller must have electronic addresses with valid scheme identifiers
  • Code list restrictions: currency codes, country codes, MIME types, unit of measure codes must come from specific code lists
  • File size and attachment constraints: embedded objects have size and type limits
  • Business process requirements: ProfileID and CustomizationID must match expected values

Example — PEPPOL-EN16931-R003 (Order reference):

<assert id="PEPPOL-EN16931-R003"
  test="exists(cac:OrderReference/cbc:ID)"
  flag="warning">
  [PEPPOL-EN16931-R003] An invoice transaction SHOULD have a Buyer reference
  (BT-10) or an Order reference (BT-13).
</assert>

Note the flag="warning" — this is a soft rule. Some Peppol rules are warnings (won't reject the invoice but signal a problem), while most EN16931 core rules are fatal.

Example — PEPPOL-EN16931-R008 (Document currency):

<assert id="PEPPOL-EN16931-R008"
  test="every $Currency in cbc:DocumentCurrencyCode
    satisfies normalize-space($Currency) != ''"
  flag="fatal">
  [PEPPOL-EN16931-R008] Document MUST contain a document currency code.
</assert>

Layer 3: Country-Specific Rules (BR-CC-*, BR-DE-*, BR-IT-*, etc.)

Each EU member state layers its own CIUS (Core Invoice Usage Specification) rules on top of EN16931. These are the most specific — and often the most frustrating, because they vary by country.

Common country rule sets:

PrefixCountryKey requirements
BR-DE-*Germany (XRechnung)Leitweg-ID for B2G, payment means code, seller contact details
BR-IT-*Italy (SDI)Codice Fiscale, SDI recipient code, Italian tax identifiers
BR-FR-*France (Factur-X)SIRET numbers, French VAT specifics
BR-NL-*NetherlandsKVK number requirements
BR-SE-*SwedenBankgiro/Plusgiro payment details
BR-NO-*NorwayOrganisasjonsnummer
BR-PL-*Poland (KSeF)KSeF-specific FA(3) schema requirements

Example — BR-DE-1 (Buyer reference for German B2G):

<assert id="BR-DE-1"
  test="cac:AccountingSupplierParty/cac:Party/cac:Contact"
  flag="fatal">
  [BR-DE-1] An invoice (Rechnung) MUST contain Seller contact information.
</assert>

In Germany, every XRechnung must include seller contact details — name, phone, and email. Most other countries don't require this at the EN16931 level. If you're building a multi-country pipeline, these are the rules that catch you off guard.

For a deep dive into German-specific rules, see our BR-DE validation error reference.

How Schematron Validation Actually Works

Understanding the execution flow helps you debug faster.

Step 1: Schema Validation (XSD)

Your XML is checked against the syntax-specific schema — UBL 2.1 or CII D16B. This catches structural problems: missing namespaces, wrong element order, invalid data types. If this fails, Schematron never runs.

Step 2: Schematron Compilation

Schematron files (.sch) are compiled into XSLT stylesheets. This is an implementation detail, but it matters: the compilation step is why Schematron validation is slower than XSD. Most validators pre-compile and cache the XSLT.

Step 3: Rule Execution

The compiled XSLT runs against your invoice XML. Each <rule> block defines a context (which elements to check) and one or more assertions (XPath tests that must be true). When an assertion fails, the rule fires.

Step 4: Report Generation (SVRL)

Results come back as SVRL (Schematron Validation Report Language) — another XML document listing every fired rule with its ID, flag (fatal/warning), location (XPath to the offending element), and message.

<svrl:failed-assert test="exists(cac:LegalMonetaryTotal/cbc:PayableAmount)"
  id="BR-16"
  flag="fatal"
  location="/Invoice[1]">
  <svrl:text>[BR-16] An Invoice shall have the Amount due for payment (BT-115).</svrl:text>
</svrl:failed-assert>

This SVRL output is what validators like Invoice Navigator's validator parse, classify, and present to you. The rule ID (BR-16), the flag (fatal), and the XPath location tell you exactly what went wrong and where.

Reading a Schematron Rule: Anatomy Breakdown

Let's dissect a real rule so you can read any .sch file:

<pattern id="UBL-model">
  <rule context="cac:InvoiceLine">
    <assert id="BR-21"
      test="exists(cbc:ID)"
      flag="fatal">
      [BR-21] Each Invoice line (BG-25) shall have an Invoice line identifier (BT-126).
    </assert>
  </rule>
</pattern>
ComponentValueMeaning
<pattern>UBL-modelGroups related rules
<rule context>cac:InvoiceLineRuns for every InvoiceLine element in the document
<assert id>BR-21Human-readable rule identifier
testexists(cbc:ID)XPath expression — must evaluate to true for the invoice to pass
flagfatalSeverity — fatal = rejection, warning = advisory
Message[BR-21] Each Invoice line...Explanation shown when the rule fires

The test attribute is always an XPath expression evaluated in the context defined by the <rule>. If the test returns false, the assertion fails and the rule fires.

Common Schematron Errors and How to Fix Them

Based on millions of invoices validated through our pipeline, these are the rules that fire most often:

Calculation Errors (BR-CO-*)

Problem: Your line totals, tax amounts, or document totals don't add up.

Root cause: Usually rounding. EN16931 rules expect decimal precision of 2 places for amounts and specific rounding behavior. If your ERP rounds line items individually before summing, you'll get different results than rounding the sum.

Fix: Sum first, round last. Calculate at full precision, then round the final amount to 2 decimal places. Check that LineExtensionAmount = Σ(InvoiceLine/LineExtensionAmount) exactly.

Missing Conditional Fields (BR-* with co-occurrence)

Problem: You included field A but forgot field B, which is required when A is present.

Example: You set TaxCategory/ID to AE (reverse charge) but didn't include TaxExemptionReason. Rule BR-AE-10 fires.

Fix: Map all co-occurrence rules for the tax categories you use. When TaxCategory/ID is AE, E, G, O, or K, additional fields become mandatory.

Country-Specific Surprises (BR-CC-*)

Problem: Your invoice passes EN16931 and Peppol validation but fails country rules.

Example: Sending to a German public buyer without cac:Contact on the seller party. BR-DE-1 fires.

Fix: Know your destination. Multi-country pipelines need per-country configuration. Invoice Navigator's validator detects the target country automatically and applies the right rule set.

Electronic Address Scheme Errors (PEPPOL-EN16931-R*)

Problem: Your electronic address (EndpointID) uses an unrecognized scheme.

Fix: Use valid scheme IDs from the Peppol code list. Common values: 0106 (NL KvK), 0190 (NL OIN), 9930 (IT Codice Fiscale), 0088 (EAN/GLN), 0184 (DK CVR), 0201 (IT Codice IPA).

Where to Find the Actual Schematron Files

All EU e-invoicing Schematron files are open source. You can read, compile, and run them yourself.

Rule setRepositoryMaintainer
EN16931 core (UBL + CII)ConnectingEurope/eInvoicing-EN16931CEN/TC 434
Peppol BIS 3.0OpenPEPPOL/peppol-bis-invoice-3OpenPEPPOL
XRechnung (Germany)itplr-kosit/xrechnung-schematronKoSIT
Country-specific CIUSVaries by countryNational standardization bodies

These repos are the source of truth. When a new release drops (Peppol releases biannually in May and November, EN16931 updates periodically), the Schematron files change and your validation results may differ.

Integrating Schematron Validation in Your Pipeline

If you're building validation into your own pipeline, you have three options:

Option 1: Run Schematron Yourself

Use a Schematron processor like ph-schematron (Java) or the Saxon XSLT processor. Download the .sch files from the repos above, compile them to XSLT, and run them against your invoices.

Pros: Full control, no external dependencies. Cons: You maintain the rule sets yourself. Every Peppol release, every EN16931 update, every country CIUS change — you need to pull, recompile, retest.

Option 2: Use a Reference Validator

The KoSIT Validator is the German reference implementation. It bundles EN16931, Peppol, and XRechnung rules. Other countries have their own reference validators.

Pros: Pre-configured for specific country stacks. Cons: Country-specific. No cross-country coverage out of the box.

Option 3: Use an API

Invoice Navigator's validation API runs all three layers — EN16931, Peppol, and country CIUS — in a single call. It returns structured JSON with every fired rule classified by severity, layer, and rule set. Built for ERP pipelines that need to validate invoices across multiple EU countries without managing rule sets themselves.

curl -X POST https://api.invoicenavigator.eu/v1/validate \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "file=@invoice.xml"

Response:

{
  "valid": false,
  "errors": [
    {
      "rule_id": "BR-DE-1",
      "layer": "cius-de",
      "severity": "fatal",
      "message": "An invoice MUST contain Seller contact information.",
      "location": "/Invoice/AccountingSupplierParty/Party",
      "fix_suggestion": "Add Contact element with Name, Telephone, and ElectronicMail"
    }
  ]
}

FAQ

Do all EU countries use Schematron for validation?

Yes. EN16931 mandates Schematron-based validation for all conformant invoices, regardless of syntax (UBL or CII). Individual countries add their own Schematron rule sets on top. The exception is when a country uses a centralized platform (like Italy's SDI or Poland's KSeF) that performs validation server-side — but the validation logic is still based on Schematron or equivalent rule assertions.

What's the difference between a "fatal" and "warning" flag?

A fatal flag means the invoice is non-compliant and will be rejected by the receiving system. A warning flag means the invoice technically passes but has an issue that should be addressed. Most EN16931 core rules are fatal. Some Peppol rules are warnings (like the order reference recommendation in PEPPOL-EN16931-R003).

How often do Schematron rules change?

EN16931 updates are infrequent — major releases every few years, with minor corrections periodically. Peppol BIS releases biannually (May and November). Country CIUS rules can change more frequently, especially for countries actively rolling out mandates (Germany, France, Poland in 2025-2026).

Can I write custom Schematron rules for my own validation?

Yes. Schematron is a general-purpose standard. Many ERP vendors add internal rules on top of the EU standard — for example, checking that specific custom fields are populated before sending to a particular buyer. The syntax is the same as the rules shown above.

My invoice passes EN16931 but fails Peppol validation. Why?

Peppol BIS 3.0 is a superset of EN16931. It adds requirements that EN16931 doesn't mandate — electronic address schemes, specific code list restrictions, and additional business process rules. An invoice can be EN16931-compliant and still fail Peppol validation if it lacks Peppol-specific elements.

Where does Invoice Navigator fit in the validation stack?

Invoice Navigator is a compliance API that runs all three Schematron layers (EN16931 core, Peppol BIS, and country CIUS) against your invoices. It detects the applicable rule sets automatically based on the invoice content and target country, returns structured error reports, and suggests fixes. It's designed for ERP vendors who need to validate across multiple EU countries without maintaining the rule sets themselves.


Debugging a specific Schematron error? Check our error library for detailed explanations of every EN16931 and country-specific rule. Need to validate invoices programmatically? See our API documentation.

Check Your Compliance Status

Find out exactly what your business needs to do for e-invoicing compliance.

Use Obligation Finder