Most ZATCA rejections are not integration bugs. They are data problems that were always in your database and never had to be correct before. A VAT rate stored as the string 15%, a customer with no country code, an item line whose name is empty because the description carries the meaning — none of that broke anything until an authority started validating it on every invoice.
ZATCA returns one of three outcomes per document: Accepted, Accepted with warnings, or Rejected. There are more than a hundred possible combinations of warning messages and more than a hundred of error messages. This article is about what separates the two, and which of your fields are most likely to produce them.
Engineering context, not tax advice.
The field codes are not ZATCA's invention
The first thing that helps is recognising the vocabulary. ZATCA's validation messages cite codes like BT-1, BG-5, BT-153. These are EN 16931 business terms — the European standard for electronic invoicing. BT- is a business term (a single field), BG- is a business group (a block of related fields).
Alongside them you will see `KSA-` codes, which are the Saudi extensions. KSA-13 is the previous invoice hash. These are the fields that exist because ZATCA requires them and no European invoice does.
This matters practically: when you hit a validation message you do not recognise, the BT-/BG- codes are documented in a public international standard, not only in ZATCA's annexes. And it explains why the invoice model feels more prescriptive than your own — you are mapping onto someone else's semantic model, not designing one.
Rejected versus accepted-with-warnings
The distinction decides your error handling, so be precise about it.
Rejected means at least one fatal error. The document is not valid, it was not accepted, and — for a standard invoice — it has not been cleared, so it cannot go to the buyer. Examples ZATCA gives:
- VAT rates must be 0.00 to 100.00, maximum two decimals, numerals only — the
%symbol is not allowed - An invoice shall have an invoice issue date (
BT-2) - An invoice shall have an invoice type code (
BT-3) - Base quantity (
BT-149) must be a positive number
Accepted with warnings means no fatal errors, but at least one problem that stops the document being considered compliant — while still being accepted. Examples:
- An invoice shall have an invoice number (
BT-1) - An invoice shall contain the seller name (
BT-27) - An invoice shall contain the seller postal address (
BG-5) - The seller postal address shall contain a seller country code (
BT-40) - Each invoice line (
BG-25) shall contain the item name (BT-153) - The allowed maximum number of decimals for the paid amount (
BT-113) is 2 - Allowance amount must equal base amount × percentage ÷ 100 where both exist
- Previous invoice hash (`KSA-13`) must exist in an invoice
Two things are worth sitting with. First, that list is full of fields you would call mandatory — seller name, invoice number — arriving as warnings. Second, ZATCA states that warnings are temporarily accepted and might become rejections in the future, and that continuous warnings by taxpayers will be investigated, with taxpayers "educated / audited accordingly".
So the correct engineering posture is: treat warnings as defects with a deadline you have not been told. A system that logs 202 Accepted with warnings as success will accumulate thousands of non-compliant documents and discover the problem when the classification changes.
Model it as a first-class state. Surface a count of accepted-with-warnings documents on whatever dashboard your finance team actually looks at, grouped by message. They cluster hard — one missing field in your company profile will produce the same warning on every invoice you have ever issued.
The fields that break real integrations
From the shape of the validation rules, the recurring offenders:
Your own seller details. The seller name, address block and country code come from your configuration, not from the transaction — so if they are wrong, they are wrong on every invoice, and they are the cheapest possible thing to fix before you start. Check the address block has every component ZATCA expects, not just the ones your invoice template prints.
VAT rates as formatted strings. Anywhere a rate reaches the XML as 15%, 0.15, or 15.000 it is a rejection. Rates go in as a number, 0.00–100.00, two decimals maximum. This is usually a presentation layer leaking into a data layer — the same string that renders on the PDF being reused in the XML.
Rounding and decimals. Monetary amounts cap at two decimals in the places ZATCA checks, and allowance arithmetic must actually reconcile: amount = base × percentage ÷ 100. If you store a discount percentage and a discount amount independently and let the UI round one of them, they will eventually disagree by a halala and the invoice will be flagged. Compute one from the other at serialisation.
Empty item names. Systems where the meaningful text lives in a description or a free-text note frequently emit lines with a blank BT-153. Every line needs a name.
Missing previous invoice hash. KSA-13 must exist. If it is absent, something in your chaining is broken — most often the first invoice after a deployment, a database restore, or a new device that started its chain from nothing.
Credit notes with no parent. Issuing a credit note without reference to the original invoice violates Article 54 of the KSA VAT Regulations. If your system allows a standalone credit memo, that path needs closing or re-shaping.
Out-of-scope supplies. Where an invoice covers only supplies not subject to VAT, no tax invoice is required. But if the same billing solution issues those alongside standard-rated invoices, ZATCA's guidance is that there must be no gaps in the counter value or in the previous-invoice-hash sequence. In other words: excluding a document from submission does not excuse it from the chain.
Fix the data before you fix the code
The ordering that saves the most time:
1. Export a month of historical invoices and validate them offline against the implementation standard before you write any submission code. You will find the systematic problems — the missing country code, the percentage strings — in an afternoon.
2. Fix the company-level data first. One record, fixes thousands of future warnings.
3. Then the catalogue, then the customer master.
4. Then integrate, in the sandbox, with the shapes you know are awkward: exports, zero-rated lines, advance payments, credit notes, multi-line discounts.
5. Then go live, with accepted-with-warnings visible from day one.
Teams that integrate first spend the same effort, but spread over months, in production, with a queue backing up.
A note on size and duplicates
Two operational limits that surface late. Invoices cap at roughly 10MB — exceed it and the API returns 413. Real invoices reach this only when something is embedded that should be a reference instead.
And the same document cannot be submitted twice — though a duplicate is not rejected at submission time. That combination is worth reading carefully: your retry logic can create duplicates that appear to succeed. Make submissions idempotent on your side, keyed on the document's UUID, rather than relying on ZATCA to reject the second attempt.
For how these documents get stamped and chained, see cryptographic stamping, CSID and the QR code and the invoice counter and hash chain. For the wider obligation, our ZATCA Phase 2 integration guide.
Frequently asked questions
What do BT and BG codes mean?
They are EN 16931 business terms and business groups — the European e-invoicing semantic standard that ZATCA's XML builds on. KSA- codes are the Saudi-specific extensions.
Is "accepted with warnings" compliant?
No. It is accepted but not considered a compliant document. ZATCA says warnings are temporarily accepted and may become rejections later, and that repeated warnings can lead to investigation.
Why is a missing seller name only a warning?
That is how ZATCA classifies it. Do not read the classification as a judgement about importance — read it as a grace period.
Can I submit `15%` as a VAT rate?
No. Numerals only, 0.00–100.00, maximum two decimals. The percentage symbol is not accepted.
What do I do about an invoice that only contains non-taxable supplies?
No tax invoice is required where the transaction covers solely supplies not subject to VAT. But if the same solution also issues standard-rated invoices, keep the counter and hash sequence gap-free.
How large can an invoice be?
Around 10MB. Beyond that the API returns 413, payload too large.
If I resubmit after a rejection, does the invoice keep its number?
No. The rejected document keeps its own hash and counter value, and you submit a corrected document as a new one.
Sources
- ZATCA — Detailed Guidelines for E-Invoicing (PDF) — response classifications, warning and error examples, size limits
- ZATCA — E-Invoicing standards and specifications
- EN 16931 and the European eInvoicing standard — European Commission