BR-KSA-CL-02 Rejects the invoice

Mixed currency in the document — a currencyID attribute does not match the invoice currency

Every amount in the XML carries an attribute naming its currency, and all of them must repeat the invoice currency. One line, or one tax amount, carrying something else fails the whole document even when the totals are right. The single exception is the VAT total in the accounting currency.

The message text as the Authority sends it in English, unedited

All currencyID attributes (BT-5) must have the same value as the invoice currency code (BT-5), except for the invoice total VAT amount in accounting currency (BT-111).

What does this mean?

Nearly every monetary element in UBL is written with a currencyID attribute: the line net, the unit price, the line VAT amount, each tax subtotal, the tax total, and the seven amounts in cac:LegalMonetaryTotal. This rule says all of them, without exception, carry the same value as cbc:DocumentCurrencyCode.

And the exception is one, named in the message: the invoice total VAT amount in accounting currency (BT-111). That amount carries the accounting currency because that is genuinely what it is denominated in. The rule has to name an exception at all because the accounting currency is pinned to SAR by BR-KSA-EN16931-02 while the invoice currency is free — so a foreign-currency invoice necessarily carries exactly one amount in a currency the rest of it does not use, and this is it.

Four rules surround the currency, and keeping them in order saves you rounds: BR-KSA-CL-01 asks whether the invoice currency is a valid code, this one asks whether the whole document repeats it, BR-KSA-68 asks whether the tax currency exists, and BR-KSA-EN16931-02 asks whether its value is SAR. Valid, consistent, present, correct — four questions across two fields.

The mechanism that most often produces this code is that the value has two doors rather than one: the header element is read from the invoice record, while the currencyID attribute is written from a default in the serializer or the amount formatter. The library ZATCA Tools uses does exactly this: every currencyID attribute in the file is written from a single static variable set when the generator is called, while the header comes from the payload. Change one and forget the other and you produce a document that passes CL-01 and fails here — and that reads as perfectly correct when you look at it.

Why does it happen?

Ordered from the most common to the least — your cause is most likely the first or the second.

  • A hand-assembled template where the header was changed to the customer's currency and the line and tax attributes were left at their template value.
  • A system with a per-line currency field — multi-currency price lists — so each line leaves in whatever currency its price was stored in.
  • Two sources for one value, as above: the header from the invoice record, the attribute from a library default.
  • A block copied from an older document in another currency: an added shipping line, a rounding line, a line on a credit note.
  • An empty attribute on one of the amounts. Empty is not SAR — though since we read the rule text rather than the test, we cannot promise whether that lands here or in schema validation.
  • Lower case, or a symbol, in an attribute whose header got the format right: the comparison here is a string comparison, not a comparison of meaning.

How to fix it

3 steps, then send the invoice again.

  1. 1
    Search your file for currencyID

    One text search answers it: every occurrence of the attribute should read the same three letters as cbc:DocumentCurrencyCode. There are more of them than you would first guess — at least twice on every line, once on every tax subtotal, once on the tax total, and on seven amounts in the document totals. A single odd one is enough to fail.

  2. 2
    Give the value one source

    One constant on the document, read by the header element and by every attribute. If your library sets the attribute from somewhere independent of the payload — and that is common — set both doors, and do not settle for the one in the header just because it is the visible one.

  3. 3
    And leave BT-111 alone

    The VAT total in the accounting currency is the stated exception, and it carries SAR. Unifying it with the rest on a foreign-currency invoice fixes this code and breaks BR-KSA-97, which expects the two VAT totals to differ once the invoice currency is not SAR. The structure of that second block is itself the subject of BR-KSA-EN16931-09.

Once corrected, send the invoice again. A rejected invoice was never recorded with the Authority, so it needs no credit note — send the corrected invoice itself.

Does ZATCA Tools prevent it?

Yes, and it is the shape of the product that prevents it. The payload builder writes SAR as a literal for the invoice currency, and the XML generator is called in InvoiceSubmitter with its default currency, which is also SAR — so the header and every currencyID attribute come out as the same single string. And there is no per-line currency in the product at all: a line is a name, a quantity, a price and a tax category, and nothing else. The observation worth stating in full: those two SARs are set in two independent places and happen to agree because neither is configurable — not because anything on the path compares one against the other.

Related codes

Frequently asked questions

Only one line is wrong. Why is the whole invoice rejected? +
Because the document is signed and reported as a single object, and because the rule says all currencyID attributes. There is no partial acceptance: the file is accepted whole or rejected whole, so one odd line takes an otherwise perfectly correct invoice down with it.
My invoice is in euros. Which amounts stay in euros? +
All of them but one: the invoice total VAT amount in accounting currency (BT-111) stays in riyals, because the accounting currency is always SAR. Everything else — the lines, the prices, the line VAT amounts, the subtotals and the seven document totals — is in euros.
I fixed the header and it is still being rejected. +
Because the header is the field BR-KSA-CL-01 checks, while this rule reads the attributes on the amounts. In most systems those are written by two different places, so find whatever writes the attribute and change that too.
You fixed this one — now avoid the next

ZATCA Tools builds the signature, the PIH, the counter and the encoding for you, and validates your data before it is sent. Start free, with no credit card.

Start for free

← All ZATCA error codes