What BR-CO-15 checks
BR-CO-15 defines the gross total: Invoice total amount with VAT (BT-112) = Invoice total amount without VAT (BT-109) + Invoice total VAT amount (BT-110). It joins the two halves of the totals arithmetic, the net chain and the VAT breakdown, into the number the buyer actually recognises as "the invoice amount". In UBL, BT-112 is the cbc:TaxInclusiveAmount of the legal monetary total.
Layer and formats
An en16931 core layer rule for every syntax and profile. Both inputs are themselves policed: BT-109 by BR-CO-13 and BT-110 by BR-CO-14, so a failure lower down normally drags BR-CO-15 (and BR-CO-16) into the report with it. A lone BR-CO-15 failure points squarely at BT-112 itself.
Why it fails in practice
- Gross imported from the source system. The ERP's gross amount was rounded along a different path than the XML's net and VAT figures, and the three no longer add up.
- VAT recomputed at the wrong granularity. BT-110 is refreshed per category while BT-112 still holds net times a blended rate.
- Manual edits. A corrected VAT amount or net total is patched into the document without recomputing the gross.
How to fix it
Never source BT-112 independently: derive it as the sum of the emitted BT-109 and BT-110.
<cac:LegalMonetaryTotal>
<cbc:TaxExclusiveAmount currencyID="EUR">970.00</cbc:TaxExclusiveAmount>
<cbc:TaxInclusiveAmount currencyID="EUR">1154.30</cbc:TaxInclusiveAmount>
...
</cac:LegalMonetaryTotal>
In an InvoiceXML create request the field is totals.grandTotalAmount; leave invoice.totals out and the API derives it from the computed net total and VAT breakdown server-side.
The raw rejection vs the InvoiceXML finding
The Schematron report:
<svrl:failed-assert id="BR-CO-15" flag="fatal"
location="/*:Invoice[namespace-uri()='urn:oasis:names:specification:ubl:schema:xsd:Invoice-2'][1]">
<svrl:text>[BR-CO-15]-Invoice total amount with VAT (BT-112) =
Invoice total amount without VAT (BT-109) + Invoice total
VAT amount (BT-110).</svrl:text>
</svrl:failed-assert>
The InvoiceXML finding for the same failure:
{
"rule": "BR-CO-15",
"layer": "en16931",
"line": null,
"message": "The total amount with VAT must equal the total without VAT plus the total VAT amount. Check totals.",
"btCodes": ["BT-112", "BT-109", "BT-110"],
"fields": ["totals.grandTotalAmount", "totals.taxBasisTotalAmount", "totals.taxTotalAmount"],
"raw": "[BR-CO-15] EN16931: Invoice total amount with VAT (BT-112) = Invoice total amount without VAT (BT-109) + Invoice total VAT amount (BT-110)."
}
The message is safe to show an end customer, and fields names all three request fields in the equation for your UI to highlight. Clients can localize by the stable rule key without losing the mapping.
Check or generate documents with correct totals
Validate a file in the UBL validator or the XRechnung validator. Generation via POST /v1/create/ubl or POST /v1/create/facturx keeps the chain consistent by construction, with every document validated before delivery. The wider arithmetic is covered in EN 16931 explained.