واجهة مطابق البرمجية
افحص ملفات XML للفواتير الإلكترونية في المرحلة الثانية من أنظمتك مباشرةً — خطافاً في نظام ERP، أو عاملاً يعالج قائمة انتظار، أو مهمة تكامل مستمر — بالفحوص الحتمية نفسها التي تجريها صفحة الرفع، واقرأ النتائج بصيغة JSON بالعربية أو الإنجليزية أو كلتيهما.
تحقق مسبق وفق متطلبات هيئة الزكاة والضريبة والجمارك المنشورة. والنتيجة PASS ليست اعتماداً من الهيئة: فمطابق لا يُصدر فاتورة ولا يرسلها، ولا يقدّم شيئاً للاعتماد أو للإبلاغ، ولا يتصل بمنصة «فاتورة».
إن كنت تشغّل حزمة أدوات التطوير SDK التي تنشرها الهيئة
واصل تشغيلها. فحزمة قواعد مطابق تقابل ملفات القواعد التي تأتي مع حزمة أدوات التطوير، وكل حالة اختبار خلفها مستند حكمت عليه حزمة أدوات التطوير أولاً، وتسرد صفحة التغطية كل رمز قاعدة فيها يفحصه مطابق وكل رمز لا يفحصه. وما يضيفه مطابق يأتي بعد الحكم: شرح كل ملاحظة بالعربية والإنجليزية مع الحقل الذي يُراجع في الربط، وتقرير يقرؤه زميل أو عميل دون أن تكون حزمة أدوات التطوير مثبتة لديه.
ومكانه، في صفحة الرفع وعبر هذه الواجهة البرمجية، هو التهيئة والدعم: مهمة تكامل مستمر على قالب التصدير، أو خطاف على النسخة التي تخرج قبل الإبلاغ، أو مكتب دعم يقرأ ملفاً مرفوضاً. وليس هو مسار التوقيع: فمطابق لا يوقّع ولا يختم ولا يرسل ولا يقدّم شيئاً للاعتماد، والنتيجة PASS منه ليست اعتماداً من الهيئة.
الوصول والمفاتيح
الواجهة البرمجية جزء من باقتي الفريق والشريك. ينشئ مالك المنشأة المفاتيح من صفحة مفاتيح API. يُعرض المفتاح مرة واحدة عند إنشائه، ولا يحتفظ مطابق إلا ببصمة SHA-256 منه، ويُرفض المفتاح الملغى فوراً. وكل فحص يتم بمفتاح يُحتسب من عمليات الفحص الشهرية للمنشأة كما يُحتسب الرفع تماماً.
ويتوقف المفتاح كذلك تلقائياً إذا غادر المالك الذي أنشأه المنشأة، أو أُزيل منها، أو حذف حسابه، أو جعل عضواً آخر مالكاً. وعندها ينشئ مالك آخر مفتاحاً جديداً، ولا سبيل إلى استعادة المفتاح القديم.
أرسل المفتاح في ترويسة Authorization مع كل طلب:
Authorization: Bearer mtb_…نقطة الطلب
POST /api/v1/validateمتن الطلب هو الملف نفسه لا نموذجاً، ويُرسل بأحد أنواع Content-Type الآتية: application/xml أو text/xml أو application/zip أو application/pdf أو application/octet-stream. وتحدد البايتات الأولى من الملف نوعه لا ترويسة Content-Type، ويُقرأ XML بترميز UTF-8 دون سواه.
- فاتورة XML واحدة، حتى 5 ميغابايت.
- ملف PDF يتضمن فاتورة XML، أي ملف PDF/A-3، حتى 10 ميغابايت: يُفحص ملف XML ولا يُفحص ملف PDF.
- أرشيف ZIP يضم حتى 100 فاتورة XML، حتى 20 ميغابايت، ويُجاب عنه بمدخل لكل فاتورة. وتُحتسب كل فاتورة عملية فحص واحدة.
ويُفحص الأرشيف كله في طلب واحد، ويجوز أن يستغرق هذا الطلب حتى 120 ثانية (قيمة maxDuration في المسار) قبل أن تنهيه المنصة؛ وحدّا عدد الملفات والحجم أعلاه هما ما يُبقي الدفعة دون ذلك بكثير.
قِيس على جهاز تطوير لا على بيئة الإنتاج: استغرق أرشيف ZIP يضم 100 فاتورة اصطناعية، بعضها يجتاز الفحص وبعضها لا، نحو 8 ثوانٍ عبر أداة الفحص، وهو وسيط ثلاث عمليات تشغيل. وبيئة الإنتاج أبطأ وتضيف بدء تشغيل بارداً، فاقرأ الرقم على أنه تقدير تقريبي لا وعد.
أضف إلى سلسلة الاستعلام المعامل lang بإحدى القيم en أو ar أو both لتختار لغة شرح كل ملاحظة. والقيمة الافتراضية en.
لتصنيف الفحص باسم عميل أو فرع أو ارتباط، أرسل الترويسة X-Mutabiq-Label: حتى 80 حرفاً، بنص UTF-8 مرمَّز بترميز النسبة المئوية كما تكتبه encodeURIComponent (والتسمية المكتوبة بأحرف ASCII وليس فيها % تُرسل كما هي). تُحفظ مع الفحص، وتظهر في سجل الفحوص، وتعود في الحقل label. اكتب فيها مرجعاً من عندك، لا قيمة من الفاتورة؛ ولا تُدوَّن في سجلات الخادم أبداً.
مثال
فحص إحدى الفواتير النموذجية التي ينشرها هذا الموقع، وفيها رقم التسجيل في ضريبة القيمة المضافة للبائع ينقصه رقم واحد: bad-04-vat-number-14-digits.xml
curl -X POST "https://mutabiq.hayatiq.ai/api/v1/validate?lang=en" \
-H "Authorization: Bearer $MUTABIQ_API_KEY" \
-H "Content-Type: application/xml" \
--data-binary @bad-04-vat-number-14-digits.xmlالرد الذي أعادته الواجهة لذلك الملف:
{
"source": "xml",
"verdict": "FAIL",
"rule_pack_version": "2.2.0",
"reference_date": "2026-09-27",
"zatca_sdk": "3.4.8",
"evaluated_at": "2026-09-27T09:14:05.212Z",
"build_id": "4757443f97d2b322bb98307284bfa750d0e67767",
"request_id": "7c0e4b1a-5d2f-4e8b-9a61-3f2d8c9b0e47",
"run_id": "3b9d2f64-8a1e-4c57-b0d3-6e2f91a4c8d5",
"label": null,
"coverage": {
"checked_codes": 96,
"total_codes": 242,
"zatca_sdk": "3.4.8"
},
"document_identity": {
"invoice_number": "MTB-G01",
"issue_date": "2026-09-01",
"invoice_type_code": "388",
"invoice_type_name": "0100000",
"seller_vat_number": "30000000000003",
"icv": "1"
},
"qr_consistent": null,
"findings": [
{
"rule_id": "MTB-BR-KSA-40",
"zatca_codes": [
"BR-KSA-40"
],
"severity": "ERROR",
"xpath": "//*[local-name()='AccountingSupplierParty']//*[local-name()='PartyTaxScheme']/*[local-name()='CompanyID']",
"observed_value_class": "not_15_digits",
"explanation_key": "br_ksa_40.not_15_digits",
"explanation": {
"en": {
"title": "Seller VAT registration number is not 15 digits",
"what_is_wrong": "The seller VAT registration number (BT-31), in cbc:CompanyID under the seller's VAT tax scheme, is not exactly 15 digits. ZATCA rejects it under BR-KSA-40, which requires 15 digits and nothing else - too few, too many, or any character that is not a digit all fail the same test.",
"what_to_check_in_your_erp": "Send the 15 digits alone: no spaces, no dashes, no VAT or SA prefix, and no group separators. The common causes are a number stored with formatting for display and exported as stored, a value padded or truncated to a fixed column width, and the commercial registration number being mapped into this field by mistake - it is a different number and usually 10 digits. Correct it in the company record or the mapping, not in the exported file."
}
}
}
]
}الرد
يُجاب عن الفحص الناجح بالرمز 200 وبصيغة JSON، وحقوله:
source- ما فُحص: xml، أو pdf_a3 (ملف XML المضمَّن في PDF)، أو zip.
verdict- PASS أو FAIL. وتعني FAIL أن ملاحظة واحدة على الأقل بدرجة ERROR؛ فالتحذير WARNING والإرشاد INFO لا يُسقطان مستنداً.
rule_pack_version- إصدار القواعد التي حكمت على المستند.
reference_date- التاريخ الذي قيس عليه تاريخ الإصدار في قاعدة التاريخ — انظر «الحتمية» أدناه.
zatca_sdk- إصدار حزمة أدوات التطوير SDK التي تنشرها الهيئة، وهو الإصدار الذي تحاكي قواعدُ rule_pack_version قواعدَه المنشورة.
evaluated_at- وقت إجراء الفحص، بصيغة ISO 8601 وبتوقيت UTC.
build_id- النسخة المنشورة من الخدمة (إيداع git الذي بُنيت منه)؛ وتكون unknown حين لا تذكرها جهة الاستضافة. ولا تُعطى إلا لمن يستدعي الواجهة البرمجية بمفتاح API، لأغراض الدعم؛ أما /api/health و/status العامتان فلا تذكران الإيداع.
request_id- معرِّف هذا الطلب، وهو نفسه ما في ترويسة X-Request-Id.
run_id- معرِّف الفحص المخزَّن، كما تعرضه صفحة التقرير وسجل الفحوص؛ ويكون null حين لم يُخزَّن شيء (أرشيف ZIP كل ملفاته ملفات PDF رُفضت قبل العثور على فاتورة).
label- تسمية الفحص المرسلة في الترويسة X-Mutabiq-Label كما خُزِّنت مع الفحص؛ وتكون null حين لم تُرسل تسمية أو لم يُخزَّن شيء.
coverage- عدد رموز القواعد في ملفات قواعد التحقق التي تنشرها الهيئة ويفحصها مطابق: checked_codes من أصل total_codes، وإصدار zatca_sdk الذي عُدّ عليه. وهي الأرقام نفسها في /rules/coverage، التي تسرد كل رمز غير مفحوص مع سبب عدم فحصه. ويكون null حين لا يكون rule_pack_version هو الحزمة التي عُدّ عليها هذا البناء.
document_identity- بيانات المستند الذي فُحص، مقروءةً منه لهذه الاستجابة وحدها، ولا تُخزَّن ولا تُسجَّل: invoice_number وissue_date وinvoice_type_code وinvoice_type_name وseller_vat_number وicv، كلٌّ كما يرد في المستند، أو null. وفي أرشيف ZIP يحمل كل عنصر بيانات مستنده.
qr_consistent- هل يتفق رمز الاستجابة السريعة مع الفاتورة التي تحمله: true حين فُكّ ترميز حمولته وطابقت الحقول من 1 إلى 5 ما في الفاتورة، وfalse حين تعذّر فكّ ترميزها أو غاب حقل أو خالف ما في الفاتورة (MTB-QR-00 إلى MTB-QR-05)، وnull حين لا رمز يُقارن. إشارة إلى جانب النتيجة لا جزء منها: تلك الملاحظات مصنَّفة تحذيراً لا خطأً، فقد تكون النتيجة PASS مع qr_consistent بقيمة false. في كل نتيجة مفردة وفي كل عنصر من الدفعة.
findings- عنصر لكل ملاحظة، بترتيب ثابت.
rule_id- معرّف القاعدة في مطابق، ولها صفحة خاصة بها تحت /rules.
zatca_codes- رمز قاعدة الهيئة أو رموزها التي تحاكيها القاعدة. ويكون فارغاً في الفحوص الخاصة بمطابق، كفحوص رمز الاستجابة السريعة وفحوص الأرشيف.
severity- ERROR أو WARNING أو INFO.
xpath- موضع الملاحظة في المستند.
observed_value_class- صورة القيمة التي وُجدت — empty وnot_15_digits وما شابههما — لا القيمة نفسها.
explanation_key- مفتاح نص الشرح، وهو ثابت في اللغتين.
explanation- الشرح مفهرساً باللغة: عنوان، وما الخطأ، وما الذي تتحقق منه في نظام ERP لديك. ويبيّن المدخل العربي كذلك هل مراجعته معلّقة أم معتمدة. ويكون null حيث لم يُكتب نص بعد.
summary- في أرشيف ZIP: عدد المدخلات في الأرشيف، وعدد الفواتير الناجحة وغير الناجحة، وعدد المدخلات التي تُخطيت لأنها ليست فاتورة XML ولا ملف PDF.
entries- في أرشيف ZIP: عنصر لكل فاتورة، فيه موضعها في الأرشيف، واسم ملفها كما يورده الأرشيف، ونتيجتها، وملاحظاتها.
الأخطاء
الرفض رد بصيغة JSON فيه رمز خطأ ثابت، ومعه reason حين يُرفض ملف PDF أو أرشيف. ولا يُسجَّل شيء ولا يُحتسب عند الرفض.
error | الحالة | المعنى |
|---|---|---|
missing_api_key | 401 | لا توجد في الطلب ترويسة Authorization تحمل المفتاح بعد كلمة Bearer. |
invalid_api_key | 401 | المفتاح ليس مما أصدره مطابق، أو كُتب خطأً. |
api_key_revoked | 401 | ألغى مالك المنشأة هذا المفتاح. |
monthly_limit_reached | 402 | استُنفدت عمليات الفحص المتاحة للمنشأة هذا الشهر. |
api_not_in_plan | 403 | باقة المنشأة لا تشمل الواجهة البرمجية. |
zip_not_in_plan | 403 | باقة المنشأة لا تشمل دفعات ZIP. |
invalid_lang | 400 | قيمة lang ليست en ولا ar ولا both. |
invalid_label | 400 | قيمة الترويسة X-Mutabiq-Label ليست تسمية يحفظها مطابق: أطول من 80 حرفاً، أو ليست نص UTF-8 مرمَّزاً بترميز النسبة المئوية، أو فيها محرف تحكم أو محرف صفري العرض أو محرف اتجاه. |
empty_body | 400 | الطلب بلا متن. |
not_utf8 | 400 | ملف XML ليس بترميز UTF-8 صحيح. |
encoding_not_utf8 | 400 | يعلن ملف XML ترميزاً غير UTF-8، أو يبدأ بعلامة ترتيب البايتات لترميز غيره. |
nul_byte | 400 | في ملف XML محرف NUL. |
pdf_rejected | 400 | تعذّرت قراءة ملف PDF، أو لا يتضمن فاتورة XML واحدة بعينها. وتبيّن reason السبب. |
batch_rejected | 400 | تعذّرت قراءة الأرشيف، أو لا يضم فاتورة XML. وتبيّن reason السبب. |
body_too_large | 413 | الطلب أكبر من أكبر ملف تقبله الواجهة. |
file_too_large | 413 | الملف أكبر من الحد المقرر لنوعه. |
unsupported_media_type | 415 | ترويسة Content-Type غائبة، أو ليست من الأنواع المقبولة. |
rate_limited | 429 | تجاوز هذا المفتاح عدد الطلبات المسموح به في الدقيقة الحالية. |
engine_failed | 500 | تعذّر إجراء الفحص من جهتنا. حاول مرة أخرى. |
storage_failed | 500 | تعذّر تسجيل النتيجة من جهتنا. حاول مرة أخرى. |
auth_unavailable | 500 | تعذّر التحقق من المفتاح من جهتنا. حاول مرة أخرى. |
حدود المعدل
لكل مفتاح 60 طلباً في الدقيقة. ويحمل كل رد على طلب بمفتاح صالح الترويسات X-RateLimit-Limit وX-RateLimit-Remaining وX-RateLimit-Reset، والأخيرة بثواني يونكس؛ ويحمل الرد 429 كذلك Retry-After بالثواني. ويُحتسب الحد لكل مفتاح لا لكل عنوان، فالأنظمة التي تشترك في عنوان واحد لا تستهلك حصص بعضها. أما عمليات الفحص الشهرية فمستقلة عن ذلك: متى استُنفدت كان الرد 402 حتى يبدأ الشهر التالي بتوقيت الرياض أو تتغير الباقة.
ويحمل كل رد ناجح كذلك الترويسات X-Usage-Limit وX-Usage-Used وX-Usage-Reset: ففي X-Usage-Limit عدد عمليات الفحص الشهرية التي تتيحها باقتك، وفي X-Usage-Used عدد ما احتُسب منها هذا الشهر بما فيه هذا الطلب، وفي X-Usage-Reset موعد بدء العدّ من جديد، وهو منتصف ليل اليوم الأول من الشهر التالي بتوقيت الرياض، بصيغة ISO 8601. وإذا تعذّرت قراءة العدّ لم يحمل الرد أياً من الثلاث.
ويقصر جدار الحماية (Firewall) عند حافة الشبكة كذلك طلبات /api/v1/ على 120 طلباً في كل 60 ثانية لكل عنوان IP، ويرد عليها بالرمز 429 مجرّداً: لا متن JSON ولا Retry-After ولا أي من الترويسات أعلاه. فعامِل أي رد 429 على أنه إشارة إلى التمهّل.
ترقيم الإصدارات
يمثل v1 بنية الرد، ولا يتغير إلا بإضافة حقول؛ أما حذف حقل أو تغيير اسمه فيكون في v2. وللقواعد إصدارها المستقل، وهو rule_pack_version: يتقدم رقمه الأوسط كلما أُضيفت قاعدة أو حُذفت أو تغيّرت درجتها، ويتقدم رقمه الأخير حين يتغير فحص قاعدة دون أن يتغير ما تُبلغ عنه. وكل إصدار مدوّن في سجل التغييرات.
تغييرات الواجهة البرمجية
كل إصدار من بنية الرد، ويوم صدوره، والحقول التي أضافها. ولم يُحذف شيء.
1.4.0- الحقل label في كل نتيجة: تسمية الفحص المرسلة في الترويسة الاختيارية X-Mutabiq-Label؛ والخطأ invalid_label.
1.3.0- الحقل qr_consistent في كل نتيجة مفردة وكل عنصر من الدفعة: هل يتفق رمز الاستجابة السريعة مع الفاتورة، إلى جانب النتيجة.
1.2.0- الحقل coverage في كل نتيجة: عدد رموز القواعد في ملفات قواعد التحقق التي تنشرها الهيئة ويفحصها مطابق.
1.1.0- الحقول zatca_sdk وevaluated_at وbuild_id وrequest_id وrun_id في كل نتيجة؛ وdocument_identity؛ والترويسات X-Request-Id وX-Usage-*.
1.0.0- أول واجهة برمجية تعمل بمفتاح.
الحتمية
تصدر النتيجة عن شيفرة حتمية وحدها، ولا يقررها نموذج لغوي. والملف نفسه، بإصدار rule_pack_version نفسه وتاريخ reference_date نفسه، ينال النتيجة نفسها. وreference_date هو التاريخ بتوقيت الرياض (Asia/Riyadh) وقت فحص الطلب، وهو ما تقيس عليه القاعدة الوحيدة المعتمدة على التاريخ، MTB-BR-KSA-04، تاريخ إصدار الفاتورة.
وتفحص حزمة القواعد نفسها الرموز نفسها: فالحزمة 2.2.0 تفحص 96 من أصل 242 من رموز القواعد في ملفات قواعد التحقق ضمن الإصدار 3.4.8 من حزمة أدوات التطوير SDK التي تنشرها الهيئة، ويذكر كل رد هذه النسبة في الحقل coverage. ما الذي لا يُفحص، ولماذا.
ما يُحفظ وما لا يُحفظ أبداً
يُفحص الملف في الذاكرة ويُتخلص منه عند إرسال الرد، ولا يُكتب أبداً على قرص ولا في قاعدة بيانات ولا في سجل، ولا اسم مدخل ZIP كذلك. ويحفظ مطابق لكل فحص ما يخلّفه الرفع: النتيجة، وعدد الملفات، ورمز نوع الفاتورة، وتسمية الفحص إن أرسلتها، ولكل ملاحظة معرّف القاعدة ودرجتها ومسار XPath وصورة القيمة ومفتاح الشرح، مع موضع المدخل في أرشيف ZIP. ولا تحمل السجلات إلا معرّفات القواعد والأعداد والأزمنة. ولا يُحفظ المفتاح إلا بصمةً بخوارزمية SHA-256. والتفاصيل في سياسة الخصوصية.
OpenAPI
وصف هذه الواجهة بمواصفة OpenAPI 3.1، لتوليد عميل برمجي منه، في /api/v1/openapi.json.