PEPPOL-EN16931-R004: Fix Tax Amount Calculation Errors
PEPPOL-EN16931-R004 is the most common tax-related validation error in Peppol. It means the total tax amount on your invoice does not match the sum of the individual tax category amounts. The mismatch is usually caused by rounding differences between per-line and per-category tax calculations. Here is how to fix it.
The error
[PEPPOL-EN16931-R004] - Tax Amount (BT-110) must equal the sum of Tax category tax amount (BT-117).What this rule checks
Every Peppol invoice has a total tax amount (BT-110 in the TaxTotal/TaxAmount element). It also has one or more tax subtotals, each representing a different VAT rate. Rule R004 checks that the total equals the sum of the subtotals. The rule is defined in the Peppol BIS Billing 3.0 rules.
In the UBL XML, it looks like this:
<cac:TaxTotal>
<!-- BT-110: This must equal the sum of all BT-117 values below -->
<cbc:TaxAmount currencyID="EUR">131.25</cbc:TaxAmount>
<!-- Tax category 1: 21% VAT -->
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="EUR">500.00</cbc:TaxableAmount>
<!-- BT-117: Tax for this category -->
<cbc:TaxAmount currencyID="EUR">105.00</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
<!-- Tax category 2: 6% VAT -->
<cac:TaxSubtotal>
<cbc:TaxableAmount currencyID="EUR">437.50</cbc:TaxableAmount>
<!-- BT-117: Tax for this category -->
<cbc:TaxAmount currencyID="EUR">26.25</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>6</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>In this example: 105.00 + 26.25 = 131.25. The total matches. Rule R004 passes.
Why it fails: the rounding problem
The most common cause of R004 failures is calculating tax per invoice line instead of per tax category. Here is a concrete example:
Example: Three line items at 21% VAT
Line 1: €33.33 net. VAT = 33.33 × 0.21 = 6.9993 → rounded to €7.00
Line 2: €33.33 net. VAT = 33.33 × 0.21 = 6.9993 → rounded to €7.00
Line 3: €33.34 net. VAT = 33.34 × 0.21 = 7.0014 → rounded to €7.00
Per-line sum: 7.00 + 7.00 + 7.00 = €21.00
Per-category: (33.33 + 33.33 + 33.34) × 0.21 = 100.00 × 0.21 = €21.00
This example matches. But change the amounts slightly and it breaks.
Example that fails R004
Line 1: €10.01 net. VAT = 10.01 × 0.21 = 2.1021 → rounded to €2.10
Line 2: €10.01 net. VAT = 10.01 × 0.21 = 2.1021 → rounded to €2.10
Line 3: €10.01 net. VAT = 10.01 × 0.21 = 2.1021 → rounded to €2.10
Per-line sum: 2.10 + 2.10 + 2.10 = €6.30
Per-category: (10.01 + 10.01 + 10.01) × 0.21 = 30.03 × 0.21 = 6.3063 → rounded to €6.31
Mismatch: 6.30 vs 6.31. R004 fails.
The correct calculation order
Peppol expects this calculation order. Follow these steps exactly to avoid R004:
Calculate each line net amount
Line net amount = quantity × unit price (adjusted for line allowances/charges). Round to 2 decimal places.
Sum line amounts by tax category
Group all lines by their tax category (e.g., 21% S, 6% S, 0% Z). Sum the line net amounts for each group. This gives you the taxable amount (BT-116) per category.
Calculate tax per category
For each tax category: tax amount (BT-117) = taxable amount (BT-116) × tax rate. Round to 2 decimal places. Do NOT sum per-line tax amounts.
Sum category tax amounts
Total tax amount (BT-110) = sum of all BT-117 values. This is what R004 checks.
Pseudocode for correct tax calculation
// Step 1: Calculate line net amounts
for each line:
line.netAmount = round(line.quantity * line.unitPrice, 2)
// Step 2: Group by tax category and sum
taxCategories = {}
for each line:
key = line.taxCategory + "_" + line.taxRate
taxCategories[key].taxableAmount += line.netAmount
// Step 3: Calculate tax per category (NOT per line)
for each category in taxCategories:
category.taxAmount = round(
category.taxableAmount * category.taxRate, 2
)
// Step 4: Total tax = sum of category tax amounts
totalTax = sum(category.taxAmount for each category)
// This totalTax goes into TaxTotal/TaxAmount (BT-110)
// Each category.taxAmount goes into TaxSubtotal/TaxAmount (BT-117)
// R004 checks: totalTax == sum(all BT-117)Rounding rules
Peppol uses the round-half-up method for all monetary amounts:
- All amounts rounded to 2 decimal places
- When the value is exactly at the midpoint (e.g., 2.105), round up (to 2.11)
- Round only at each final step, not intermediate calculations
- Tolerance per tax category subtotal: ±€0.01
- Total tolerance for R004: number of tax categories × €0.02
Tolerance example
Invoice with 2 tax categories (21% and 6%):
Allowed tolerance = 2 × €0.02 = €0.04. If the total tax is €131.25 but the sum of subtotals is €131.23, the difference is €0.02 which is within tolerance. R004 passes.
Correct UBL XML example
<!-- Invoice with 21% VAT: correct per-category calculation -->
<cac:InvoiceLine>
<cbc:ID>1</cbc:ID>
<cbc:InvoicedQuantity unitCode="EA">1</cbc:InvoicedQuantity>
<cbc:LineExtensionAmount currencyID="EUR">10.01</cbc:LineExtensionAmount>
<cac:Item>
<cbc:Name>Item A</cbc:Name>
<cac:ClassifiedTaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:ClassifiedTaxCategory>
</cac:Item>
<cac:Price>
<cbc:PriceAmount currencyID="EUR">10.01</cbc:PriceAmount>
</cac:Price>
</cac:InvoiceLine>
<!-- Lines 2 and 3: same structure, same amounts -->
<!-- Tax totals: calculated per category, NOT per line -->
<cac:TaxTotal>
<!-- BT-110: Sum of all BT-117 = 6.31 -->
<cbc:TaxAmount currencyID="EUR">6.31</cbc:TaxAmount>
<cac:TaxSubtotal>
<!-- BT-116: Sum of line amounts for 21% -->
<cbc:TaxableAmount currencyID="EUR">30.03</cbc:TaxableAmount>
<!-- BT-117: 30.03 x 0.21 = 6.3063, rounded = 6.31 -->
<cbc:TaxAmount currencyID="EUR">6.31</cbc:TaxAmount>
<cac:TaxCategory>
<cbc:ID>S</cbc:ID>
<cbc:Percent>21</cbc:Percent>
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme>
</cac:TaxCategory>
</cac:TaxSubtotal>
</cac:TaxTotal>
<cac:LegalMonetaryTotal>
<cbc:TaxExclusiveAmount currencyID="EUR">30.03</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="EUR">36.34</cbc:TaxInclusiveAmount>
<cbc:PayableAmount currencyID="EUR">36.34</cbc:PayableAmount>
</cac:LegalMonetaryTotal>Let us handle the tax calculations
With e-invoice.be, send your invoices as JSON via the API or PDF via email. We calculate all tax amounts, apply correct rounding, and generate the UBL XML. No R004 errors, no rounding headaches.
Frequently asked questions
What is PEPPOL-EN16931-R004?
PEPPOL-EN16931-R004 is a Peppol validation rule that checks whether the total tax amount (BT-110) equals the sum of all individual tax subtotal amounts. If the amounts do not match within a tolerance of two cents per tax category, the invoice is rejected with error code 106.
How much rounding tolerance does Peppol allow for tax amounts?
Peppol allows a tolerance of plus or minus one cent (0.01) per tax category subtotal when comparing calculated vs. stated amounts. For the total tax amount check in PEPPOL-EN16931-R004, the tolerance is the number of tax categories multiplied by 0.02. So if you have two VAT rates, the tolerance is 0.04.
Should I calculate VAT per line or on the total?
Peppol expects tax to be calculated per tax category subtotal, not per individual line. Sum all line net amounts for each tax category first, then calculate VAT on that subtotal. This avoids cumulative rounding errors from per-line calculations.
What is the correct rounding method for Peppol tax amounts?
Peppol uses the round-half-up method. When a value is exactly at the midpoint (e.g., 0.005), round up to 0.01. All monetary amounts must be rounded to two decimal places. Apply rounding only at the final step of each calculation, not on intermediate results.
Why does my invoice fail R004 when my accounting software calculates correctly?
Many accounting systems calculate VAT per invoice line and then sum the rounded results. Peppol expects VAT calculated on the tax category subtotal. The difference is subtle but can produce mismatches. For example, three lines at 10.01 each with 21% VAT: per-line rounding gives 3 times 2.10 = 6.30, but 21% of 30.03 = 6.31. This one-cent difference triggers R004.