Country-compliant tax invoices: UAE FTA vs Saudi ZATCA requirements

A developer's guide to generating tax invoices that satisfy UAE FTA and Saudi ZATCA rules — covering required fields, QR codes, Arabic localization, and e-invoicing formats.

pdfinvoicese-invoicingzatcauae-ftacompliance

Country-compliant tax invoices: UAE FTA vs Saudi ZATCA requirements

If you're shipping a billing system across the Gulf, you'll quickly discover that "just generate a PDF invoice" is a much bigger problem than it sounds. The UAE and Saudi Arabia both enforce VAT-compliant invoices, but the rules differ in important ways — field requirements, QR code formats, Arabic content, and (in KSA) full e-invoicing integration with the tax authority.

This post walks through what backend developers actually need to implement for each.

The shared baseline: VAT invoices

Both countries enforce a 5% (UAE) / 15% (KSA) VAT regime and require invoices in a structured format. At minimum, every tax invoice in both jurisdictions must include:

  • The phrase "Tax Invoice" (in English and/or Arabic)
  • Supplier name, address, and VAT registration number (TRN in UAE, VAT number in KSA)
  • Buyer name and address (and VAT number if registered)
  • Sequential invoice number
  • Issue date (and supply date if different)
  • Line items with description, quantity, unit price
  • Taxable amount, VAT rate, VAT amount, total

That's the easy part. The divergence starts with formatting and submission.

UAE FTA requirements

The UAE Federal Tax Authority (FTA) currently requires a "tax invoice" for B2B and a "simplified tax invoice" for B2C under AED 10,000. Key developer-relevant points:

  • Currency: Amounts must be shown in AED. If you bill in another currency, you must include the AED equivalent and the exchange rate used.
  • Language: Arabic is required by law, though in practice the FTA accepts English invoices. Safe move: include both.
  • TRN format: 15 digits, validated against the FTA portal.
  • Rounding: To the nearest fils (2 decimal places).

The UAE is rolling out a Peppol-based e-invoicing mandate (phased through 2026), but for now most businesses still issue PDF tax invoices. A typical generation flow looks like this:

ts
import { kamy } from "@kamydev/sdk"; const pdf = await kamy.documents.render({ template: "uae-tax-invoice", data: { supplier: { name: "Acme FZ-LLC", trn: "100123456700003", address: "Dubai Internet City, Dubai, UAE", }, buyer: { name: "Client Co", trn: "100987654300003", }, invoiceNumber: "INV-2025-0142", issueDate: "2025-02-14", currency: "AED", lineItems: [ { description: "API subscription", qty: 1, unitPrice: 500.00, vatRate: 0.05 }, ], subtotal: 500.00, vatAmount: 25.00, total: 525.00, }, });

Saudi ZATCA requirements

ZATCA (Zakat, Tax and Customs Authority) is stricter. Saudi Arabia mandates e-invoicing in two phases:

  1. Phase 1 (Generation) — live since Dec 2021. All invoices must be generated electronically in a structured format, include a TLV-encoded QR code, and be in Arabic (English optional, alongside Arabic).
  2. Phase 2 (Integration) — rolling out in waves. Invoices must be submitted as XML (UBL 2.1) to ZATCA's Fatoora platform, cryptographically signed, and cleared (B2B) or reported within 24 hours (B2C).

For Phase 1 simplified invoices (B2C), the QR code is mandatory and must encode the following fields using TLV (Tag-Length-Value) encoding, then Base64:

  • Seller name
  • VAT registration number
  • Timestamp (ISO 8601)
  • Invoice total (with VAT)
  • VAT total

Here's a minimal TLV encoder:

ts
function tlv(tag: number, value: string): Buffer { const valueBuf = Buffer.from(value, "utf8"); return Buffer.concat([ Buffer.from([tag, valueBuf.length]), valueBuf, ]); } function buildZatcaQr(data: { sellerName: string; vatNumber: string; timestamp: string; invoiceTotal: string; vatTotal: string; }): string { const buf = Buffer.concat([ tlv(1, data.sellerName), tlv(2, data.vatNumber), tlv(3, data.timestamp), tlv(4, data.invoiceTotal), tlv(5, data.vatTotal), ]); return buf.toString("base64"); }

That Base64 string is then rendered as a QR code on the invoice PDF. Phase 2 adds tags 6–8 for the invoice hash, ECDSA signature, and public key — these come from ZATCA's compliance API after onboarding.

Generating both from one system

The pragmatic approach: keep one canonical invoice schema and branch on the issuing country to pick the right template, language, and QR logic.

ts
async function renderInvoice(invoice: Invoice) { const isKSA = invoice.country === "SA"; const qrCode = isKSA ? buildZatcaQr({ sellerName: invoice.supplier.name, vatNumber: invoice.supplier.vatNumber, timestamp: invoice.issueDate, invoiceTotal: invoice.total.toFixed(2),