PEPPOL API
REST API for PEPPOL e-invoicing. Transform validation errors into human-readable explanations. Integrate into your access point, ERP system, or e-invoicing platform.
Endpoint
Choose Your Error Source
Select the validation source you're integrating with to see the specific format and examples:
eConnect PSB Response Format
The eConnect PSB API returns validation errors in this format:
{
"source": {
"typeId": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1",
"typeName": "Peppol BIS Billing UBL Invoice V3",
"profileId": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0"
},
"assertions": [
{
"artifact": { "type": "schema", "name": "UBL Version 2.1" },
"flag": "error",
"text": "The 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2:IssueDate' element is invalid..."
},
{
"artifact": { "type": "business", "name": "Peppol BIS Billing v1.0.18 (Nov24)" },
"flag": "fatal",
"test": "cbc:EndpointID",
"location": "/*:Invoice[namespace-uri()='...'][1]/*:AccountingSupplierParty[...][1]/*:Party[...][1]",
"code": "PEPPOL-EN16931-R020",
"text": "Seller electronic address MUST be provided."
}
]
}
documentInfo extracted from your XML, or explanations will be generic and unhelpful.
Complete Request (PSB + documentInfo)
Combine the PSB response with extracted document data:
POST /api/explainer
Content-Type: application/json
{
"assertions": [
// ... paste assertions from eConnect PSB response
],
"documentInfo": {
"currency": "EUR",
"taxExclusiveAmount": 200.00,
"taxAmount": 42.00,
"taxInclusiveAmount": 242.00,
"sellerName": "Acme Corp B.V.",
"sellerCountry": "NL",
"buyerName": "Client GmbH"
}
}
code field. The API generates IDs like SCHEMA-ISSUEDATE from the error text.
Field Mapping
| eConnect Field | Maps To | Notes |
|---|---|---|
code | rule | Only present for business rules, not schema errors |
location | xpath | Full namespace-uri XPath, auto-cleaned by API |
text | message | |
flag | severity | error, warning, or fatal |
test | test | Schematron test expression (preserved) |
artifact.type | — | Used to detect schema vs business errors |
Helger/PHIVE Response Format
The Helger PHIVE validator returns errors in this structure:
{
"success": false,
"resultObj": {
"results": [
{
"items": [
{
"errorLevel": "ERROR",
"errorID": "BR-01",
"errorText": "An Invoice shall have an Invoice number.",
"errorLocation": "/Invoice/cbc:ID"
}
]
}
]
}
}
documentInfo from your XML, or explanations will show "N/A" for all values.
Complete Request (Helger + documentInfo)
POST /api/explainer
Content-Type: application/json
{
"success": false,
"resultObj": {
"results": [{ "items": [/* errors from Helger */] }]
},
"documentInfo": {
"currency": "EUR",
"taxExclusiveAmount": 200.00,
"taxAmount": 42.00,
"sellerName": "Acme Corp"
}
}
Field Mapping
| Helger Field | Maps To |
|---|---|
errorID | rule |
errorLocation | xpath |
errorText | message |
errorLevel | severity |
Basic Format
An array of error objects with flexible field names:
[
{
"rule": "BR-01",
"message": "An Invoice shall have an Invoice number",
"xpath": "/Invoice/cbc:ID",
"severity": "error"
}
]
documentInfo, calculation errors (BR-CO-xx) will show "N/A" instead of actual amounts. The API cannot guess your document's values.
Complete Request (with documentInfo)
POST /api/explainer
Content-Type: application/json
{
"errors": [
{
"rule": "BR-CO-15",
"message": "Invoice total amount with VAT must equal...",
"xpath": "/Invoice/cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount",
"actual": "243.00"
}
],
"documentInfo": {
"taxExclusiveAmount": 200.00,
"taxAmount": 42.00,
"currency": "EUR"
}
}
Supported Field Names
| For Rule ID | rule, code, errorCode, errorID, id, ruleId |
|---|---|
| For Location | xpath, location, path, context |
| For Message | message, text, description |
| For Value | actual, value, actualValue |
| For Severity | severity, flag, level, errorLevel |
Automatic Integration
The mimBo's PEPPOL Validator automatically calls this API and displays explanations. No extra work needed!
Manual API Call
If calling manually, use the errors array from the validation response:
POST /api/explainer
Content-Type: application/json
{
"errors": [
{
"rule": "BR-CO-15",
"severity": "fatal",
"xpath": "/Invoice/cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount",
"message": "[BR-CO-15]-Invoice total amount with VAT...",
"actual": "243.00"
}
],
"documentInfo": {
"currency": "EUR",
"taxExclusiveAmount": 200.00,
"taxAmount": 42.00,
"taxInclusiveAmount": 243.00
}
}
documentInfo from your XML, so explanations include the actual amounts.
How It Works
Why documentInfo Matters
Comparison: With vs Without documentInfo
{
"rule": "BR-CO-15",
"explanation": "The invoice total with VAT is incorrect. You have N/A but it should be N/A + N/A = the correct sum.",
"fix": "Set TaxInclusiveAmount to the correct sum. Verify TaxExclusiveAmount (N/A) and TaxAmount (N/A) are correct first."
}
{
"rule": "BR-CO-15",
"explanation": "The invoice total with VAT is incorrect. You have 243.00 EUR but it should be 200.00 + 42.00 = 242.00 EUR.",
"fix": "Set TaxInclusiveAmount to 242.00 EUR. Verify TaxExclusiveAmount (200.00) and TaxAmount (42.00) are correct first."
}
The difference is clear: with documentInfo, users see both the current wrong value (243.00) and the expected correct value (242.00), making it obvious what to fix.
Adding documentInfo to Your Request
Extract ALL available fields from your XML and include them in every request. Fields that don't exist in the invoice (e.g., prepaidAmount if there's no prepayment) can be omitted - the API will show "N/A" for those.
POST /api/explainer
Content-Type: application/json
{
"assertions": [/* your validation errors */],
"documentInfo": {
// Document identification
"invoiceNumber": "INV-2024-001",
"issueDate": "2024-12-05",
"dueDate": "2025-01-05",
"documentType": "380",
"orderReference": "PO-2024-123",
"contractReference": "CONTRACT-456",
// Monetary amounts (ALWAYS include these)
"currency": "EUR",
"lineExtensionAmount": 1000.00,
"taxExclusiveAmount": 1000.00,
"taxAmount": 210.00,
"taxInclusiveAmount": 1210.00,
"payableAmount": 1210.00,
"prepaidAmount": 0.00,
// Seller details
"sellerName": "Acme Corp B.V.",
"sellerCountry": "NL",
"sellerVAT": "NL123456789B01",
"sellerEndpoint": "12345678",
"sellerEndpointScheme": "0106",
"sellerRegistration": "12345678",
"sellerStreet": "Keizersgracht 123",
"sellerCity": "Amsterdam",
"sellerPostcode": "1015 CJ",
// Buyer details
"buyerName": "Client Company GmbH",
"buyerCountry": "DE",
"buyerVAT": "DE123456789",
"buyerEndpoint": "DE123456789",
"buyerEndpointScheme": "9930",
"buyerStreet": "Hauptstraße 1",
"buyerCity": "Berlin",
"buyerPostcode": "10115",
// Tax breakdown (if available)
"taxCategoryCode": "S",
"taxPercent": 21,
"taxableAmount": 1000.00,
// Payment info
"paymentMeansCode": "58",
"iban": "NL91ABNA0417164300",
"bic": "ABNANL2A"
}
}
Where to Find These Values
XPath locations vary by invoice format. Click your format below:
// Document info
invoiceNumber: //cbc:ID
issueDate: //cbc:IssueDate
dueDate: //cbc:DueDate
currency: //cbc:DocumentCurrencyCode
documentType: //cbc:InvoiceTypeCode
// Monetary amounts
lineExtensionAmount: //cac:LegalMonetaryTotal/cbc:LineExtensionAmount
taxExclusiveAmount: //cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount
taxInclusiveAmount: //cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount
payableAmount: //cac:LegalMonetaryTotal/cbc:PayableAmount
prepaidAmount: //cac:LegalMonetaryTotal/cbc:PrepaidAmount
taxAmount: //cac:TaxTotal/cbc:TaxAmount
// Seller
sellerName: //cac:AccountingSupplierParty//cac:PartyLegalEntity/cbc:RegistrationName
sellerCountry: //cac:AccountingSupplierParty//cac:Country/cbc:IdentificationCode
sellerVAT: //cac:AccountingSupplierParty//cac:PartyTaxScheme/cbc:CompanyID
sellerEndpoint: //cac:AccountingSupplierParty//cbc:EndpointID
sellerEndpointScheme://cac:AccountingSupplierParty//cbc:EndpointID/@schemeID
sellerRegistration: //cac:AccountingSupplierParty//cac:PartyLegalEntity/cbc:CompanyID
sellerStreet: //cac:AccountingSupplierParty//cac:PostalAddress/cbc:StreetName
sellerCity: //cac:AccountingSupplierParty//cac:PostalAddress/cbc:CityName
sellerPostcode: //cac:AccountingSupplierParty//cac:PostalAddress/cbc:PostalZone
// Buyer
buyerName: //cac:AccountingCustomerParty//cac:PartyLegalEntity/cbc:RegistrationName
buyerCountry: //cac:AccountingCustomerParty//cac:Country/cbc:IdentificationCode
buyerVAT: //cac:AccountingCustomerParty//cac:PartyTaxScheme/cbc:CompanyID
buyerEndpoint: //cac:AccountingCustomerParty//cbc:EndpointID
buyerEndpointScheme: //cac:AccountingCustomerParty//cbc:EndpointID/@schemeID
// Tax breakdown (first category)
taxCategoryCode: //cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:ID
taxPercent: //cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory/cbc:Percent
taxableAmount: //cac:TaxTotal/cac:TaxSubtotal/cbc:TaxableAmount
// Payment
paymentMeansCode: //cac:PaymentMeans/cbc:PaymentMeansCode
iban: //cac:PaymentMeans/cac:PayeeFinancialAccount/cbc:ID
bic: //cac:PaymentMeans/cac:PayeeFinancialAccount/cac:FinancialInstitutionBranch/cbc:ID
NLCIUS uses standard UBL 2.1 structure with Dutch-specific requirements:
// Same as UBL, plus Dutch-specific fields:
// Seller KVK (required for Dutch sellers)
sellerRegistration: //cac:AccountingSupplierParty//cac:PartyLegalEntity/cbc:CompanyID
// schemeID should be "0106" for KVK
// Seller OIN (for government)
sellerEndpoint: //cac:AccountingSupplierParty//cbc:EndpointID
// schemeID "0190" for OIN, "0106" for KVK
// Dutch payment codes (NL-R-008)
paymentMeansCode: //cac:PaymentMeans/cbc:PaymentMeansCode
// Valid: 30, 48, 49, 57, 58, 59
// IBAN (Dutch format: NL + 2 check + 4 bank + 10 account)
iban: //cac:PaymentMeans/cac:PayeeFinancialAccount/cbc:ID
Polish KSeF uses the FA (Faktura) XML schema, different from UBL:
// Document info (namespace: //tns:Faktura or //Faktura)
invoiceNumber: //Fa/P_2
issueDate: //Fa/P_1
currency: //Fa/KodWaluty
documentType: //Fa/RodzajFaktury // "VAT", "KOR", "ZAL"
// Monetary amounts
taxExclusiveAmount: //Fa/P_13_1 // Suma wartości netto
taxAmount: //Fa/P_14_1 // Suma podatku
taxInclusiveAmount: //Fa/P_15 // Suma brutto
payableAmount: //Fa/P_15 // Kwota do zapłaty
// Seller (Podmiot1 = sprzedawca)
sellerName: //Podmiot1//Nazwa or //Podmiot1//NazwaHandlowa
sellerCountry: //Podmiot1//KodKraju
sellerVAT: //Podmiot1//NIP // Polish NIP (10 digits)
sellerStreet: //Podmiot1//Ulica
sellerCity: //Podmiot1//Miejscowosc
sellerPostcode: //Podmiot1//KodPocztowy
// Buyer (Podmiot2 = nabywca)
buyerName: //Podmiot2//Nazwa or //Podmiot2//NazwaHandlowa
buyerCountry: //Podmiot2//KodKraju
buyerVAT: //Podmiot2//NIP // or //Podmiot2//NrId for EU VAT
buyerStreet: //Podmiot2//Ulica
buyerCity: //Podmiot2//Miejscowosc
buyerPostcode: //Podmiot2//KodPocztowy
// Tax breakdown (per rate: P_13_x and P_14_x)
// x = 1 (23%), 2 (8%), 3 (5%), 4 (0%), 5 (zw), 6 (np), 7 (oo)
taxPercent: 23, 8, 5, or 0 based on which P_13_x is used
taxableAmount: //Fa/P_13_1 // For 23% rate
// Payment
paymentMeansCode: //Platnosc/FormaPlatnosci // "przelew", "gotówka"
iban: //Platnosc/RachunekBankowy/NrRB
JavaScript Helper to Extract documentInfo
document.evaluate). For Node.js, use a library like xpath or fast-xml-parser.
// Browser JavaScript (works in frontend apps)
function extractDocumentInfo(xmlDoc) {
const getText = (xpath) => {
const node = xmlDoc.evaluate(xpath, xmlDoc, null,
XPathResult.STRING_TYPE, null);
return node.stringValue || null;
};
return {
invoiceNumber: getText('//cbc:ID'),
issueDate: getText('//cbc:IssueDate'),
currency: getText('//cbc:DocumentCurrencyCode'),
lineExtensionAmount: parseFloat(getText('//cac:LegalMonetaryTotal/cbc:LineExtensionAmount')),
taxExclusiveAmount: parseFloat(getText('//cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount')),
taxAmount: parseFloat(getText('//cac:TaxTotal/cbc:TaxAmount')),
taxInclusiveAmount: parseFloat(getText('//cac:LegalMonetaryTotal/cbc:TaxInclusiveAmount')),
payableAmount: parseFloat(getText('//cac:LegalMonetaryTotal/cbc:PayableAmount')),
sellerName: getText('//cac:AccountingSupplierParty//cbc:RegistrationName'),
buyerName: getText('//cac:AccountingCustomerParty//cbc:RegistrationName')
};
}
// Usage: parse XML string first
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
const docInfo = extractDocumentInfo(xmlDoc);
Response Format
{
"success": true,
"results": [
{
"rule": "PEPPOL-EN16931-R020",
"severity": "error",
"location": "Invoice/AccountingSupplierParty/Party",
"value": null,
"originalMessage": "Seller electronic address MUST be provided.",
"explanation": "The seller must have an electronic address for PEPPOL routing.",
"importance": "Without this, the invoice cannot be delivered through PEPPOL.",
"fix": "Add cbc:EndpointID with schemeID (e.g., 0106 for NL) to the seller party.",
"fromCache": true
}
],
"stats": {
"total": 1,
"staticHits": 1,
"dbCacheHits": 0,
"aiCalls": 0
}
}
Stats Fields
| Field | Description |
|---|---|
total | Total number of errors processed |
staticHits | Errors resolved from built-in static templates (instant) |
dbCacheHits | Errors resolved from database cache (previous AI generations) |
blocked | Errors with invalid rule IDs that were blocked (no AI call) |
skipped | Errors skipped due to request limit (max 10 per request) |
aiCalls | Number of errors that required AI generation |
Rate Limits & Constraints
Additional constraints:
- Rule IDs must match known PEPPOL/UBL patterns (BR-xx, PEPPOL-xx, etc.)
- Invalid rule IDs are blocked and won't trigger AI generation
- No API key required
Privacy Option
By default, the API uses cached templates for known rules and falls back to AI for unknown rules. If you want to ensure your data is never processed by AI, add the optout field:
{
"errors": [...],
"optout": true
}
Error Responses
400 Bad Request
{
"error": "No validation errors found in request",
"hint": "Send errors as array or in validationResult/errors field",
"supportedFormats": [
"Array of error objects",
"eConnect PSB format: { validationResult: { errors: [...] } }",
"Helger format: { resultObj: { results: [...] } }",
"Generic: { errors: [...] }",
"With documentInfo: { errors: [...], documentInfo: { currency, taxAmount, ... } }"
]
}
405 Method Not Allowed
{
"error": "Method not allowed"
}
Only POST requests are accepted at /api/explainer.
500 Server Error
{
"error": "Failed to process validation errors",
"message": "Error details..."
}
CORS Support
The API supports Cross-Origin Resource Sharing (CORS) for browser-based applications:
Access-Control-Allow-Origin: *- All origins allowedAccess-Control-Allow-Methods: POST, OPTIONSAccess-Control-Allow-Headers: Content-Type, Authorization, X-API-Key
Preflight OPTIONS requests are handled automatically.
documentInfo Fields Reference
Include ALL fields that exist in your invoice. Only omit fields that genuinely don't exist in your document type (e.g., line-level fields for document-level errors):
Document & References
| Field | Type | Placeholder |
|---|---|---|
invoiceNumber | string | {invoiceNumber} |
issueDate | string | {issueDate} |
dueDate | string | {dueDate} |
documentType | string | {documentType} - Invoice or CreditNote |
orderReference | string | {orderReference} |
contractReference | string | {contractReference} |
Monetary Amounts
| Field | Type | Placeholder |
|---|---|---|
currency | string | {currency} - EUR, USD, etc. |
lineExtensionAmount | number | {net} - Sum of line net amounts |
taxExclusiveAmount | number | {taxExclusive} - Total before tax |
taxAmount | number | {tax} - Total VAT/tax |
taxInclusiveAmount | number | {taxInclusive} - Total with tax |
payableAmount | number | {payable} - Amount to pay |
prepaidAmount | number | {prepaid} - Already paid amount |
| (calculated) | number | {expectedTotal} - taxExclusive + tax |
| (calculated) | number | {expectedPayable} - taxInclusive - prepaid |
| (calculated) | number | {expectedLineAmount} - lineQuantity × lineUnitPrice |
Seller Information
| Field | Type | Placeholder |
|---|---|---|
sellerName | string | {sellerName} |
sellerCountry | string | {sellerCountry} |
sellerVAT | string | {sellerVAT} - VAT registration (NL123456789B01) |
sellerEndpoint | string | {sellerEndpoint} - PEPPOL endpoint ID |
sellerEndpointScheme | string | {sellerEndpointScheme} - e.g., 0106, 0190 |
sellerRegistration | string | {sellerRegistration} - KVK, company number |
sellerTaxScheme | string | {sellerTaxScheme} - Tax registration scheme |
sellerStreet | string | {sellerStreet} |
sellerCity | string | {sellerCity} |
sellerPostcode | string | {sellerPostcode} |
Buyer Information
| Field | Type | Placeholder |
|---|---|---|
buyerName | string | {buyerName} |
buyerCountry | string | {buyerCountry} |
buyerVAT | string | {buyerVAT} |
buyerEndpoint | string | {buyerEndpoint} - PEPPOL endpoint ID |
buyerEndpointScheme | string | {buyerEndpointScheme} |
buyerRegistration | string | {buyerRegistration} |
buyerStreet | string | {buyerStreet} |
buyerCity | string | {buyerCity} |
buyerPostcode | string | {buyerPostcode} |
Tax Details
| Field | Type | Placeholder |
|---|---|---|
taxCategoryCode | string | {taxCategoryCode} - S, Z, E, AE, etc. |
taxPercent | number | {taxPercent} - Adds % automatically |
taxableAmount | number | {taxableAmount} |
Line Item (for line-level errors)
| Field | Type | Placeholder |
|---|---|---|
lineNumber | string | {lineNumber} |
lineQuantity | number | {lineQuantity} |
lineUnitCode | string | {lineUnitCode} - EA, KGM, etc. |
lineUnitPrice | number | {lineUnitPrice} |
lineAmount | number | {lineAmount} |
itemName | string | {itemName} |
itemDescription | string | {itemDescription} |
Payment Information
| Field | Type | Placeholder |
|---|---|---|
paymentMeansCode | string | {paymentMeansCode} - 30, 58, etc. |
paymentID | string | {paymentID} - Payment reference |
iban | string | {iban} |
bic | string | {bic} |
paymentDueDate | string | {paymentDueDate} |
{expectedTotal} (taxExclusive + tax), {expectedPayable} (taxInclusive - prepaid), and {expectedLineAmount} (lineQuantity × lineUnitPrice). These show the correct values for calculation error fixes.
Template Placeholders Summary
All available placeholders that the API can fill. See the detailed tables above for source fields:
| Category | Placeholders |
|---|---|
| Error Context | {xpath} |
| Amounts | {net}, {tax}, {taxExclusive}, {taxInclusive}, {payable}, {prepaid}, {currency}, {expectedTotal}, {expectedPayable}, {expectedLineAmount} |
| Document | {invoiceNumber}, {issueDate}, {dueDate}, {documentType}, {orderReference}, {contractReference} |
| Seller | {sellerName}, {sellerCountry}, {sellerVAT}, {sellerEndpoint}, {sellerEndpointScheme}, {sellerRegistration}, {sellerTaxScheme}, {sellerStreet}, {sellerCity}, {sellerPostcode} |
| Buyer | {buyerName}, {buyerCountry}, {buyerVAT}, {buyerEndpoint}, {buyerEndpointScheme}, {buyerRegistration}, {buyerStreet}, {buyerCity}, {buyerPostcode} |
| Tax | {taxCategoryCode}, {taxPercent}, {taxableAmount} |
| Line Item | {lineNumber}, {lineQuantity}, {lineUnitCode}, {lineUnitPrice}, {lineAmount}, {itemName}, {itemDescription} |
| Payment | {paymentMeansCode}, {paymentID}, {iban}, {bic}, {paymentDueDate} |
Supported Rule Patterns
| Pattern | Example |
|---|---|
| EN16931 Business Rules | BR-01, BR-CO-10, BR-CL-01 |
| PEPPOL Rules | PEPPOL-EN16931-R001 |
| Country-specific | NL-R-001, SE-R-003, NO-R-002 |
| UBL/CII Format | UBL-CR-001, CII-SR-001 |
| XRechnung | XR-01 |
| Poland KSeF | KSeF-001 |
Example Code
JavaScript
const response = await fetch('https://mimbos.com/api/explainer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
assertions: [
{
code: 'BR-01',
location: '/Invoice/cbc:ID',
text: 'An Invoice shall have an Invoice number'
}
]
})
});
const data = await response.json();
data.results.forEach(r => {
console.log(r.rule + ': ' + r.explanation);
console.log('Fix: ' + r.fix);
});
cURL
curl -X POST https://mimbos.com/api/explainer \
-H "Content-Type: application/json" \
-d '{"assertions":[{"code":"BR-01","text":"An Invoice shall have an Invoice number"}]}'
Privacy & Service Level
Data Privacy
- Your invoice data is processed only to generate explanations
- No data is stored, logged, or retained after processing
- Data is never used for AI model training
- Use the
optout: trueflag to prevent any AI processing - All traffic is encrypted via HTTPS/TLS
Service Level Agreement
- 99.9% uptime target
- Average response time: <500ms for cached rules
- AI-generated explanations: 1-3 seconds
- No rate limiting for normal usage patterns
- Free tier with generous limits
Related
PEPPOL Validator - Validate invoices online
PEPPOL Lookup - Find PEPPOL participants