BR-S-09Standard-rated VAT breakdown states no VAT amount
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-S-09.
Five checks a month on the Free plan. The file is checked in memory and is never stored.
The message ZATCA prints
[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.
Also cited in this report
BR-CO-17
[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.
What Mutabiq reports
One entry for each way this rule can be reported.
Reported as
What kind of value was found — empty, not_15_digits and the like — never the value itself.
Standard-rated VAT breakdown states no VAT amount
Reported astax_amount_missingthe VAT amount is missing
What is wrong
A cac:TaxSubtotal sitting directly under the invoice's own cac:TaxTotal has a cac:TaxCategory coded S - standard rated - under the VAT tax scheme, and the subtotal carries no cbc:TaxAmount. EN 16931 rule BR-S-09, which ZATCA runs, requires the VAT category tax amount (BT-117) to equal the VAT category taxable amount (BT-116) multiplied by the rate (BT-119) and divided by one hundred. With no amount written there is nothing to compare and the rule fails. It reads only the breakdown directly under the document root; a cac:TaxSubtotal written inside an invoice line is outside it and is judged by BR-CO-17 instead.
What to check in your ERP
The VAT breakdown block must carry three things together - cbc:TaxableAmount, cbc:TaxAmount, and the category with its cbc:Percent - and cbc:TaxAmount is the one most often dropped, because the same figure already appears as the total in the parent cac:TaxTotal and looks redundant. It is not: the total and the per-category amount are different fields and both are read. Check whether your export writes the breakdown from a per-category tax summary or rebuilds it from the lines; a rebuild that finds no rows for a category can emit the block with the taxable amount and nothing else.
Standard-rated VAT breakdown states no taxable amount
Reported astaxable_amount_missingthe taxable amount is missing
What is wrong
A cac:TaxSubtotal directly under the invoice's own cac:TaxTotal has a cac:TaxCategory coded S - standard rated - under the VAT tax scheme, and the subtotal carries no cbc:TaxableAmount. EN 16931 rule BR-S-09, which ZATCA runs, multiplies that taxable amount (BT-116) by the rate to arrive at the VAT it expects, so with the element absent the multiplication has no left-hand side and the rule fails whatever the VAT amount says.
What to check in your ERP
cbc:TaxableAmount is the net of the lines in this category after allowances and charges, and it is a field of the breakdown itself rather than a copy of any document total. It goes missing most often when the breakdown is assembled from the document totals block, where a single tax-exclusive amount stands in for every category and nothing per-category exists to write. Build the breakdown from the per-category tax summary your tax engine already produces, and write one cac:TaxSubtotal for each category in it.
Standard-rated VAT breakdown states no VAT rate
Reported asrate_missingthe rate is missing
What is wrong
A cac:TaxSubtotal directly under the invoice's own cac:TaxTotal has a cac:TaxCategory coded S - standard rated - under the VAT tax scheme, and that category carries no cbc:Percent. EN 16931 rule BR-S-09, which ZATCA runs, needs the rate (BT-119) to work out the VAT it expects from the taxable amount, and an absent element is an empty sequence: every comparison made against it is false, so the rule fails rather than passing quietly. A breakdown with no rate is also reported by BR-48, which requires one on every breakdown except an out-of-scope one.
What to check in your ERP
The rate belongs in cac:TaxSubtotal/cac:TaxCategory/cbc:Percent, in the same block as the category code cbc:ID and the cac:TaxScheme. A common cause is an export that writes the category code and the tax scheme from a lookup table while taking the rate from the invoice line, then omits it when the breakdown is written before the lines are walked. Another is a tax code configured with an empty rate rather than a number for a category the business treats as fully taxed.
Standard-rated VAT 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 directly under the invoice's own cac:TaxTotal, coded S - standard rated - under the VAT tax scheme, the cbc:TaxAmount is not what the cbc:TaxableAmount and the cbc:Percent produce. EN 16931 rule BR-S-09, 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. If you are reconciling to the halalah, this is not the rule that will tell you about it - the document totals rules BR-CO-14 and BR-CO-15 compare exactly and are the ones that will.
What to check in your ERP
Three figures in one block disagree, so find which one is out rather than adjusting the VAT to fit. The frequent cause is a taxable amount that excludes a document-level allowance or charge which was nevertheless taxed, so the VAT was worked out on one base while the breakdown reports another. Another is a breakdown assembled per line and summed, where each line's VAT was rounded before the sum: the check adds first and rounds once. A third is a rate held as a fraction rather than as a percentage, where a rate written 0.15 makes the expected VAT a hundred times too small. Compare the tax summary your engine produces with the block your export writes, field by field.
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
$checkOne or 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))
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.