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โ€‹