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
-
typestring requiredsimplified(B2C, a receipt — reported) orstandard(B2B tax invoice — cleared). -
numberstring requiredYour invoice number, up to 127 characters. -
itemslist requiredWhat was sold.-
namestring requiredItem name. -
quantitynumber requiredGreater than zero; fractions allowed. -
unit_pricenumber requiredBefore VAT — or with VAT whenprices_include_vatis true. -
discount / discount_typenumber / stringAn item discount, as anamount(default) or apercent. -
tax_categorystring defaultSS15%,Zzero-rated,Eexempt,Oout of scope. -
tax_reason_codestringZATCA'sVATEX-SA-…reason, for Z, E and O. -
unitstring defaultPCEUnit of measure code, e.g.HUR,MTR. -
descriptionstringPrinted under the name on the PDF. Not part of the XML.
-
-
kindstring defaultinvoiceinvoice,credit(credit note) ordebit(debit note). -
date / timestringYYYY-MM-DD/HH:MM:SS, Riyadh time. Now by default; never in the future. -
prices_include_vatbool defaultfalseTrue when unit prices and amount discounts are what the customer pays. -
discountnumberA discount on the whole invoice. -
buyerdictRequired on a standard invoice:name;vat_number, oridwithid_scheme(CRN, NAT, IQA, 700, PAS…);addresswith street, building_number, district, city, postal_code, country. -
original_invoice / reasonstringRequired on credit and debit notes: the invoice adjusted, and why. -
delivery_datestringThe supply date, when it differs from the issue date. -
uuidstringGenerated when left out. -
icv / pihint / stringOnly 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.
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.
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():
- CSR —
generate_csr(), local: a new secp256k1 key pair and the certificate request ZATCA expects. - Compliance CSID — the request and the OTP go to ZATCA; a test certificate comes back.
- Compliance checks — an invoice, a credit note and a debit note for each kind the system issues, signed with that test certificate.
- 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.
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.
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.
{
"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
}
{
"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
}
{
"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
}
{
"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
}
{
"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
}
{
"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"
}
]
}
}
{
"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
}
{
"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.
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)
| code | Exception | source | Where | Meaning |
|---|---|---|---|---|
| validation_error | ValidationError | local | create_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_error | XmlError | local | create_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_error | SigningError | local | create_invoice() | The certificate or private key cannot sign: unreadable, the wrong curve, or a key that does not belong to the certificate. |
| pdf_error | PdfError | local | save_pdf() | The logo is not an image, the XML cannot be printed, or the installation is incomplete (reinstall the package). |
| network_error | NetworkError | network | submit() → NOT_SENT / UNKNOWN; onboard() raises | The 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_error | AuthenticationError | zatca | submit() → FAILED; onboard() raises | ZATCA 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_error | ZatcaRequestError | zatca | submit() → FAILED; onboard() raises | ZATCA refused the request itself — an invalid OTP, a malformed request. Its own messages follow in errors, each with a help_url. |
| zatca_service_error | ZatcaServiceError | zatca | submit() → FAILED; onboard() raises | ZATCA answered without a verdict: a server error (5xx), maintenance, rate limiting (429), clearance switched off (303), or an unexpected body. |
| compliance_checks_failed | ComplianceCheckError | zatca | onboard() | 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.
| code | rule | Meaning |
|---|---|---|
| missing_exemption_reason | BR-KSA-69 | A zero-rated or exempt item has no tax_reason_code. ZATCA accepts it with a warning; the SDK never guesses a reason. |
| buyer_address_incomplete | BR-KSA-63 | A 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.
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()andsave_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.
Credentialshides 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
| Symptom | Usually |
|---|---|
Invalid-OTP during onboarding | Mistyped, already used, older than an hour, or from the other environment. Generate a new one. |
certificate-permissions from ZATCA | The 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 submit | Credentials from another environment, or compliance credentials used for real invoices. |
SigningError: the key does not belong to the certificate | The key from a different onboarding. Use the credentials onboard() returned, all three together. |
Warnings BR-KSA-63 / BR-KSA-67 | A 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 counter | The chain broke: two invoices made from the same saved state, or the state not saved. See the chain. |
API reference
| Call | Returns · 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.chain | Chain(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.