Advanced · 3 of 3

Invoice fields

create_invoice() takes a dict. Only type, number and items are needed for a receipt; the rest is there when the invoice needs it. A field it does not know is refused, with a hint when it looks like a typo — a misspelt discount can never be silently left off a signed invoice.

Invoice

  • type string required
    simplified (B2C, a receipt — reported) or standard (B2B tax invoice — cleared).
  • number string required
    Your invoice number, up to 127 characters.
  • items list required
    What was sold.
    • name string required
      Item name.
    • quantity number required
      Greater than zero; fractions allowed.
    • unit_price number required
      Before VAT — or with VAT when prices_include_vat is true.
    • discount / discount_type number / string
      An item discount, as an amount (default) or a percent.
    • tax_category string default S
      S 15%, Z zero-rated, E exempt, O out of scope.
    • tax_reason_code string
      ZATCA's VATEX-SA-… reason, for Z, E and O.
    • unit string default PCE
      Unit of measure code, e.g. HUR, MTR.
    • description string
      Printed under the name on the PDF. Not part of the XML.
  • kind string default invoice
    invoice, credit (credit note) or debit (debit note).
  • date / time string
    YYYY-MM-DD / HH:MM:SS, Riyadh time. Now by default; never in the future.
  • prices_include_vat bool default false
    True when unit prices and amount discounts are what the customer pays.
  • discount number
    A discount on the whole invoice.
  • buyer dict
    Required on a standard invoice: name; vat_number, or id with id_scheme (CRN, NAT, IQA, 700, PAS…); address with street, building_number, district, city, postal_code, country.
  • original_invoice / reason string
    Required on credit and debit notes: the invoice adjusted, and why.
  • delivery_date string
    The supply date, when it differs from the issue date.
  • uuid string
    Generated when left out.
  • icv / pih int / string
    Only to keep the chain yourself — see the invoice chain.

Invoices and notes

Simplified invoices are receipts for consumers: hand them over at the sale, and submit() reports them within 24 hours. Standard invoices are tax invoices between businesses: submit() clears them first, and the buyer gets ZATCA's cleared copy.

A credit note ("kind": "credit") reduces an invoice, a debit note ("kind": "debit") increases it. Each names original_invoice and gives a reason, and is reported or cleared like the invoice it adjusts.

b2b_and_credit_note.py
from zatca_tools import Zatca

zatca = Zatca("sandbox", design={"accent": "#0F766E", "footer": "Payment within 30 days. Thank you for your business."})
zatca.onboard()

buyer = {
    "name": "Buyer Trading Co.",
    "vat_number": "399999999800003",
    "address": {"street": "Prince Sultan Street", "building_number": "8228", "district": "Al Rawdah", "city": "Jeddah", "postal_code": "23435"},
}

invoice = zatca.create_invoice({
    "type": "standard",
    "number": "INV-2001",
    "buyer": buyer,
    "items": [
        {"name": "Consulting (hours)", "quantity": 10, "unit_price": 350},
        {"name": "Medical devices", "quantity": 2, "unit_price": 1200, "tax_category": "Z", "tax_reason_code": "VATEX-SA-35"},
    ],
})
cleared = zatca.submit(invoice)
print("Invoice:", cleared.status)
if cleared.success:
    cleared.save_xml("INV-2001.xml")  # ZATCA's stamped copy: the one the buyer gets
    cleared.save_pdf("INV-2001.pdf")

note = zatca.create_invoice({
    "type": "standard",
    "kind": "credit",
    "number": "CRN-2001",
    "original_invoice": "INV-2001",
    "reason": "Two hours not delivered",
    "buyer": buyer,
    "items": [{"name": "Consulting (hours)", "quantity": 2, "unit_price": 350}],
})
result = zatca.submit(note)
print("Credit note:", result.status)
if result.success:
    result.save_pdf("CRN-2001.pdf")
for warning in result.warnings:
    print("warning:", warning.code, warning.help_url)

VAT treatments and discounts

One invoice can mix treatments — 15% shampoo beside zero-rated medicine and an out-of-scope delivery tip. ZATCA expects a subtotal per treatment and exemption reason; the SDK builds them, and rounds the way ZATCA checks: half up, once, on the final figure — not item by item.

With prices_include_vat, shelf prices are converted at each item's own rate and the invoice settles at exactly what the customer pays: a 400.00 repair with 40.00 off is invoiced at 360.00. A discount on the whole invoice is spread into the items, so the printed items add up to the signed totals; the few halalas an item cannot absorb are declared once as an allowance.

A zero-rated or exempt item without a reason is accepted by ZATCA with a warning. The SDK never guesses one; it tells you in invoice.warnings.

The invoice chain (ICV and PIH)

Each invoice a system signs carries a counter, the ICV (+1 every time), and the hash of the invoice before it, the PIH. ZATCA checks that they link. The client moves both forward by itself: zatca.chain is the counter and hash of the last invoice signed.

  • It moves for every invoice signed, whatever ZATCA answers — a rejected invoice still took its place in the chain.
  • The counter counts credit and debit notes too, so it runs ahead of an invoice-number series once a note is issued. That is expected.
  • Outside the sandbox a client refuses to guess where a chain stands: pass chain= (the saved dict, or "new"). Keeping the chain.

To keep the chain entirely yourself, pass icv and pih on each invoice; the client still records the last one in zatca.chain.

chain kept by hand
invoice = zatca.create_invoice({
    "type": "simplified",
    "number": "INV-0042",
    "icv": 42,                                   # this unit's counter
    "pih": previous_hash,                        # the hash of invoice 41
    "items": [...],
})

Certificates and keys (CSR and CSID)

onboard() is these four steps in one call. To run them yourself — to store the key in your own vault between steps, or to onboard from a separate tool — use zatca.onboarding():

  1. CSR — generate_csr(), local: a new secp256k1 key pair and the certificate request ZATCA expects.
  2. Compliance CSID — the request and the OTP go to ZATCA; a test certificate comes back.
  3. Compliance checks — an invoice, a credit note and a debit note for each kind the system issues, signed with that test certificate.
  4. Production CSID — the certificate that signs real invoices.

The sandbox signs with ZATCA's shared sample certificate, which matches no one's key; the SDK allows that mismatch in the sandbox only.

step by step
from zatca_tools import CsrRequest, Zatca

zatca = Zatca("simulation", seller=SELLER)
onboarding = zatca.onboarding()

keys = onboarding.generate_csr(CsrRequest(          # local: key pair + CSR
    vat_number=SELLER["vat_number"],
    organization_name=SELLER["name"],
    organization_unit="Riyadh Branch",            # VAT group: the member's TIN
    location="RRRD2929",
    industry="Retail",
    invoice_types="1100",
))
compliance = onboarding.request_compliance_csid(keys, otp="123456")
report = onboarding.run_compliance_checks(compliance.credentials(keys.private_key))
assert report.passed, report.to_dict()
production = onboarding.request_production_csid(compliance)
credentials = production.credentials(keys.private_key)

XML, signature, hash and QR code

XML — UBL 2.1 with ZATCA's extensions. Signature — XAdES, ECDSA over secp256k1 with SHA-256. Hash — SHA-256 of the canonical XML without the signature parts; ZATCA recomputes it, and whitespace matters, which is why the layout is fixed and tested byte for byte against the ZATCA Tools platform. QR code — the seller, VAT number, time, total, VAT, hash, signature and public key, plus the certificate's signature on simplified invoices.

zatca_tools.decode_qr(invoice.qr) reads a QR code back into its fields.

Printed invoices (PDF/A-3)

save_pdf() writes the invoice as PDF/A-3 with its XML embedded as an associated file — the format ZATCA's guidelines describe for sharing an e-invoice, readable by people and systems from one file. veraPDF, the reference PDF/A checker, passes it as PDF/A-3b; the test suite runs veraPDF when it is installed.

It is drawn from the XML itself — for a cleared invoice, from ZATCA's cleared copy — so the printed figures cannot disagree with the signed ones. Arabic first, English beside it: the title, number, date (and time on a receipt), seller and buyer with their VAT numbers, each item with its price before VAT, its VAT and its total with VAT, the exemption's reason, the totals and the QR code. The rate sits in the VAT column's header, or gets a column of its own when rates differ; an invoice discount shows the total before it and the amount off. It is the layout the ZATCA Tools platform prints.

Yours to brand: the logo, the accent colour, a footer of your own, and the small "ZATCA Tools" mark in the page footer, which the default design shows and zatca_tools_mark=False removes. The layout itself is fixed, so a design can never drop or move what a tax invoice must show.

A standard invoice is printed from its result, once cleared; calling save_pdf() on it before clearance is refused.

pdf.py
from zatca_tools import InvoiceDesign, Zatca

zatca = Zatca("production", seller=SELLER, credentials=CREDENTIALS, chain=CHAIN, design=InvoiceDesign(
    logo="logo.png",                     # PNG, JPEG or WebP; transparent is fine
    accent="#0F766E",                    # the table header and the total
    footer="Bank: SA00 0000 0000 0000 0000 0000\nReturns within 14 days.",
    zatca_tools_mark=True,               # the small mark in the page footer
))

receipt = zatca.create_invoice({...})    # simplified: print it at the sale
receipt.save_pdf("INV-1001.pdf")

result = zatca.submit(b2b_invoice)       # standard: print it once cleared
if result.success:
    result.save_pdf("INV-2001.pdf")      # embeds ZATCA's cleared copy

Results

Every result has to_dict() and to_json(); XML stays out unless asked for (to_dict(include_xml=True)). Each sample below says where it came from: ZATCA's sandbox, the SDK on its own, or the SDK with ZATCA replaced by a test double for the cases ZATCA cannot be made to produce on demand. QR codes are shortened.

create_invoice() — nothing sent
{
  "stage": "created",
  "invoice": {
    "type": "simplified",
    "kind": "invoice",
    "number": "INV-1001",
    "uuid": "98891a92-befc-44a7-bc62-5bd5bca85477",
    "icv": 1,
    "pih": "NWZlY2ViNjZmZmM4NmYzOGQ5NTI3ODZjNmQ2OTZjNzljMmRiYzIzOWRkNGU5MWI0NjcyOWQ3M2EyN2ZiNTdlOQ==",
    "hash": "cOLtpOe3RotM6GANvNBwZyy9eOupuMQWB/DOsPL3Te4=",
    "date": "2026-10-03",
    "time": "17:25:21",
    "signing_time": "2026-10-03T14:25:21"
  },
  "submission": "reporting",
  "totals": {
    "line_total": 200.0,
    "discount": 0.0,
    "taxable": 200.0,
    "tax": 30.0,
    "total": 230.0,
    "allowance": 0.0,
    "subtotals": [
      {
        "category": "S",
        "rate": 15.0,
        "taxable": 200.0,
        "tax": 30.0,
        "reason_code": null,
        "reason": null
      }
    ]
  },
  "qr": "ARRTYW5kYm94IFRlc3QgQ29tcGFueQIPMzk5OTk5…",
  "warnings": [],
  "xml_bytes": 12047
}
Reported
{
  "success": true,
  "status": "REPORTED",
  "operation": "reporting",
  "invoice": {
    "number": "INV-1001",
    "uuid": "98891a92-befc-44a7-bc62-5bd5bca85477",
    "icv": 1,
    "hash": "cOLtpOe3RotM6GANvNBwZyy9eOupuMQWB/DOsPL3Te4="
  },
  "error": null,
  "errors": [],
  "warnings": [],
  "http_status": 200,
  "validation_status": "PASS",
  "cleared_xml_available": false
}
Cleared
{
  "success": true,
  "status": "CLEARED",
  "operation": "clearance",
  "invoice": {
    "number": "INV-1002",
    "uuid": "4eb61d9e-4520-44b1-b090-4b0428989b82",
    "icv": 2,
    "hash": "HFU71NGj7C7V4MXrEODnOOKoKH5I3xiY06y3J6IIQPQ="
  },
  "error": null,
  "errors": [],
  "warnings": [],
  "http_status": 200,
  "validation_status": "PASS",
  "cleared_xml_available": true
}
Accepted with warnings (202)
{
  "success": true,
  "status": "CLEARED",
  "operation": "clearance",
  "invoice": {
    "number": "INV-1003",
    "uuid": "cf9c6eb8-c024-4cd4-8f2d-b2c6fbfd25ad",
    "icv": 3,
    "hash": "7ndJYbgS9tIKvBzRKDaH/yt6VJ6UFmG4FBzr9VazE+0="
  },
  "error": null,
  "errors": [],
  "warnings": [
    {
      "type": "warning",
      "code": "BR-KSA-63",
      "message": "If the buyer country code (BT-55) is “SA”, then these fields are mandatory: street name (BT-50), building number (KSA-18), postal code (BT-53), city (BT-52), District (KSA-4), country code (BT-55). For more information please access this link: https://splonline.com.sa/en/national-address-1/",
      "source": "zatca",
      "category": "KSA",
      "rule": null,
      "help_url": "https://zatcatools.com/en/docs/errors/br-ksa-63"
    },
    {
      "type": "warning",
      "code": "BR-KSA-F-06-C28",
      "message": "[BR-KSA-F-06-C28] - Field character limits for the Buyer Address - District field (KSA-4) have not been met. The minimum limit is 1 character  and the maximum limit is 127 characters.",
      "source": "zatca",
      "category": "KSA",
      "rule": null,
      "help_url": "https://zatcatools.com/en/docs/errors/br-ksa-f-06-c28"
    },
    {
      "type": "warning",
      "code": "BR-KSA-67",
      "message": "If the buyer country code (BT-55) is 'SA', then the Buyer postal code(BT-53) must be 5 digits.",
      "source": "zatca",
      "category": "KSA",
      "rule": null,
      "help_url": "https://zatcatools.com/en/docs/errors/br-ksa-67"
    }
  ],
  "http_status": 202,
  "validation_status": "WARNING",
  "cleared_xml_available": true
}
Rejected (400) — total altered after signing
{
  "success": false,
  "status": "NOT_REPORTED",
  "operation": "reporting",
  "invoice": {
    "number": "INV-1004",
    "uuid": "b21640c0-f293-413e-a81b-7f51b23fe77d",
    "icv": 4,
    "hash": "NbrtroY5OLbVOVXDq1NSFk0tUxqikfcBx0kzO5DE1Kk="
  },
  "error": {
    "type": "error",
    "code": "BR-CO-15",
    "message": "Invoice total amount with VAT (BT-112) ",
    "source": "zatca",
    "category": "EN_16931",
    "rule": null,
    "help_url": "https://zatcatools.com/en/docs/errors"
  },
  "errors": [
    {
      "type": "error",
      "code": "BR-CO-15",
      "message": "Invoice total amount with VAT (BT-112) ",
      "source": "zatca",
      "category": "EN_16931",
      "rule": null,
      "help_url": "https://zatcatools.com/en/docs/errors"
    },
    {
      "type": "error",
      "code": "invoiceTotal_QRCODE_INVALID",
      "message": "Amount due for payment (BT-115) in the Invoice does not match with QR code invoice total or Amount due for payment (BT-115) missing in the invoice",
      "source": "zatca",
      "category": "QRCODE_VALIDATION",
      "rule": null,
      "help_url": "https://zatcatools.com/en/docs/errors"
    }
  ],
  "warnings": [],
  "http_status": 400,
  "validation_status": "ERROR",
  "cleared_xml_available": false
}
ValidationError.to_dict()
{
  "success": false,
  "error": {
    "code": "validation_error",
    "message": "items[0].quantity must be a number greater than zero. (and 3 more)",
    "source": "local",
    "help_url": "https://zatcatools.com/docs/sdk/advanced#validation_error",
    "errors": [
      {
        "field": "items[0].quantity",
        "message": "items[0].quantity must be a number greater than zero.",
        "rule": null,
        "help_url": null
      },
      {
        "field": "buyer.id",
        "message": "A buyer without a VAT number is identified by buyer.id and buyer.id_scheme on a standard invoice.",
        "rule": "BR-KSA-14",
        "help_url": "https://zatcatools.com/en/docs/errors/br-ksa-14"
      },
      {
        "field": "buyer.address.street",
        "message": "buyer.address.street is required on a standard invoice.",
        "rule": "BR-KSA-10",
        "help_url": "https://zatcatools.com/en/docs/errors/br-ksa-10"
      },
      {
        "field": "buyer.address.city",
        "message": "buyer.address.city is required on a standard invoice.",
        "rule": "BR-KSA-10",
        "help_url": "https://zatcatools.com/en/docs/errors/br-ksa-10"
      }
    ]
  }
}
No answer — UNKNOWN
{
  "success": false,
  "status": "UNKNOWN",
  "operation": "reporting",
  "invoice": {
    "number": "INV-1006",
    "uuid": "36732cfd-8203-4780-979d-1d4261dd1122",
    "icv": 1,
    "hash": "+2Vd/DY6OVbSKkTU57xe/3apRQcrQjaW+QsQ7sIsQ2Q="
  },
  "error": {
    "type": "error",
    "code": "network_error",
    "message": "ZATCA did not answer in time; the request may have been processed.",
    "source": "network",
    "category": null,
    "rule": null,
    "help_url": "https://zatcatools.com/docs/sdk/advanced#network_error"
  },
  "errors": [
    {
      "type": "error",
      "code": "network_error",
      "message": "ZATCA did not answer in time; the request may have been processed.",
      "source": "network",
      "category": null,
      "rule": null,
      "help_url": "https://zatcatools.com/docs/sdk/advanced#network_error"
    }
  ],
  "warnings": [],
  "http_status": null,
  "validation_status": null,
  "cleared_xml_available": false
}
Credentials refused — FAILED
{
  "success": false,
  "status": "FAILED",
  "operation": "reporting",
  "invoice": {
    "number": "INV-1007",
    "uuid": "a9c3e1dc-4fb3-4162-b81a-c35f116bd8dd",
    "icv": 1,
    "hash": "vk9GeSONdylkZ4A3WbDpWaLkF53elxv1EqVdwNUD5OQ="
  },
  "error": {
    "type": "error",
    "code": "authentication_error",
    "message": "ZATCA refused the credentials (401). Check the certificate, the secret and that they belong to this environment.",
    "source": "zatca",
    "category": null,
    "rule": null,
    "help_url": "https://zatcatools.com/docs/sdk/advanced#authentication_error"
  },
  "errors": [
    {
      "type": "error",
      "code": "authentication_error",
      "message": "ZATCA refused the credentials (401). Check the certificate, the secret and that they belong to this environment.",
      "source": "zatca",
      "category": null,
      "rule": null,
      "help_url": "https://zatcatools.com/docs/sdk/advanced#authentication_error"
    }
  ],
  "warnings": [],
  "http_status": 401,
  "validation_status": null,
  "cleared_xml_available": false
}

Errors and warnings

Every error answers three questions: code (what), message (why) and help_url (how to fix it). source says where it happened: local — in your process, nothing sent; network — between you and ZATCA; zatca — ZATCA's answer.

Codes in snake_case are the SDK's own. Every other code is ZATCA's. A local check that applies one of ZATCA's rules names that rule separately, as rule — it is never presented as a code ZATCA returned.

create_invoice() raises when the data cannot make an invoice. submit() never raises for the outcome: whatever happens comes back as a result. Onboarding raises.

handling errors
from zatca_tools import ValidationError

try:
    invoice = zatca.create_invoice(data)
except ValidationError as error:
    for problem in error.errors:                 # every problem at once
        print(problem["field"], problem["message"], problem["help_url"])
    raise

result = zatca.submit(invoice)                   # never raises for the outcome
for message in result.errors + result.warnings:
    print(message.type, message.code, message.message, message.help_url)
codeExceptionsourceWhereMeaning
validation_errorValidationErrorlocalcreate_invoice()The data cannot make a valid invoice. error.errors lists every problem: field, message, and — when the check applies one of ZATCA's rules — that rule and its help_url. Nothing was sent.
xml_errorXmlErrorlocalcreate_invoice()The invoice could not be written as well-formed XML. The field checks catch the usual cause (a control character) first. Nothing was sent.
signing_errorSigningErrorlocalcreate_invoice()The certificate or private key cannot sign: unreadable, the wrong curve, or a key that does not belong to the certificate.
pdf_errorPdfErrorlocalsave_pdf()The logo is not an image, the XML cannot be printed, or the installation is incomplete (reinstall the package).
network_errorNetworkErrornetworksubmit() → NOT_SENT / UNKNOWN; onboard() raisesThe connection failed. may_have_reached_zatca says whether ZATCA might have the request: if it might, send the same invoice again, never a new one.
authentication_errorAuthenticationErrorzatcasubmit() → FAILED; onboard() raisesZATCA answered 401: the certificate and secret were refused — wrong environment, compliance credentials used for real invoices, or a renewed certificate not yet in use.
zatca_request_errorZatcaRequestErrorzatcasubmit() → FAILED; onboard() raisesZATCA refused the request itself — an invalid OTP, a malformed request. Its own messages follow in errors, each with a help_url.
zatca_service_errorZatcaServiceErrorzatcasubmit() → FAILED; onboard() raisesZATCA answered without a verdict: a server error (5xx), maintenance, rate limiting (429), clearance switched off (303), or an unexpected body.
compliance_checks_failedComplianceCheckErrorzatcaonboard()ZATCA rejected one of the sample invoices onboarding sends. error.report holds every check; error.errors ZATCA's reasons.

Local warnings

On invoice.warnings or result.warnings. The invoice is valid; these are worth fixing.

coderuleMeaning
missing_exemption_reasonBR-KSA-69A zero-rated or exempt item has no tax_reason_code. ZATCA accepts it with a warning; the SDK never guesses a reason.
buyer_address_incompleteBR-KSA-63A Saudi buyer's address lacks a building number, district or postal code on a standard invoice. Accepted with a warning.
standard_invoice_required — A simplified invoice of SAR 1,000 or more names a VAT-registered buyer: the regulations expect a standard tax invoice.
cleared_xml_unreadable — ZATCA cleared the invoice but its stamped copy could not be decoded. The original answer is kept in result.raw["clearedInvoice"].

Help links

A ZATCA code links to its written guide in the ZATCA error reference when it has one, to its row in that reference when it does not, and to the reference's front page when the code is not in it. The SDK's own codes link to the table above.

The links are built offline, from a list of codes shipped with the SDK — no request is made to build one, and handling an error never needs the internet. A link carries a code from that list and nothing else: no invoice data, VAT number, certificate or part of ZATCA's answer. help_url(code) builds one for a code you stored earlier.

help_url
from zatca_tools import help_url

help_url("BR-KSA-63")        # 'https://zatcatools.com/en/docs/errors/br-ksa-63'
help_url("network_error")    # 'https://zatcatools.com/docs/sdk/advanced#network_error'
help_url("not a code!")      # None

Security and privacy

  • Where it runs. In your process. create_invoice() and save_pdf() make no network call at all.
  • What goes to ZATCA. During onboarding, the certificate request (your public key) and the OTP. Then, per invoice, the signed XML, its hash and UUID, with the certificate and secret as HTTP credentials — what ZATCA's API requires, nothing more.
  • What goes to ZATCA Tools. Nothing. No telemetry, no analytics, no request to any host but gw-fatoora.zatca.gov.sa; the test suite checks it.
  • Private keys. Created on your machine and never sent. Keep the credentials in a secret manager or encrypted storage, never in source control. Credentials hides its secrets when printed.
  • Logs. The SDK writes none. Invoices hold your customers' details: do not log them where your logs are less protected than your invoicing data.

Troubleshooting

SymptomUsually
Invalid-OTP during onboardingMistyped, already used, older than an hour, or from the other environment. Generate a new one.
certificate-permissions from ZATCAThe seller's VAT number is not the one in the certificate. In the sandbox it must be 399999999900003 (leave the seller out); elsewhere, the VAT number the system was onboarded with.
authentication_error on submitCredentials from another environment, or compliance credentials used for real invoices.
SigningError: the key does not belong to the certificateThe key from a different onboarding. Use the credentials onboard() returned, all three together.
Warnings BR-KSA-63 / BR-KSA-67A Saudi buyer's address lacks a building number or district, or its postal code is not five digits. Accepted, but complete it.
Errors about the previous hash or the counterThe chain broke: two invoices made from the same saved state, or the state not saved. See the chain.

API reference

CallReturns · does
Zatca(environment="sandbox", seller=, credentials=, chain=, design=, timeout=30)The client, one per system. In the sandbox, seller and chain default to ZATCA's test seller and a new chain.
zatca.onboard(otp=None, branch=, location=, industry=, invoice_types="1100")Credentials — and keeps them on the client. The OTP is only optional in the sandbox.
zatca.renew(otp=None, …)Credentials — a new certificate for the same system; the chain carries on.
zatca.create_invoice(dict)Invoice — validated, signed, hashed, with its QR code. Local only.
zatca.submit(invoice)SubmissionResult — clearance for standard, reporting for simplified. Never raises for the outcome.
zatca.report(invoice) · zatca.clear(invoice) · zatca.check_compliance(invoice)SubmissionResult — the explicit paths.
zatca.chainChain(icv, hash) of the last invoice signed · .to_dict()
zatca.onboarding()The onboarding steps one by one: generate_csr, request_compliance_csid, run_compliance_checks, request_production_csid, renew_production_csid.
Invoice.number .type .kind .uuid .icv .pih .hash .qr .xml .totals .items .warnings · .save_xml() · .save_pdf() · .to_pdf() · .to_dict() · .to_json()
SubmissionResult.success .status .error .errors .warnings .xml .cleared_xml .http_status .validation_status .raw · .save_xml() · .save_pdf() · .to_dict()
Message.type .code .message .source .category .rule .help_url · .to_dict()
Credentials.certificate .secret .private_key .expires_at · .export()
InvoiceDesign(logo=, accent=, footer=, zatca_tools_mark=True)The branding of printed invoices.
help_url(code) · decode_qr(qr)A help link for a code, or None · the QR code's fields.

Not included yet

  • Storage, queues and retries — your application owns the credentials, the chain and the schedule.
  • Currencies other than SAR; prepayment invoices; the third-party, self-billed, export and summary transaction flags.
  • Charges on the whole invoice (fees). Discounts are supported.
  • Verifying ZATCA's stamp on a cleared invoice.
  • A choice of invoice layouts: one layout, branded with your logo, colour and footer.
Questions about the SDK: [email protected].