BR-Z-08Zero-rated VAT breakdown with nothing zero-rated on the invoice
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-Z-08.
Five checks a month on the Free plan. The file is checked in memory and is never stored.
The message ZATCA prints
[BR-Z-08]-In a VAT breakdown (BG-23) where the VAT category code (BT-118) is " Zero rated" the VAT category taxable amount (BT-116) shall equal the sum of Invoice line net amounts (BT-131) minus the sum of Document level allowance amounts (BT-92) plus the sum of Document level charge amounts (BT-99) where the VAT category codes (BT-151, BT-95, BT-102) are "Zero Rated".
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.
Zero-rated VAT breakdown with nothing zero-rated on the invoice
Reported ascategory_unuseda breakdown for a category nothing on the invoice uses
What is wrong
The invoice's VAT breakdown has a cac:TaxSubtotal coded Z - zero-rated - under the VAT scheme, but no line item and no document-level allowance or charge is coded Z under the VAT scheme. EN 16931 rule BR-Z-08, which ZATCA's checker runs since SDK 3.4.8, checks that the zero-rated taxable amount equals the zero-rated lines less allowances plus charges, and reports a breakdown with nothing to add up. ZATCA flags this as a warning, which does not by itself reject the document.
What to check in your ERP
Either the breakdown should not be there, or the lines it summarises are coded with another category. Check that the category code on each line item (cac:ClassifiedTaxCategory/cbc:ID), and its tax scheme, match the breakdown the line is totalled into; a breakdown left at Z while the line was recoded as exempt is the usual cause.
Zero-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 is coded Z - zero-rated - under the VAT tax scheme and carries no cbc:TaxableAmount. EN 16931 rule BR-Z-08, which ZATCA's checker runs since SDK 3.4.8, compares that amount against the sum of the zero-rated line amounts, plus zero-rated charges, minus zero-rated allowances. An absent element is an empty sequence in XPath: arithmetic over it yields the empty sequence and every comparison against it is false, so the rule fails on absence rather than passing over it, and the amount has to be written out even when it is nought. ZATCA flags this as a warning, which does not by itself reject the document. Two things about the sums, because they are narrower than the words suggest: they are taken from the direct children of the document root only, so an allowance attached to an invoice line is not in them; and the contributing lines, charges and allowances are picked up by the category letter alone, with no test that the tax scheme is VAT.
What to check in your ERP
The usual cause is an export that suppresses amount elements whose value is nought, and the zero-rated group is one it drops, because a breakdown rebuilt from posted tax lines has no tax line for a supply taxed at nought. Write cbc:TaxableAmount explicitly, formatted to two decimals and carrying the same currencyID attribute as the other amounts. If the group has elements but no figures, look at the per-category tax summary rather than at the export template. Expect the same suppression to be affecting your exempt and out-of-scope breakdowns, so fix it once.
Zero-rated VAT breakdown's taxable amount does not match its lines
Reported astaxable_amount_mismatchthe taxable amount does not match the lines
What is wrong
A cac:TaxSubtotal directly under the invoice's own cac:TaxTotal is coded Z - zero-rated - under the VAT tax scheme, and its cbc:TaxableAmount does not equal the sum of the zero-rated line amounts, plus zero-rated charges, minus zero-rated allowances. EN 16931 rule BR-Z-08, which ZATCA's checker runs since SDK 3.4.8, compares the two exactly: there is no tolerance and no rounding step, unlike BR-S-09 and BR-CO-17, which allow a band of one currency unit around a category's VAT amount. A difference of one halalah fails this rule. ZATCA flags it as a warning, which does not by itself reject the document. The test expression ZATCA's stylesheet records for this rule names the standard-rated category, by a copying slip; the check ZATCA runs adds up the zero-rated ones, and this note follows the check. Two things about the sums, because they are narrower than the words suggest: they are taken from the direct children of the document root only, so an allowance attached to an invoice line is not in them; and the contributing lines, charges and allowances are picked up by the category letter alone, with no test that the tax scheme is VAT.
What to check in your ERP
Rebuild the figure by hand from the exported file: add up cbc:LineExtensionAmount on every line whose category is Z, add the document-level charges coded Z, subtract the document-level allowances coded Z, and compare the result with the breakdown's cbc:TaxableAmount. The frequent causes are the zero-rated taxable amount taken from the document's tax-exclusive total instead of from the zero-rated group, a document-level allowance whose own cac:TaxCategory carries a different letter so the rule does not subtract it, a discount applied to the breakdown but not to the line's cbc:LineExtensionAmount, and rounding applied to the breakdown independently of the lines. Start at the per-category tax summary, which is where a breakdown and its lines stop agreeing.
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
count(/*/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory[normalize-space(cbc:ID) = 'Z'][cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT'])> 0 and not(exists(../../../cac:AllowanceCharge/cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'Z']) or exists(../../../cac:InvoiceLine[cac:Item/cac:ClassifiedTaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'Z']])) or not( (xs:decimal(../cbc:TaxableAmount) =(sum(../../../cac:InvoiceLine[cac:Item/cac:ClassifiedTaxCategory/normalize-space(cbc:ID)='Z']/xs:decimal(cbc:LineExtensionAmount)) + sum(../../../cac:AllowanceCharge[cbc:ChargeIndicator=true()][cac:TaxCategory/normalize-space(cbc:ID)='Z']/xs:decimal(cbc:Amount)) - sum(../../../cac:AllowanceCharge[cbc:ChargeIndicator=false()][cac:TaxCategory/normalize-space(cbc:ID)='Z']/xs:decimal(cbc:Amount)) )))
Reported test
count(/*/cac:TaxTotal/cac:TaxSubtotal/cac:TaxCategory[normalize-space(cbc:ID) = 'Z'][cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT'])> 0 and (exists(../../../cac:AllowanceCharge/cac:TaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'Z']) or exists(../../../cac:InvoiceLine[cac:Item/cac:ClassifiedTaxCategory[cac:TaxScheme/normalize-space(upper-case(cbc:ID))='VAT']/cbc:ID[normalize-space(.) = 'Z']])) or ((xs:decimal(../cbc:TaxableAmount) =(sum(../../../cac:InvoiceLine[cac:Item/cac:ClassifiedTaxCategory/normalize-space(cbc:ID)='S']/xs:decimal(cbc:LineExtensionAmount)) + sum(../../../cac:AllowanceCharge[cbc:ChargeIndicator=true()][cac:TaxCategory/normalize-space(cbc:ID)='S']/xs:decimal(cbc:Amount)) - sum(../../../cac:AllowanceCharge[cbc:ChargeIndicator=false()][cac:TaxCategory/normalize-space(cbc:ID)='S']/xs:decimal(cbc:Amount)) )))
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.
Document
bad-114-z-taxable-amount-off-by-hundredth.xml
Mutabiq's verdict
Passed
Reported as
taxable_amount_mismatchthe taxable amount does not match the lines