نقاط النهاية
/api/v1/invoices
/api/v1/invoices/{id}
/api/v1/invoices
/api/v1/invoices/{id}/status
/api/v1/invoices/{id}/validate
/api/v1/invoices/{id}/issue
/api/v1/invoices/{id}/pdf
/api/v1/invoices/{id}/xml
/api/v1/invoices/{id}/notes
/api/v1/invoices/{id}/payments
/api/v1/invoices/{id}/payments
مسودة أم فاتورة صادرة
طلب الإنشاء يصدر الفاتورة ما لم ترسل "issue": false.
| مسودة (`"issue": false`) | صادرة (الافتراضي) | |
|---|---|---|
| الرقم | مرجع DRAFT-…، وnumber_is_final: false |
الرقم التالي في تسلسلك، وnumber_is_final: true |
| الصلاحيات | invoices:write |
invoices:write وinvoices:issue |
| الهيئة | لا يُرسل شيء | تُعتمد أو يُبلَّغ عنها تلقائيًا إن كانت المنشأة مرتبطة بالهيئة |
| التعديل | تُصدر عبر POST /invoices/{id}/issue، أو تُعدَّل أو تُصدر أو تُلغى من لوحة التحكم |
نهائية؛ لا تُصحح إلا بإشعار دائن أو مدين |
عند إصدار الفاتورة يصبح تاريخ إصدارها يوم العمل الحالي في المملكة، وإن كان تاريخ الاستحقاق سيسبقه فإنه يتقدم بنفس مدة السداد. الأرقام لا تُمنح إلا عند الإصدار، فلا تترك المسودات فجوات في تسلسلك النظامي.
POST /invoices/{id}/validate (لا يتغير شيء)، ثم استدعِ POST /invoices/{id}/issue بمفتاح يملك invoices:issue. وإصدار فاتورة صادرة أصلًا يرد بـ 200 مع already_issued: true.
حقول الطلب
القائمة الكاملة في مرجع الواجهة البرمجية. الأساسيات:
| الحقل | ملاحظات |
|---|---|
issue_date |
مطلوب. بصيغة YYYY-MM-DD. |
items |
مطلوب، من 1 إلى 200 بند. يحتاج كل بند description وquantity (أكبر من 0) وunit_price (قبل الضريبة). |
items[].vat_rate |
نسبة مئوية، الافتراضي 15. أو tax_rate_id لاستخدام إحدى نسب الضريبة المحفوظة لديك. |
items[].discount |
مبلغ يُخصم من البند قبل الضريبة. |
customer |
اختياري: {"id": …} لعميل موجود، أو بيانات مباشرة. راجع العملاء. |
external_reference |
معرّفك الخاص للعملية، ويجعل الإنشاء آمنًا عند إعادة المحاولة. |
currency، exchange_rate |
SAR (الافتراضي) أو USD أو AED. العملة الأخرى تحتاج exchange_rate بالريال لكل وحدة. |
document_discount_type، document_discount_value |
خصم على الفاتورة كلها (amount أو percent) يوزَّع على البنود قبل الضريبة. |
due_date، notes، terms، locale |
اختيارية. locale (ar أو en) لغة مستند الفاتورة. |
مدفوعة مسبقًا: فاتورة تصل مدفوعة
إذا كان موقعك يستلم الدفع عبر بوابة الدفع الخاصة به ثم يصدر الفاتورة، فأرسل الدفعة معها في payment. تُصدر الفاتورة وتُسجَّل الدفعة في الطلب نفسه، فتصل paid (أو partial إذا كانت الدفعة جزءًا من الإجمالي)، مع سند قبض مرقّم.
| الحقل | ملاحظات |
|---|---|
payment.method |
cash (الافتراضي) أو bank_transfer أو mada أو visa_mastercard أو apple_pay أو stc_pay أو cheque أو other، أو رمز طريقة دفع أضافتها منشأتك. |
payment.reference |
رقم العملية في بوابة الدفع. يظهر في سند القبض. |
payment.amount |
اختياري. الافتراضي إجمالي الفاتورة، ولا يزيد عليه. |
payment.paid_on |
اختياري. الافتراضي اليوم، ولا يكون يومًا لاحقًا. |
payment.notes وpayment.account_id |
اختياريان. account_id حساب النقدية أو البنك الذي دخل فيه المبلغ. |
- يحتاج المفتاح إلى صلاحية
payments:write، ويجب أن تكون الفاتورة صادرة (لا مع"issue": false). - إذا رُفضت الدفعة (
payment_exceeds_balanceأوvalidation_failed) لا يُنشأ شيء: لا تُصدر فاتورة دون دفعتها. - مع
external_referenceتعيد إعادة المحاولة الفاتورة نفسها ولا تسجل دفعة ثانية. - المبلغ الذي يصل لاحقًا (تحويل بنكي، أو تأكيد متأخر من البوابة) يُسجَّل بـ
POST /invoices/{id}/payments. وكل دفعة ترسل حدثinvoice.payment_recordedعبر Webhooks.
# Your gateway confirmed the payment: issue the invoice already paid.
curl -X POST https://fatoora.tw.sa/api/v1/invoices \
-H "Authorization: Bearer wbk_test_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"external_reference": "ORDER-1001",
"issue_date": "2026-11-12",
"customer": {"name": "Walk-in customer"},
"items": [{"description": "Annual plan", "quantity": 1, "unit_price": 100}],
"payment": {"method": "mada", "reference": "GW-TXN-55"}
}'
// After your gateway's success callback, never before.
$response = $client->post('invoices', [
'json' => [
'external_reference' => $order->id, // safe to retry
'issue_date' => date('Y-m-d'),
'items' => $order->lines,
'payment' => [
'method' => 'mada', // or visa_mastercard, apple_pay, stc_pay …
'reference' => $gateway->transactionId, // shown on the receipt
// 'amount' defaults to the invoice total
],
],
'http_errors' => false,
]);
$body = json_decode((string) $response->getBody(), true);
// $body['data']['invoice']['status'] === 'paid'
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.id, // safe to retry
issue_date: new Date().toISOString().slice(0, 10),
items: order.items,
payment: { method: "mada", reference: gatewayTxn.id }, // amount defaults to the total
}),
});
const body = await res.json();
// body.data.invoice.status === "paid", body.data.payment.number is the receipt
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["id"], # safe to retry
"issue_date": order["date"],
"items": order["items"],
"payment": {"method": "mada", "reference": txn["id"]}, # amount defaults to the total
},
timeout=60,
)
body = res.json()
# body["data"]["invoice"]["status"] == "paid"
إعادة المحاولة بأمان مع external_reference
الشبكات تتعطل. حين تنتهي مهلة طلب الإنشاء لا تعرف هل أُنشئت الفاتورة، والإصدار مرتين يعني فاتورتين ضريبيتين لا يمكن سحبهما. أرسل external_reference فريدًا من عندك مع كل طلب إنشاء:
- الطلب الأول يرد بـ
201 Createdمع الفاتورة الجديدة. - أي طلب لاحق بالمرجع نفسه يرد بـ
200 OKمع الفاتورة المنشأة سابقًا و"replayed": true. لا يُنشأ شيء جديد، ولا يُحتسب التكرار من باقتك. - في الطلب المكرر، المفتاح الذي لا يملك
invoices:readلا يتلقى إلاidوnumberوstatus.
import os, time, uuid
import requests
API = "https://fatoora.tw.sa/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['WONDERFATOORA_API_KEY']}",
"Accept": "application/json"}
def create_invoice(order):
payload = {
# Your own order id: a retry after a timeout returns the invoice
# the first attempt created instead of issuing a second one.
"external_reference": order["id"],
"issue_date": order["date"],
"items": order["items"],
}
for attempt in range(5):
try:
res = requests.post(f"{API}/invoices", json=payload, headers=HEADERS, timeout=60)
except requests.RequestException:
time.sleep(2 ** attempt) # network error: same payload, try again
continue
if res.status_code == 429:
time.sleep(int(res.headers.get("Retry-After", "5")))
continue
if res.status_code >= 500:
time.sleep(2 ** attempt)
continue
return res.json() # 200/201, or a 4xx to fix, not retry
raise RuntimeError("WonderFatoora API unavailable, will retry later")
async function createInvoice(order) {
const payload = {
external_reference: order.id, // safe to retry
issue_date: order.date,
items: order.items,
};
for (let attempt = 0; attempt < 5; attempt++) {
let res;
try {
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(payload),
signal: AbortSignal.timeout(60_000),
});
} catch {
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
continue;
}
if (res.status === 429) {
await new Promise((r) => setTimeout(r, Number(res.headers.get("Retry-After") ?? 5) * 1000));
continue;
}
if (res.status >= 500) {
await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
continue;
}
return res.json(); // 200/201, or a 4xx to fix rather than retry
}
throw new Error("WonderFatoora API unavailable, retry later");
}
Idempotency-Key (من 8 إلى 255 حرفًا مرئيًا من ASCII): إعادة الطلب بالمفتاح والمحتوى نفسيهما تعيد الرد الأول مع Idempotent-Replayed: true، والمفتاح نفسه مع محتوى مختلف يُرفض بـ 422 idempotency_key_reused. تُحفظ المفاتيح 24 ساعة لكل بيانات اعتماد على حدة: لا يُعاد الرد إلا لمفتاح API (أو ربط التطبيق) الذي أرسل الطلب، أما مفتاح آخر للمنشأة يرسل Idempotency-Key نفسه فيُنفَّذ طلبه كطلب جديد، وكذلك بديل المفتاح بعد تجديده.
العملات الأجنبية والإعفاءات الضريبية
للتصدير أو أي بند آخر خاضع للنسبة الصفرية أو معفى، اجعل vat_rate يساوي 0 وضع رمز سبب الهيئة في vat_exemption_code، مثل VATEX-SA-32 (تصدير السلع) أو VATEX-SA-33 (تصدير الخدمات) أو VATEX-SA-35 (الأدوية والمعدات الطبية). في المنشأة المرتبطة بالهيئة يحتاج هذا البند سببه قبل إصدار الفاتورة، إلا إذا كانت العملية نفسها تدل عليه (التصدير).
{
"issue_date": "2026-11-03",
"currency": "USD",
"exchange_rate": 3.75,
"customer": { "name": "Example Inc.", "country": "US" },
"items": [{
"description": "Software licence (export of services)",
"quantity": 1,
"unit_price": 400,
"vat_rate": 0,
"vat_exemption_code": "VATEX-SA-33"
}]
}
حالة الفاتورة لدى الهيئة
تحمل كل فاتورة كائن zatca يبين موقفها لدى هيئة الزكاة والضريبة والجمارك:
| الحقل | المعنى |
|---|---|
flow |
clearance (الاعتماد) للفواتير الضريبية بين المنشآت، وreporting (الإبلاغ) للفواتير المبسطة، أو null. |
status |
pending أو cleared أو reported أو rejected أو retrying أو not_submitted أو unknown؛ وnull حين لا يُرسل شيء (مسودة، أو منشأة غير مرتبطة بالهيئة). |
with_warnings |
true حين قبلت الهيئة الفاتورة مع تحذيرات. |
uuid |
المعرّف الفريد للفاتورة بعد إرسالها. |
deliverable |
true حين يجوز تسليم الفاتورة للمشتري. الفاتورة الضريبية بين المنشآت تُحجز حتى تعتمدها الهيئة. |
errors |
رسائل الهيئة نفسها حين ترفض الفاتورة (حتى 5 رسائل). |
| الحالة | المعنى | ماذا تفعل |
|---|---|---|
pending |
أُرسلت أو في الانتظار، ولا رد بعد. | اقرأ الفاتورة مجددًا بعد قليل. |
retrying |
تعذر الوصول إلى الهيئة، وتُعاد المحاولة تلقائيًا. | لا شيء؛ اقرأها لاحقًا. |
cleared، reported |
قبلتها الهيئة. | سلّمها (public_url). |
rejected |
رفضتها الهيئة، ويبيّن errors السبب. |
عالج السبب (غالبًا بيانات المنشأة أو المشتري) من لوحة التحكم. |
not_submitted |
حُجزت قبل وصولها إلى الهيئة، مثلًا لعدم اكتمال ربط المنشأة بالهيئة. | راجع إعدادات الهيئة في لوحة التحكم. |
unknown |
أُرسلت لكن النتيجة لم تُسجل. | يطابقها فريق الدعم؛ تواصل معنا إن استمرت. |
اشترك في Webhook الحدث invoice.zatca_status_changed ليصلك إشعار عند تغيرها. ومن دون Webhooks اقرأ GET /invoices/{id} بفواصل متزايدة (مثلًا بعد 5 ثوانٍ ثم 30 ثانية ثم دقيقتين ثم 10 دقائق) حتى يصبح deliverable مساويًا true أو تصبح الحالة نهائية.
مشاركة الفاتورة
public_url صفحة يفتحها عميلك مع إمكانية تنزيل PDF. وما دامت الفاتورة الضريبية بانتظار الاعتماد، تعرض الصفحة تنبيهًا بدل المستند.
صفحات ذات صلة
جاهز للبدء؟
الوصول إلى الواجهة البرمجية ضمن باقات: الأعمال والمؤسسات. أنشئ مفتاحًا من الإعدادات ← مفاتيح API وأرسل أول طلب خلال دقائق.