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-CL-18.
Five checks a month on the Free plan. The file is checked in memory and is never stored.
The message ZATCA prints
[BR-CL-18]-Invoice tax categories MUST be coded using UNCL5305 code list.
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.
Invoice line's VAT category code is empty
Reported asemptypresent but empty
What is wrong
An invoice line carries a cac:Item/cac:ClassifiedTaxCategory/cbc:ID element with no text in it. EN 16931 rule BR-CL-18, which ZATCA runs, requires the line's VAT category code (BT-151) to be one of the codes on its list, and an empty element is on no list. Note that this rule reads the line item's category code and not the VAT breakdown's: the breakdown and the allowance blocks carry cac:TaxCategory, which this rule does not look at, and nothing in the published rule files confines those codes to a list at all.
What to check in your ERP
The element is being written and the value is not, which normally means the export emits the classified tax category block for every line while the code itself comes from a field that is only filled when the item has a tax code assigned. Look for items set up without a tax code - newly created items, free-of-charge lines, text-only or comment lines that were exported as real invoice lines - and for a mapping table that returns nothing rather than a default when a lookup misses.
Invoice line's VAT category code contains a space
Reported ascontains_a_spacecontains a space
What is wrong
An invoice line's cac:Item/cac:ClassifiedTaxCategory/cbc:ID contains a space inside it. EN 16931 rule BR-CL-18, which ZATCA runs, is written as two separate conditions and this is the second one failing: the value must be on the code list, and it must contain no space. The two are not the same test, which is why this is reported separately from a code that is simply not on the list. ZATCA tests membership by looking for the value inside the text ' AE L M E S Z G O K B ', which is the list written out with spaces between the codes - so a value made of two codes with a space between them is genuinely found inside that text and passes the membership half. The no-space condition is the only thing that rejects it.
What to check in your ERP
The value is a concatenation of something. The usual causes are two codes joined because the item carries more than one tax code, a code followed by a descriptive word from a lookup table that returns the label as well as the key, and a fixed-width field padded with spaces where the padding was not trimmed on export. Trim the value and map the item to a single code. This rule reads the invoice line's code, so start at the item's tax code rather than at the VAT breakdown.
Invoice line's VAT category code is not one ZATCA accepts
Reported asnot_in_uncl5305not a VAT category code ZATCA accepts
What is wrong
An invoice line's cac:Item/cac:ClassifiedTaxCategory/cbc:ID holds a value that is not on the list ZATCA checks against. EN 16931 rule BR-CL-18, which ZATCA runs, confines the line's VAT category code (BT-151) to ten codes, written in the rule files as AE L M E S Z G O K B. That is a subset of the wider UNCL5305 code list, so a code that is valid in UNCL5305 but absent from those ten still fails. The comparison is exact and case-sensitive: a lower-case letter is not the same value as an upper-case one. The four that matter on a Saudi invoice are S for standard rated, Z for zero-rated, E for exempt and O for outside the scope of VAT.
What to check in your ERP
Look at the mapping between your VAT code table and the category letter. The frequent causes are a system code exported as-is - a tax code name, a rate, or an internal identifier - rather than being mapped to a letter, a lower-case letter written where an upper-case one is required, and a code correct for another country's e-invoicing profile but not on the Saudi list. This rule reads the invoice line's code specifically, so check the item and its tax code rather than the breakdown block.
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(( ( not(contains(normalize-space(.),' ')) and contains( ' AE L M E S Z G O K B ',concat(' ',normalize-space(.),' ') ) ) ))
Reported test
( ( not(contains(normalize-space(.),' ')) and contains( ' AE L M E S Z G O K B ',concat(' ',normalize-space(.),' ') ) ) )
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.