الاستيثاق
كل طلب يتطلب مفتاح API في ترويسة Authorization.
ولّد مفتاحك من الإعدادات ← واجهة API — يظهر مرة واحدة فقط عند التوليد، وصلاحيته كاملة على فوترة منشأتك فعامله كسر:
خزّنه في متغير بيئة ولا تضعه في الكود أو المستودع.
curl https://zatcatools.com/api/v1/account \
-H "Authorization: Bearer ztk_live_..."
الأخطاء
كل الأخطاء ترجع بشكل موحّد:
{
"error": {
"code": "quota_exceeded",
"message": "Free-plan invoice quota exhausted. Contact us to upgrade."
}
}
| HTTP | code | المعنى |
|---|---|---|
| 401 | unauthenticated | مفتاح API مفقود أو غير صحيح |
| 402 | quota_exceeded | استهلكت رصيد باقتك — تواصل معنا للترقية |
| 404 | not_found | لا يوجد مورد بهذا المعرّف ضمن منشأتك |
| 409 | not_connected | المنشأة غير مرتبطة بـ ZATCA بعد — أكمل الربط أولًا |
| 422 | validation_failed … | بيانات ناقصة أو غير صالحة — الرسالة توضح السبب |
| 429 | too_many_requests | تجاوزت حد الطلبات (60/دقيقة) — أعد المحاولة لاحقًا |
/v1/account
بيانات المنشأة، حالة الربط، والرصيد المتبقي من الباقة.
{
"name": "مؤسسة النخبة",
"vat_number": "310000000000003",
"cr_number": "1010101010",
"zatca": {
"connected": true,
"environment": "production",
"certificate_expires_at": "2027-07-01"
},
"quota": { "used": 12, "limit": 50, "remaining": 38 }
}
/v1/invoices
يبني الفاتورة، يوقّعها رقميًا، ويرسلها إلى ZATCA في طلب واحد —
تخليصًا (Clearance) للضريبية أو إبلاغًا (Reporting) للمبسطة.
الفاتورة الضريبية standard تتطلب كائن
customer كاملًا؛
المبسطة simplified لا تتطلبه.
العملاء يُطابَقون تلقائيًا بالرقم الضريبي (أو يُنشأون إن لم يوجدوا).
curl -X POST https://zatcatools.com/api/v1/invoices \
-H "Authorization: Bearer ztk_live_..." \
-H "Content-Type: application/json" \
-d '{
"type": "standard",
"branch_id": 3, // البيع من فرع؟ سمِّ الفرع وجهازه معًا —
"device_id": 7, // القاعدة نفسها للضريبية والمبسطة سواء
"customer": {
"name": "شركة المثال للتجارة",
"vat_number": "311111111101113",
"cr_number": "1010101010",
"short_address": "RRRD2929"
},
"lines": [
{ "name": "اشتراك سنوي", "quantity": 1, "unit_price": 1000 }
],
"discount": 0,
"issue_date": "2026-07-19"
}'
{
"type": "simplified",
"external_id": "order-5501",
"customer_email": "[email protected]",
"prices_include_vat": true, // أسعار الرف كما هي — نشتق الصافي
"branch_id": 3, // البيع من فرع؟ سمِّ الفرع…
"device_id": 7, // …وجهازه معًا (القائمة على GET /account)
"lines": [
{ "name": "قهوة مختصة 250غ", "quantity": 2, "unit_price": 55,
"description": "حبوب إثيوبية — تحميصة فاتحة" }, // يُطبع تحت اسم البند
{ "name": "أدوات تحضير", "quantity": 1, "unit_price": 120,
"discount": 10, "discount_type": "percent" }, // خصم على البند: نسبة أو مبلغ
{ "name": "دواء", "quantity": 1, "unit_price": 80,
"tax_category": "Z", "tax_reason_code": "VATEX-SA-35" }
]
}
external_id— معرّف الطلب في نظامك. إعادة إرسال نفس القيمة تُرجع الفاتورة نفسها (200) بدل إصدار مكرر. تقدر بدلًا منه تمرّر ترويسةIdempotency-Key.customer_email— نرسل نسخة PDF من الفاتورة (مع رمز QR) لهذا البريد تلقائيًا.lines[].description— شرح يُطبع تحت اسم البند (حتى 500 حرف). وlines[].discount— خصم على البند نفسه، مبلغًا افتراضيًا أو نسبة مع"discount_type": "percent". يُخصم من سعر الوحدة ويُطبع السعر المتفق عليه وبجانبه الخصم؛ وحقلdiscountعلى مستوى الفاتورة يبقى كما هو لخصمٍ على الإجمالي.customer.short_address— العنوان الوطني المختصر للمشتري (٤ أحرف + ٤ أرقام، مثل RRRD2929). نستخرج منه الشارع ورقم المبنى والحي والمدينة والرمز البريدي التي تطلبها الهيئة للفاتورة الضريبية — فلا تحتاج تجمعها بنفسك. وتقدر بدلًا منه ترسل الحقول صريحة: street, building_number, subdivision, city, postal_zone.due_date— تاريخ استحقاق السداد للفاتورة الضريبية (B2B)، بصيغة YYYY-MM-DD. يظهر على الفاتورة ويُستخدم في متابعة السداد.notes— ملاحظات حرة للعميل (حتى 2000 حرف): شروط السداد، ملاحظة تسليم، شكر. تُطبع أسفل جدول الفاتورة في ملف PDF ولا تدخل في XML الموقّع.
{
"uuid": "8d9f6e0a-4b7c-4f2e-9a1d-3c5b7e9f1a2b",
"number": "INV-2026-00042",
"kind": "invoice",
"type": "standard",
"status": "accepted",
"issue_date": "2026-07-19",
"totals": {
"lines": "1000.00", "discount": "0.00",
"vat": "150.00", "grand": "1150.00", "currency": "SAR"
}, // + "line_discounts" عند وجود خصم على البنود
"customer": { "name": "شركة المثال للتجارة", "vat_number": "311111111101113" },
"lines": [ // كما أُرسلت: description و discount و net_unit_price عند وجودها
{ "name": "اشتراك سنوي", "quantity": 1, "unit_price": 1000, "total": 1000 }
],
"due_date": null, "notes": null,
"zatca": {
"channel": "clearance",
"icv": 42,
"submitted_at": "2026-07-19T09:30:00+03:00",
"warnings": []
},
"links": {
"xml": "https://zatcatools.com/api/v1/invoices/{uuid}/xml",
"pdf": "https://zatcatools.com/api/v1/invoices/{uuid}/pdf",
"qr": "https://zatcatools.com/api/v1/invoices/{uuid}/qr.svg"
},
"lines": [
{ "name": "اشتراك سنوي", "quantity": 1, "unit_price": 1000, "total": 1000 }
]
}
status دائمًا:
accepted مقبولة ·
warnings مقبولة مع تحذيرات (مفصّلة في zatca.warnings) ·
rejected رفضتها الهيئة (الأسباب في zatca.rejection).
الضريبة: الفئات، الشامل، والفروع
"prices_include_vat": true
يعني أن كل سعر في الطلب — البنود والخصم — هو ما يدفعه العميل فعلًا (سعر الرف)،
ونحن نشتق الصافي لكل بند بنسبة ذلك البند نفسه. المستند المخزَّن والموقَّع صافٍ دائمًا كما يلزم النظام —
الخيار يغيّر معنى الريال المُرسل، لا شكل الفاتورة. بدونه (الافتراضي) الأسعار صافية ونضيف الضريبة فوقها.
كل بند يقبل tax_category
— والافتراضي S (خاضعة 15%).
الفئات غير الخاضعة تتطلب tax_reason_code
من قائمة الهيئة — الهيئة ترفض بندًا صفريًا أو معفى بلا سبب، ونرسل نص السبب الرسمي تلقائيًا مع كوده.
الفاتورة الواحدة تجمع فئات مختلفة، ويُبنى لكل فئة إجماليها الفرعي كما تطلب الهيئة.
| tax_category | المعنى | tax_reason_code |
|---|---|---|
| S · 15% | خاضعة 15% | لا يلزم |
| Z · 0% | صفرية 0% |
VATEX-SA-32 — تصدير سلع
VATEX-SA-33 — تصدير خدمات
VATEX-SA-34-1 — نقل دولي للبضائع
VATEX-SA-34-2 — نقل دولي للركاب
VATEX-SA-34-3 — خدمات مرتبطة بالنقل الدولي للركاب
VATEX-SA-34-4 — توريد وسيلة نقل مؤهلة
VATEX-SA-34-5 — خدمات متعلقة بنقل البضائع أو الركاب
VATEX-SA-35 — أدوية ومستلزمات طبية
VATEX-SA-36 — معادن مؤهلة (ذهب/فضة/بلاتين استثماري)
VATEX-SA-EDU — تعليم خاص لمواطن
VATEX-SA-HEA — رعاية صحية خاصة لمواطن
|
| E · 0% | معفاة |
VATEX-SA-29 — خدمات مالية (المادة 29)
VATEX-SA-29-7 — تأمين على الحياة (المادة 29)
VATEX-SA-30 — تعاملات عقارية (المادة 30)
|
| O · 0% | خارج نطاق الضريبة |
VATEX-SA-OOS — خارج نطاق الضريبة
|
كل فروعك تحت رقم ضريبي واحد، لكن كل فرع يُصدر عبر جهازه الخاص — نموذج الهيئة نفسه (EGS unit): كل جهاز يُفعَّل بـ OTP خاص به تولّده من بوابة فاتورة (إلحاق حل فوترة جديد — رمز لكل جهاز، يعمل مرة واحدة خلال ساعة)، ويحمل شهادته وسلسلة فواتيره المستقلة، فتُصدر فروعك بالتوازي بلا طابور واحد. المقر الرئيسي جهازه هو ربط المنشأة الأساسي. الفرع لا يُصدر حتى يتصل له جهاز، والفرع الواحد يتحمّل عدة أجهزة.
| POST /v1/branches | إنشاء فرع — name وshort_address إلزاميان (الشارع والحي والمدينة والرمز البريدي تُستخرج من المختصر تلقائيًا)، cr_number اختياري. حقول العنوان الصريحة لحالة واحدة: تعذر حلّ الرمز (address_unresolved) |
| POST /v1/branches/{id}/devices | تفعيل جهاز للفرع — otp من بوابة فاتورة. يرجع 202 فورًا والتفعيل يكمل في الخلفية |
| GET /v1/devices/{id} | تتبّع الحالة حتى connected — تمر بفحوص التوافق الستة؛ failed مع otp_invalid = ولّد رمزًا جديدًا وأعد |
| POST /v1/devices/{id}/connect | إعادة تفعيل جهاز فشل أو تجمّد بعد التخرج — otp جديد، وسلسلته تبدأ من جديد |
| DELETE /v1/devices/{id} | سحب جهاز — وثائقه المُصدرة تبقى كما هي |
بعدها فاتورة الفرع تسمّي فرعها وجهازها معًا — للضريبية (B2B) والمبسطة (B2C) سواء:
branch_id
+
device_id
— نقطة البيع تعرف جهازها، والمستند يحمل عنوان الفرع وسجله التجاري ويوقَّع بذلك
الجهاز تحديدًا وعلى سلسلته. أحدهما بلا الآخر يُرفض
(device_required /
branch_required)،
والاسم القانوني والرقم الضريبي يبقيان للمنشأة دائمًا.
بلا الاثنين: المقر الرئيسي.
الإشعارات (POST /notes) تتبع فرع فاتورتها الأصل تلقائيًا،
وdevice_id عليها اختياري.
القائمة الكاملة للفروع وأجهزتها على GET /account،
وتديرها أيضًا من الإعدادات ← ملف المنشأة.
/v1/invoices
الأحدث أولًا. معاملات اختيارية:
status
(accepted | warnings | rejected | draft) ·
type
(standard | simplified) ·
page ·
per_page (الأقصى 100).
{
"data": [ { "uuid": "...", "number": "INV-2026-00042", "status": "accepted", ... } ],
"meta": { "page": 1, "per_page": 25, "total": 128, "last_page": 6 }
}
/v1/invoices/{uuid}
التفاصيل الكاملة لفاتورة أو إشعار — نفس شكل استجابة الإصدار، شاملة البنود وأسباب الرفض إن وجدت.
الملفات — XML · PDF · QR
لكل مستند مُصدَر ثلاثة تنزيلات (بنفس ترويسة الاستيثاق):
/v1/invoices/{uuid}/xml
الـ XML الموقّع — كما استلمته ZATCA حرفيًا
/v1/invoices/{uuid}/pdf
PDF عربي جاهز للطباعة مع رمز QR
/v1/invoices/{uuid}/qr.svg
رمز QR وحده بصيغة SVG (حمولة TLV الموقّعة)
/v1/notes
إشعار دائن (credit) أو مدين (debit)
مرتبط بفاتورة أصلية عبر invoice_uuid.
السبب reason إلزامي (متطلب الهيئة BR-KSA-17)،
وقيمة إشعار الدائن لا تتجاوز الرصيد المتبقي للفاتورة بعد الإشعارات السابقة (المادة 40).
{
"kind": "credit",
"invoice_uuid": "8d9f6e0a-4b7c-4f2e-9a1d-3c5b7e9f1a2b",
"reason": "إرجاع جزئي للبضاعة",
"lines": [
{ "name": "اشتراك سنوي", "quantity": 1, "unit_price": 500 }
]
}
"full": true
بدون lines،
ونحسب نحن الرصيد المتبقي للفاتورة بعد أي إشعارات سابقة — فلا تحتاج تحسبه بنفسك ولا يمكن أن يتجاوز الحد النظامي.
فاتورة مغطّاة بالكامل ترجع 422 nothing_to_credit.
وإعادة تفعيلها: نفس الطلب بـ
"kind": "debit"
و"full": true
يصدر إشعار مدين بمقدار ما خُصم سابقًا فتعود الفاتورة مستحقة. لا شيء مخصوم ⟵ 422 nothing_to_restore.
استخدم external_id مختلفًا لكل دورة إلغاء/تفعيل، وإلا أرجعنا لك الإشعار السابق نفسه.
حدود الاستخدام
- 60 طلبًا في الدقيقة لكل مفتاح — التجاوز يرجع 429.
- إصدار الفواتير يستهلك من رصيد باقتك — تابع المتبقي عبر
GET /v1/account. - مفتاح واحد فعّال لكل منشأة — إعادة التوليد تبطل المفتاح السابق فورًا.