Skip to main content

Document Validation

GoRoute validates all documents through multiple layers before transmission to ensure compliance with Peppol, EU, and national requirements.

Validation Layers

Document → Layer 1 → Layer 2 → Layer 3 → Layer 4 → Transmission
│ │ │ │
▼ ▼ ▼ ▼
UBL XSD Business Schematron Country
Schema Rules (EN16931) CIUS

Layer 1: XSD Validation

Validates document structure against UBL 2.1 XML Schema:

  • Element names and hierarchy
  • Required elements present
  • Data type correctness
  • Namespace compliance
<!-- ✅ Valid structure -->
<cbc:IssueDate>2024-01-15</cbc:IssueDate>

<!-- ❌ Invalid: wrong date format -->
<cbc:IssueDate>15/01/2024</cbc:IssueDate>

<!-- ❌ Invalid: missing required element -->
<Invoice>
<!-- IssueDate is required but missing -->
</Invoice>

Layer 2: Business Rules

Validates mathematical and logical consistency:

RuleDescription
Line totalsquantity × price = line amount
Tax calculationsTax amounts match tax rates
Invoice totalSum of lines + tax = payable amount
Date logicDue date ≥ issue date
# Example business rule validation
def validate_totals(invoice):
calculated_total = sum(line.amount for line in invoice.lines)
if calculated_total != invoice.total:
raise ValidationError(
f"Line total {calculated_total} does not match "
f"invoice total {invoice.total}"
)

Layer 3: Schematron Validation

Validates against EN16931 European standard and Peppol BIS 3.0:

EN16931 (Base)

└── Peppol BIS 3.0 (Peppol-specific rules)

└── Country CIUS (National requirements)

Common Schematron Errors:

RuleDescriptionSeverity
BR-01Invoice must have invoice numberError
BR-02Invoice must have issue dateError
BR-16Line extension amount must be rounded to 2 decimalsError
PEPPOL-EN16931-R001BIS profile must be specifiedError
PEPPOL-EN16931-R002Customization ID must be validError

Layer 4: Country CIUS

Additional rules for specific countries:

CountryCIUSKey Requirements
🇩🇪 GermanyXRechnungLeitweg-ID required for B2G
🇮🇹 ItalyFatturaPACodice Destinatario required
🇫🇷 FranceFactur-XChorus Pro requirements
🇳🇱 NetherlandsNLCIUSKVK number format

Pre-Validation API

Validate documents before sending:

import requests

def validate_invoice(xml_content: str) -> dict:
"""Validate an invoice without sending."""
response = requests.post(
"https://app.goroute.ai/peppol-api/api/v1/validate",
headers={
"X-API-Key": "your_api_key",
"Content-Type": "application/xml"
},
data=xml_content
)
return response.json()

# Example usage
result = validate_invoice(invoice_xml)

if result["valid"]:
print("✅ Document is valid")
else:
print("❌ Validation failed:")
for error in result["errors"]:
print(f" - {error['rule']}: {error['message']}")

Validation Response

{
"valid": false,
"document_type": "Invoice",
"validation_layers": {
"xsd": {"passed": true, "errors": []},
"business_rules": {"passed": true, "errors": []},
"schematron": {
"passed": false,
"errors": [
{
"rule": "BR-16",
"severity": "error",
"location": "/Invoice/cac:InvoiceLine[1]/cbc:LineExtensionAmount",
"message": "Amount MUST be rounded to maximum 2 decimals",
"value": "100.123"
}
],
"warnings": [
{
"rule": "PEPPOL-EN16931-R120",
"severity": "warning",
"location": "/Invoice/cac:AccountingSupplierParty",
"message": "Supplier VAT identifier SHOULD be provided"
}
]
},
"cius": {"passed": true, "errors": []}
},
"error_count": 1,
"warning_count": 1
}

Error Handling

Error Severity

SeverityActionCan Send?
errorMust fix before sending❌ No
warningReview recommended✅ Yes
infoInformational only✅ Yes

Common Errors and Fixes

BR-16: Rounding Error

# ❌ Wrong: Too many decimals
<cbc:LineExtensionAmount currencyID="EUR">100.123</cbc:LineExtensionAmount>

# ✅ Correct: Maximum 2 decimals
<cbc:LineExtensionAmount currencyID="EUR">100.12</cbc:LineExtensionAmount>

BR-CO-10: Tax Total Mismatch

# ❌ Wrong: Tax total doesn't match calculated
line_tax = 21.00 # (100.00 * 21%)
invoice_tax_total = 20.00 # Incorrect

# ✅ Correct: Values match
line_tax = 21.00
invoice_tax_total = 21.00

PEPPOL-EN16931-R001: Missing Profile

<!-- ❌ Wrong: Missing ProfileID -->
<Invoice>
<cbc:CustomizationID>urn:cen.eu:en16931:2017...</cbc:CustomizationID>
<!-- ProfileID missing -->
</Invoice>

<!-- ✅ Correct: Both IDs present -->
<Invoice>
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0</cbc:CustomizationID>
<cbc:ProfileID>urn:fdc:peppol.eu:2017:poacc:billing:01:1.0</cbc:ProfileID>
</Invoice>

Batch Validation

Validate multiple documents at once:

import requests

def validate_batch(invoices: list) -> dict:
"""Validate multiple invoices in one call."""
response = requests.post(
"https://app.goroute.ai/peppol-api/api/v1/validate/batch",
headers={
"X-API-Key": "your_api_key",
"Content-Type": "application/json"
},
json={
"documents": [
{"id": "inv-001", "xml": invoices[0]},
{"id": "inv-002", "xml": invoices[1]},
]
}
)
return response.json()

result = validate_batch(invoice_list)
print(f"Valid: {result['valid_count']}/{result['total_count']}")

Validation Best Practices

1. Validate Early

# ✅ Validate before storing
def create_invoice(data):
xml = generate_ubl(data)
validation = validate_invoice(xml)

if not validation["valid"]:
raise ValueError(f"Invalid invoice: {validation['errors']}")

return save_invoice(xml)

2. Handle Warnings Appropriately

# Warnings don't block sending but should be reviewed
if validation["warning_count"] > 0:
log.warning(f"Invoice has {validation['warning_count']} warnings")
for warning in validation["warnings"]:
log.warning(f" {warning['rule']}: {warning['message']}")

3. Cache Validation Results

import hashlib

def get_cached_validation(xml: str) -> dict:
"""Cache validation results by document hash."""
doc_hash = hashlib.sha256(xml.encode()).hexdigest()

cached = redis.get(f"validation:{doc_hash}")
if cached:
return json.loads(cached)

result = validate_invoice(xml)
redis.setex(f"validation:{doc_hash}", 3600, json.dumps(result))
return result

API Reference

Validate Single Document

POST /api/v1/validate
Content-Type: application/xml

<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="...">
...
</Invoice>

Validate Batch

POST /api/v1/validate/batch
Content-Type: application/json

{
"documents": [
{"id": "doc-1", "xml": "<Invoice>...</Invoice>"},
{"id": "doc-2", "xml": "<Invoice>...</Invoice>"}
]
}

Validate with Options

POST /api/v1/validate?skip_cius=true&include_warnings=false
Content-Type: application/xml
ParameterTypeDescription
skip_ciusbooleanSkip country-specific validation
include_warningsbooleanInclude warnings in response
ciusstringForce specific CIUS (e.g., xrechnung)

Next Steps