The problem
VAT-registered businesses in Saudi Arabia must issue electronic tax invoices that meet ZATCA requirements. In Phase 2 (integration), invoices are also exchanged with ZATCA: standard (B2B) invoices are cleared before they reach the buyer, and simplified (B2C) invoices are reported within 24 hours. Building that into every system that sells something is costly.
What the API does
Your system sends the sale; WonderFatoora issues the tax invoice:
- Sequential invoice numbers with no gaps, assigned at issue.
- VAT per line: standard, zero-rated and exempt, with ZATCA reason codes.
- The QR code on every invoice.
- For companies linked to ZATCA, standard invoices are cleared and simplified invoices reported automatically, and the outcome is in the
zatcafield. - A page and a PDF your buyer can open.
- Invoice documents in Arabic or English (
locale).
One call
curl -X POST https://fatoora.tw.sa/api/v1/invoices \
-H "Authorization: Bearer wbk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"external_reference": "ORDER-10045",
"issue_date": "2026-11-03",
"customer": { "id": 381 },
"items": [
{ "description": "Annual subscription", "quantity": 1, "unit_price": 1200 }
]
}'
<?php
// Issuing is the default when "issue" is omitted. The key needs
// invoices:write AND invoices:issue. external_reference makes the call
// safe to retry: a repeat returns the invoice already created.
$response = $client->post('invoices', [
'json' => [
'external_reference' => 'ORDER-10045',
'issue_date' => '2026-11-03',
'customer' => ['id' => 381],
'items' => [
['description' => 'Annual subscription', 'quantity' => 1, 'unit_price' => 1200],
],
],
'http_errors' => false,
]);
const res = await fetch("https://fatoora.tw.sa/api/v1/invoices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.WONDERFATOORA_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
external_reference: "ORDER-10045", // safe to retry
issue_date: "2026-11-03",
customer: { id: 381 },
items: [{ description: "Annual subscription", quantity: 1, unit_price: 1200 }],
}),
});
const body = await res.json();
// 201 = created now, 200 with data.replayed = true = created by an earlier call
console.log(res.status, body.data?.replayed === true, body.data?.invoice?.zatca);
res = requests.post(
"https://fatoora.tw.sa/api/v1/invoices",
headers={"Authorization": f"Bearer {os.environ['WONDERFATOORA_API_KEY']}",
"Accept": "application/json"},
json={
"external_reference": "ORDER-10045", # safe to retry
"issue_date": "2026-11-03",
"customer": {"id": 381},
"items": [{"description": "Annual subscription", "quantity": 1, "unit_price": 1200}],
},
timeout=60,
)
Who it is for
- Online stores issuing an invoice per order.
- Subscription and SaaS businesses billing every period.
- Booking, clinic and point-of-sale systems.
- ERPs and back-office tools that need Saudi tax invoices without building a ZATCA integration.
Requirements
API access is part of the Business and Enterprise plans. Your company completes its profile and links to ZATCA once in the WonderFatoora dashboard; after that, invoices created through the API follow exactly the same rules as invoices made by hand. Start with the quick start.
Related
Ready to build?
API access is part of the Business and Enterprise plans. Create a key in Settings → API keys and make your first call in minutes.