Mutabiq checks Saudi ZATCA Phase 2 e-invoice XML before you submit it: pre-validation against published ZATCA requirements, not an approval by the authority. This page explains one rule, BR-CO-17.
Five checks a month on the Free plan. The file is checked in memory and is never stored.
The message ZATCA prints
[BR-CO-17]-VAT category tax amount (BT-117) = VAT category taxable amount (BT-116) x (VAT category rate (BT-119) / 100), rounded to two decimals.
Also cited in this report
BR-S-09
[BR-S-09]-The VAT category tax amount (BT-117) in a VAT breakdown (BG-23) where VAT category code (BT-118) is "Standard rated" shall equal the VAT category taxable amount (BT-116) multiplied by the VAT category rate (BT-119) / 100), rounded to two decimals.
BR-CO-15
[BR-CO-15]-Invoice total amount with VAT (BT-112) = Invoice total amount without VAT (BT-109) + Invoice total VAT amount (BT-110).
What kind of value was found — empty, not_15_digits and the like — never the value itself.
A VAT breakdown states no VAT amount
Reported astax_amount_missingthe VAT amount is missing
What is wrong
A cac:TaxSubtotal inside a cac:TaxTotal carries no cbc:TaxAmount. EN 16931 rule BR-CO-17, which ZATCA runs, requires the VAT category tax amount (BT-117) of every breakdown to equal the taxable amount (BT-116) times the rate (BT-119) divided by one hundred, and with no amount written there is nothing to compare. This rule judges every VAT category alike - standard rated, zero-rated, exempt and out of scope - and it reads a cac:TaxSubtotal at any depth, so one written inside an invoice line is inside it, unlike the category-specific rules which read only the breakdown under the document root.
What to check in your ERP
Every cac:TaxSubtotal your export writes needs its own cbc:TaxAmount, including the zero-rated, exempt and out-of-scope ones where the amount is nought. A zero is a value and has to be written; leaving the element out because the figure is nothing is the most common cause of this note. If your export also writes a cac:TaxTotal inside each invoice line, check those too - they are judged by the same rule as the document-level one.
A VAT breakdown states no taxable amount
Reported astaxable_amount_missingthe taxable amount is missing
What is wrong
A cac:TaxSubtotal inside a cac:TaxTotal carries a rate that is not zero but no cbc:TaxableAmount. EN 16931 rule BR-CO-17, which ZATCA runs, multiplies that taxable amount (BT-116) by the rate to arrive at the VAT it expects, so the multiplication has no left-hand side and the rule fails. The taxable amount is read only when the rate is not zero, which is why a breakdown with a zero rate and no taxable amount is not reported here.
What to check in your ERP
cbc:TaxableAmount is the net of everything in this category for this document, and it belongs in the breakdown block itself. It goes missing when the breakdown is derived from the document totals, which hold one tax-exclusive amount for the whole invoice rather than one per category. Build the breakdown from your per-category tax summary instead, and check any cac:TaxTotal written inside an invoice line as well, since the same rule reads those.
VAT breakdown has no rate, or a rate of zero, but a VAT amount that is not zero
Reported asrate_zero_with_nonzero_taxno rate, or a rate of zero, with a VAT amount that is not zero
What is wrong
A cac:TaxSubtotal inside a cac:TaxTotal either carries a cbc:Percent that rounds to zero or carries no cbc:Percent at all, while its cbc:TaxAmount does not round to zero. EN 16931 rule BR-CO-17, which ZATCA runs, treats those two cases the same way: with no rate to multiply by, the only VAT it will accept is nought. A breakdown with no rate at all is legitimate for the out-of-scope category, which BR-48 exempts from carrying one, and this branch is the arithmetic counterpart of that exemption. The rounding here is to the nearest whole unit and not to two decimals, so a VAT amount of 0.49 satisfies the rule while 0.50 does not, and a rate of 0.40 counts as a rate of zero.
What to check in your ERP
Two fields disagree inside one block, so establish which is right. If the supply is taxed, the rate is what is missing or wrong. If it is not taxed, the VAT amount should be nought and something is writing a figure into it - most often a residual from the document VAT total copied into every breakdown, or a rounding adjustment posted against the wrong category. Look also for a rate held as a fraction: a rate written 0.10 rounds to zero here and turns an ordinary taxed breakdown into this note.
VAT breakdown amount does not match the taxable amount times the rate
Reported asnot_taxable_times_ratenot the taxable amount times the rate
What is wrong
In a cac:TaxSubtotal inside a cac:TaxTotal, the cbc:TaxAmount is not what the cbc:TaxableAmount and the cbc:Percent produce. EN 16931 rule BR-CO-17, which ZATCA runs, multiplies the taxable amount (BT-116) by the rate (BT-119) divided by one hundred, rounds to two decimals, and compares. The comparison carries a tolerance of one currency unit and the band is open at both ends: a difference of less than one riyal passes, and a difference of exactly one riyal does not. That is a whole riyal and not a halalah, so a few halalas of per-line rounding are never reported here. The document totals rules BR-CO-14 and BR-CO-15 compare exactly, and a halalah-level discrepancy will surface there instead.
What to check in your ERP
This rule reads every category and every depth, so identify which breakdown is out first: a standard-rated one is usually reported by BR-S-09 as well, and a breakdown reported here alone is either in another category or inside an invoice line. The usual causes are a taxable base that excludes a document-level allowance or charge that was nevertheless taxed, VAT summed from per-line figures that were each rounded first, and a rate held as a fraction rather than as a percentage. Compare the per-category tax summary your engine produces with the block your export writes.
VAT breakdown has no rate
Reported asrate_missingthe rate is missing
What is wrong
A cac:TaxSubtotal in the invoice's VAT breakdown has no cbc:Percent in its cac:TaxCategory, so BR-CO-17 - the VAT amount equals the taxable amount times the rate - has no rate to multiply by, and it fails. ZATCA's current checker applies this to every breakdown, whatever its category. ZATCA flags this as a warning, which does not by itself reject the document.
What to check in your ERP
Write the category's rate into cac:TaxSubtotal/cac:TaxCategory/cbc:Percent on every breakdown, 0 where no VAT is charged. A breakdown built from a VAT code that has no rate configured is the usual cause.
The clause that runs
Quoted from the validation-rule stylesheet ZATCA publishes, at the file and line named below. The executed guard is what the check runs; the reported test is what it prints when it fails, and the two are not always the same.
Executed guard
not($checkTwo)
Reported test
(round(cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent)) = 0 and (round(xs:decimal(cbc:TaxAmount)) = 0)) or (round(cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent)) != 0 and ((abs(xs:decimal(cbc:TaxAmount)) - 1 < round(abs(xs:decimal(cbc:TaxableAmount)) * (cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent) div 100) * 10 * 10) div 100 ) and (abs(xs:decimal(cbc:TaxAmount)) + 1 > round(abs(xs:decimal(cbc:TaxableAmount)) * (cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent) div 100) * 10 * 10) div 100 ))) or (not(exists(cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/xs:decimal(cbc:Percent))) and (round(xs:decimal(cbc:TaxAmount)) = 0))
Evaluated against
/ubl:Invoice/cac:TaxTotal/cac:TaxSubtotal
Source
vendor/zatca/schematron/CEN-EN16931-UBL.xsl:1286
As shipped in ZATCA SDK 3.4.8: the copy quoted here is, byte for byte, the file that release ships. The date in the file name is the name ZATCA gives the file; the rules in it are those of SDK 3.4.8.
A worked example
A synthetic document from the measured test corpus that trips this rule, shown where it differs from one that passed. Mutabiq's report names the class of the value, never the value itself.