# Kosoor Merchant API — كسور للمطورين > يتيح لك Kosoor Merchant API ربط نظامك ببيانات منشأة تستخدم كسور: العملاء وفواتير المبيعات والمدفوعات والإشعارات الدائنة. رابط الـ API هو `https://api.kosoor.com.sa`، وكل المسارات تبدأ بـ `/v1`، والطلبات والردود بصيغة JSON وأسماء الحقول بصيغة camelCase، عبر HTTPS فقط. الدعم: عبر واتساب فقط على الرقم [+966 12 606 1770](https://wa.me/966126061770)، بلا مكالمات ولا بريد إلكتروني. أرفق الـ `Request-Id` للطلب الذي تسأل عنه. ## تعليمات لوكلاء الذكاء الاصطناعي (AI agents) - أرسل مع كل طلب الـ Header `Authorization: Bearer `. التاجر هو من يصدر المفتاح، ولا توجد طريقة دخول أخرى. - ابنِ واختبر بمفتاح اختبار (`ksr_test_...`)، فهو لا يصل إلا إلى بيئة الاختبار الخاصة بالتاجر. تأكد أثناء البناء أن كل رد يحمل الـ Header `Kosoor-Mode: test`، ولا تنتقل إلى المفتاح الفعلي (`ksr_live_...`) إلا عندما يكون التاجر جاهزاً للتشغيل الفعلي. - أرسل الـ Header `Idempotency-Key` مع كل طلب `POST` و`PATCH`. أنشئ مفتاحاً واحداً لكل عملية (UUID يكفي)، وأرسل المفتاح نفسه عند إعادة محاولة العملية نفسها. - اعتمد على `code` في الخطأ، وليس على `message`، لأن `message` نص موجّه للمستخدم وتتغيّر لغته حسب `Accept-Language`. - خذ معرّفات الفروع والأصناف وطرق الدفع من Endpoints الكتالوج: `GET /v1/branches` و`GET /v1/items` و`GET /v1/payment-methods`. لا تخمّنها ولا تكتبها ثابتة في الكود. - أرسل الحقول والـ Query Parameters الموثّقة فقط، وأي شيء آخر يُرفض بـ `400 unknown_parameter`. - اقرأ المبالغ من الرد وليس من طلبك: الخصم الذي ينزل بالسعر عن الحد الأدنى للصنف يُخفَّض، والأسعار الشاملة للضريبة تُحوَّل وتُقرَّب إلى الهللة. - اقرأ `zatca.status` في كل فاتورة مبيعات وإشعار دائن. القيمة `pending` ليست فشلاً. - إذا حدث خطأ لا تشرحه الصفحات، اطلب من المستخدم التواصل مع دعم كسور عبر واتساب (+966 12 606 1770، https://wa.me/966126061770) مع الـ `Request-Id`. # الأدلة ## ابدأ من هنا > ما هو كسور، ومن يعطيك صلاحية الوصول، والمصطلحات التي يستخدمها الـ API، وكيف تظهر الفوترة الإلكترونية في السعودية (ZATCA) فيه. اقرأ هذه الصفحة أولاً إذا لم تعمل مع كسور من قبل، أو لم تتعامل مع الفوترة الإلكترونية في السعودية. ### ما هو كسور كسور نظام نقاط بيع وإدارة أعمال للمنشآت في السعودية، وكثير منها مغاسل سيارات ومراكز خدمة. تبيع المنشأة عن طريق كسور من نقطة البيع، ومن الكشك الذاتي (Kiosk)، ومن متجرها الإلكتروني، ومن لوحة تحكم كسور. ويحفظ كسور عملاءها وفواترها ومدفوعاتها وحساباتها، ويرسل فواترها إلى هيئة الزكاة والضريبة والجمارك (ZATCA). يتيح Merchant API لنظام آخر أن يقرأ هذه البيانات ويكتب فيها: العملاء وفواتير المبيعات والمدفوعات والإشعارات الدائنة. ### التاجر والشريك - **التاجر (Merchant)**: منشأة تستخدم كسور، وبياناتها في حسابها الخاص في كسور. - **الشريك (Partner)**: أنت. شركتك تبني نظاماً، مثل ERP أو تطبيق حجوزات أو بوابة لأساطيل السيارات، يعمل مع بيانات التاجر عن طريق هذا الـ API. كل مفتاح API مرتبط بتاجر واحد. وإذا كنت تعمل مع أكثر من تاجر، فلكل تاجر مفتاح خاص به. ### كيف يتم الوصول 1. يُصدر التاجر مفتاح API من لوحة تحكم كسور، من **الإعدادات ← المطوّرون**، ثم يعطيك إياه. الخطوات بالتفصيل في [ربط تاجر](https://docs.kosoor.com.sa/ar/onboarding.md). 2. ترسل المفتاح مع كل طلب. المفتاح وحده يحدد التاجر الذي يخصه الطلب، فلا ترسل أي معرّف للتاجر، ولا تحتاج حساباً أو تسجيل دخول خاصاً بك في كسور. 3. يحمل المفتاح الصلاحيات التي منحها التاجر فقط، وقد يكون مقصوراً على بعض فروعه. ويستطيع التاجر إبطاله في أي وقت. هناك نوعان من المفاتيح. مفتاح الاختبار (`ksr_test_...`) لا يصل إلا إلى بيئة الاختبار الخاصة بالتاجر، وهي نسخة منفصلة من بياناته الأساسية، وعليه تبني الربط. أما المفتاح الفعلي (`ksr_live_...`) فيصل إلى بيانات التاجر الحقيقية. التفاصيل في [وضع الاختبار](https://docs.kosoor.com.sa/ar/test-mode.md). ### مصطلحات الـ API | المصطلح | المعنى | |---|---| | الفرع (Branch) | أحد مواقع التاجر، مثل محل أو مركز خدمة. كل فاتورة ودفعة تتبع فرعاً، وقد يكون المفتاح مقصوراً على فروع معيّنة. | | الصنف (Item) | ما يبيعه التاجر: منتج أو خدمة أو اشتراك. بند الفاتورة يحدد الصنف بمعرّفه. | | نقطة البيع (POS) | مكان البيع في الفرع، حيث يستلم الكاشير المبلغ. البيع من هناك يظهر في الـ API بقيمة `createdVia: "pos"`. | | طريقة الدفع (Payment method) | الطريقة التي يدفع بها العميل كما أعدّها التاجر، مثل النقد أو البطاقة. | | الخزينة (Treasury) | مكان المال في دفاتر التاجر، مثل صندوق نقدي أو حساب بنكي. كل طريقة دفع تُرحَّل إلى خزينة، وقد تختلف الخزينة من فرع لآخر. | | مركز التكلفة (Cost centre) | تصنيف يستخدمه محاسب التاجر لتجميع الإيرادات والتكاليف، مثل قسم أو مشروع. ويمكن أن تحمل الفاتورة مركز تكلفة في `costCentreId`. | | حجم السيارة (Car size) | كثير من التجار يسعّرون الخدمة حسب حجم السيارة. الصنف المسعّر حسب حجم السيارة ليس له سعر واحد في الكتالوج، فيجب أن يرسل بنده في الفاتورة `unitPrice`. | | الاشتراك (Subscription) | باقة زيارات يشتريها العميل، مثل عدد من غسلات السيارة. الـ API لا يبيع الاشتراكات، والبند الذي يحمل اشتراكاً يرجع `422 item_not_sellable`. | | العميل (Customer) | فرد أو شركة. كل فاتورة تُنشأ عن طريق الـ API يجب أن تحدد عميلاً. | | الريال (SAR) | الريال السعودي، وكل المبالغ في الـ API بالريال (`currency: "SAR"`). | | الهللة (Halala) | جزء من مئة من الريال. تحفظ كسور المبالغ مقرّبة إلى الهللة، أي بمنزلتين عشريتين على الأكثر. | | ضريبة القيمة المضافة (VAT) | النسبة الأساسية في السعودية 15%. ولكل صنف في كسور نسبته الخاصة، ويعرضها كل بند في الفاتورة في `vatRate`، مثل `15`. | | الرقم الضريبي (VAT number) | رقم تسجيل المنشأة في ضريبة القيمة المضافة: 15 رقماً تبدأ بـ 3 وتنتهي بـ 3. | | السجل التجاري (CR) | رقم تسجيل الشركة في السعودية. والعميل من نوع شركة بلا رقم ضريبي يمكن تعريفه بالسجل التجاري بدلاً منه (`idType: "CRN"`). | ### هيئة الزكاة والضريبة والجمارك في خمس دقائق منصة الفوترة الإلكترونية التابعة لهيئة الزكاة والضريبة والجمارك (ZATCA) اسمها «فاتورة» (Fatoora)، وتستقبل كل فاتورة وإشعار دائن تصدره منشأة سعودية. كسور يتولى هذا الجزء عن التاجر: لا تتصل أنت بالهيئة، ولا توقّع ملف XML، ولا تبني رمز QR. كل ما عليك هو قراءة النتيجة. #### الفاتورة الضريبية والمبسطة عند الهيئة نوعان من الفواتير، وفي كسور نوع العميل هو الذي يحدد نوع الفاتورة: | | الفاتورة الضريبية (standard) | الفاتورة المبسطة (simplified) | |---|---|---| | العميل | شركة | فرد | | إجراء الهيئة | **الاعتماد (clearance)**: تفحص الهيئة الفاتورة وتعتمدها قبل أن تصل إلى المشتري | **التبليغ (reporting)**: تصدر الفاتورة أولاً ثم تُبلَّغ بها الهيئة | | في الـ API | تعتمدها كسور قبل أن ترد عليك | تبلّغ بها كسور بعد أن ترد عليك | ويوضح الحقل `zatca.kind` في الرد نوعها: `standard` أو `simplified`. والإشعار الدائن من نوع الفاتورة التي صدر عليها. #### معنى كل حالة | `zatca.status` | المعنى | |---|---| | `pending` | لم تقبل الهيئة المستند بعد. المستند المبسط يبقى `pending` حتى تبلّغ به كسور. أما الضريبي فلا يكون `pending` إلا إذا لم ترد الهيئة في الوقت المحدد، وتعيد كسور إرساله لاحقاً. | | `cleared` | اعتمدت الهيئة هذا المستند الضريبي، ويحمل `zatca.qr` رمز QR الذي أرجعته الهيئة. | | `reported` | قبلت الهيئة التبليغ بهذا المستند المبسط. | | `rejected` | رفضت الهيئة المستند، ورسائلها في `zatca.errors`. | الحالة `pending` ليست فشلاً. المستند صادر، ورقمه ومبالغه لا تتغير. والمستند الضريبي الذي ترفضه الهيئة أثناء الاعتماد لا يُحفظ أصلاً، ويرجع الطلب `422 zatca_rejected`. لذلك لا يصبح المستند المحفوظ `rejected` إلا بعد أن كان `pending`. ماذا تفعل مع كل حالة موضح في [نتائج الهيئة](https://docs.kosoor.com.sa/ar/zatca.md). وفي بيئة الاختبار تُوقَّع المستندات بشهادات الاختبار من الهيئة، ولا تصل أبداً إلى سجلات التاجر الفعلية لديها. ### الخطوة التالية 1. [مقدمة](https://docs.kosoor.com.sa/ar/index.md): رابط الـ API والقواعد التي يتبعها كل طلب. 2. [ربط تاجر](https://docs.kosoor.com.sa/ar/onboarding.md): من لوحة تحكم التاجر إلى أول طلب ترسله. 3. [تسجيل عملية بيع](https://docs.kosoor.com.sa/ar/record-a-sale.md): الخطوات كاملة، مع كل طلب ورد. المصدر: https://docs.kosoor.com.sa/ar/start-here --- ## مقدمة > رابط الـ API، وكيف تحصل على مفتاح، وأول طلب، والقواعد التي يتبعها كل طلب. يتيح لك Kosoor Merchant API ربط نظامك ببيانات التاجر في كسور. يمكنك إدارة العملاء، وإصدار فواتير المبيعات، وتسجيل المدفوعات، وإصدار الإشعارات الدائنة. إذا كانت هذه أول مرة تتعامل فيها مع كسور أو مع الفوترة الإلكترونية في السعودية، فاقرأ [ابدأ من هنا](https://docs.kosoor.com.sa/ar/start-here.md) أولاً. ### رابط الـ API ```text https://api.kosoor.com.sa ``` تبدأ جميع الـ Endpoints بـ `/v1`، ويتم الاتصال عبر HTTPS فقط. الطلبات والردود بصيغة JSON، وأسماء الحقول بصيغة camelCase. ### الحصول على مفتاح API يُنشئ التاجر المفتاح من لوحة تحكم كسور، من **الإعدادات ← المطوّرون ← مفاتيح الربط**، ثم يرسله إليك. الخطوات بالتفصيل في [ربط تاجر](https://docs.kosoor.com.sa/ar/onboarding.md). كل مفتاح مرتبط بتاجر واحد، ويحمل الصلاحيات التي اختارها التاجر فقط، وقد يكون مقصوراً على فروع معيّنة. التفاصيل في صفحة [المصادقة](https://docs.kosoor.com.sa/ar/authentication.md). ### أول طلب ```bash curl https://api.kosoor.com.sa/v1/customers?limit=1 \ -H "Authorization: Bearer ksr_live_..." ``` ```json { "data": [], "hasMore": false } ``` ### كل كائن يحدد نوعه كل كائن يرجعه الـ API يحمل الحقل `object`، وهو نص ثابت يحدد نوع الكائن، مثل `customer` أو `salesInvoice` أو `payment` أو `creditNote`، وقيمته لا تتغير للنوع الواحد. استخدمه عندما يستقبل جزء واحد من الكود عندك أكثر من نوع، مثل حفظ الردود أو تسجيلها، بدل أن تستنتج النوع من الرابط أو من الحقول الموجودة. ```json { "id": "3f1c2a6e-8b4d-4e2a-9c1f-5d7e8a9b0c12", "object": "salesInvoice", "number": "INV-1042" } ``` ### قواعد عامة لكل الطلبات - **`Request-Id`**: يصلك في كل Response، سواء نجح الطلب أو فشل. أرفقه عند التواصل مع [دعم كسور](#support) بخصوص أي طلب. - **اللغة**: أرسل `Accept-Language: ar` لتصلك رسائل الأخطاء بالعربية، وإلا فستصلك بالإنجليزية. اللغة تغيّر نص الرسالة فقط، أما الأكواد وأسماء الحقول فلا تتغيّر. - **تحقق صارم من المدخلات**: أي حقل في الـ Body أو أي Query Parameter لا يعرفه الـ API يُرفض بالكود `unknown_parameter`، حتى لا يمرّ خطأ إملائي دون أن تنتبه له. - **الـ Body**: يجب أن يكون `Content-Type: application/json`، وألا يتجاوز حجمه 1 ميغابايت. - **طلبات الإنشاء والتعديل**: كل طلب `POST` و`PATCH` يجب أن يحمل الـ Header `Idempotency-Key`، حتى لا تؤدي إعادة المحاولة إلى تسجيل العملية مرتين. التفاصيل في [منع تكرار الطلبات](https://docs.kosoor.com.sa/ar/idempotency.md). - **الأخطاء**: جميع الأخطاء ترجع بالشكل نفسه، ومعها `code` ثابت يمكنك الاعتماد عليه في الكود. القائمة الكاملة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). ### الدعم دعم كسور متاح عبر واتساب فقط على الرقم [+966 12 606 1770](https://wa.me/966126061770)، بلا مكالمات ولا بريد إلكتروني. أرفق الـ `Request-Id` للطلب الذي تسأل عنه. المصدر: https://docs.kosoor.com.sa/ar/ --- ## ربط تاجر > كل الخطوات من دخول التاجر إلى لوحة تحكم كسور حتى أول طلب ترسله بمفتاح اختبار، ثم التشغيل الفعلي. يقوم التاجر بالخطوات الأولى من لوحة تحكم كسور، وتكمل أنت الباقي. إذا لم يُصدر التاجر مفتاحاً من قبل، فأرسل له هذه الصفحة. ### 1. يدخل التاجر إلى لوحة التحكم يدخل التاجر إلى لوحة تحكم كسور بمستخدم يملك الصلاحية `integration.api_keys.management`. وبدونها لا تظهر **المطوّرون** في قائمة **الإعدادات**. ### 2. يفتح الإعدادات ← المطوّرون في صفحة **المطوّرون** خمسة تبويبات: | التبويب | ماذا فيه | |---|---| | **نظرة عامة** | مفاتيح التاجر، وبيئة الاختبار، والطلبات خلال آخر 30 يوماً | | **مفاتيح الربط** | كل المفاتيح: إصدارها وتعديلها وإبطالها | | **سجل الطلبات** | كل طلب أرسله مفتاح خلال آخر 30 يوماً | | **Webhooks** | الـ Endpoints التي سجّلتها المفاتيح، وكل حدث أُرسل إليها. التفاصيل في [Webhooks](https://docs.kosoor.com.sa/ar/webhooks.md) | | **بيئة الاختبار** | النسخة المنفصلة من البيانات الأساسية التي تصل إليها مفاتيح الاختبار | ![صفحة المطوّرون على تبويب نظرة عامة](https://docs.kosoor.com.sa/ar/img/onboarding/developers-overview.ar.png.md) ### 3. ينشئ بيئة الاختبار من تبويب **بيئة الاختبار** يضغط التاجر **إنشاء بيئة الاختبار**. تنسخ كسور إليها فروع التاجر وأصنافه وطرق الدفع وشجرة الحسابات، ولا تنسخ عملاءه ولا فواتيره. يستغرق التجهيز بضع دقائق، وترجع مفاتيح الاختبار `503 sandbox_unavailable` حتى يظهر في التبويب **بيئة الاختبار جاهزة**. تحتفظ بيئة الاختبار بالمعرّفات الفعلية لما تنسخه، فمعرّف الفرع أو الصنف أو طريقة الدفع الذي تستخدمه في بيئة الاختبار هو نفسه في البيانات الفعلية. أما العملاء فلا يُنسخون، فمعرّفاتهم مختلفة. ![تبويب بيئة الاختبار وهي جاهزة](https://docs.kosoor.com.sa/ar/img/onboarding/sandbox-ready.ar.png.md) ### 4. يُصدر مفتاح اختبار من تبويب **مفاتيح الربط** يضغط التاجر **إصدار مفتاح** ويعبّئ النموذج: | الحقل | ماذا يختار | |---|---| | **نوع المفتاح** | **اختبار**. مفتاح الاختبار لا يصل إلا إلى بيئة الاختبار، ولا يمكن اختياره إلا بعد إنشائها. | | **اسم المفتاح** | اسم نظامك. بالاسم يعرف التاجر أيّ نظام يستعمل المفتاح يوم يحتاج إلى إبطاله. | | **الصلاحيات** | لكل مورد: **بلا** أو **قراءة** أو **قراءة وكتابة**. امنح أقل ما يكفي، والجدول في الأسفل يوضح ما تحتاجه كل مهمة. | | **الفروع** | الفروع التي يعمل فيها المفتاح. إذا تُركت كلها فارغة يعمل المفتاح في جميع الفروع. | | **ينتهي في** | اختياري. يتوقف المفتاح عند بداية هذا اليوم، وترسل كسور تذكيراً بالبريد قبله بـ 14 يوماً ثم بـ 3 أيام. | ![نموذج إصدار مفتاح مع اختيار اختبار](https://docs.kosoor.com.sa/ar/img/onboarding/issue-test-key.ar.png.md) يظهر المفتاح مرة واحدة فقط، ومعه التنبيه **انسخ المفتاح الآن: لن يظهر مرة أخرى**. ينسخه التاجر ويرسله إليك. وبعد إغلاق هذه الرسالة لا يمكن عرض المفتاح مرة أخرى: إذا فُقد، يبطله التاجر ويصدر مفتاحاً جديداً. ولا يزيد عدد المفاتيح النشطة لدى التاجر على 50 مفتاحاً من كل نوع. ![المفتاح الجديد يظهر مرة واحدة مع زر النسخ](https://docs.kosoor.com.sa/ar/img/onboarding/key-shown-once.ar.png.md) #### الصلاحيات التي تحتاجها كل مهمة | المهمة | الصلاحيات | |---|---| | قراءة الكتالوج: الفروع والأصناف وطرق الدفع | `branches:read` و `items:read` و `payment_methods:read` | | البحث عن العملاء وإنشاؤهم | `customers:write` | | إصدار فواتير المبيعات | `sales_invoices:write` | | تسجيل المدفوعات بعد البيع | `payments:write` | | إصدار الإشعارات الدائنة | `credit_notes:write` | | القراءة فقط، مثل نسخ البيانات إلى نظام ERP | مستوى `read` لكل مورد تقرؤه | صلاحية `write` تشمل `read`. والطلب الذي لا يملك المفتاح صلاحيته يرجع `403 missing_permission`، ويوضح الحقل `requiredPermission` في الخطأ الصلاحية المطلوبة. التفاصيل في [المصادقة](https://docs.kosoor.com.sa/ar/authentication.md). ### 5. أرسل أول طلب اقرأ أحد العملاء الموجودين في كل بيئة اختبار، بمفتاح الاختبار: ```bash curl -i https://api.kosoor.com.sa/v1/customers/5a4db0c5-0000-4000-8000-000000000001 \ -H "Authorization: Bearer ksr_test_..." ``` ```http HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Kosoor-Mode: test Request-Id: req_01JC3W8Z9Q4M2T7XKD5R6VBN0P ``` ```json { "id": "5a4db0c5-0000-4000-8000-000000000001", "object": "customer", "type": "person", "referenceId": null, "firstName": "Sandbox Person", "lastName": null, "phone": "+966500000001", "email": null, "isActive": true, "branchId": null, "company": null, "address": { "street": null, "buildingNumber": null, "postalCode": null, "district": null, "city": null, "country": null }, "metadata": {}, "standardInvoiceMissing": null, "createdAt": "2026-11-07T09:12:40.513Z" } ``` الـ Header `Kosoor-Mode: test` يعني أن الطلب وصل إلى بيئة الاختبار. وردود المفتاح الفعلي لا تحمله. ### 6. ابحث عن الطلب في سجل الطلبات في تبويب **سجل الطلبات** يرى التاجر كل طلب أرسله مفتاح خلال آخر 30 يوماً، الأحدث أولاً. ويمكن التصفية حسب نوع المفتاح (**فعلي** أو **اختبار**) والمفتاح والحالة والمسار والتاريخ. ويعرض زر **التفاصيل** الـ `Request-Id` للطلب. ولا يُحفظ جسم الطلب والرد إلا للطلبات المرفوضة والفاشلة. ![تبويب سجل الطلبات وتفاصيل الطلب الأول مفتوحة](https://docs.kosoor.com.sa/ar/img/onboarding/request-log.ar.png.md) ### 7. ابنِ الربط على بيئة الاختبار ابنِ كل الخطوات بمفتاح الاختبار: [تسجيل عملية بيع](https://docs.kosoor.com.sa/ar/record-a-sale.md)، و[استرجاع أو تصحيح عملية بيع](https://docs.kosoor.com.sa/ar/refunds.md)، وكل ما يقوم به نظامك. وفي بيئة الاختبار عملاء جاهزون بنتائج معروفة، فتستطيع اختبار رفض الهيئة وحالات الرفض الأخرى دون أن تجهّز البيانات بنفسك. التفاصيل في [وضع الاختبار](https://docs.kosoor.com.sa/ar/test-mode.md). ### 8. التشغيل الفعلي عندما يعمل نظامك على بيئة الاختبار: 1. يُصدر التاجر مفتاحاً ثانياً، **نوع المفتاح** فيه **فعلي**، بالصلاحيات والفروع نفسها. 2. تستبدل مفتاح الاختبار بالمفتاح الفعلي. ولا يتغير شيء آخر: نفس الـ Host ونفس المسارات ونفس معرّفات الفروع والأصناف وطرق الدفع. 3. ترسل طلباً فعلياً واحداً، ويتحقق منه التاجر في **سجل الطلبات** تحت **فعلي**. راجع [قائمة التحقق قبل التشغيل الفعلي](https://docs.kosoor.com.sa/ar/go-live.md) قبل أول عملية بيع حقيقية. ولإيقاف مفتاح، يبطله التاجر من تبويب **مفاتيح الربط**، فيتوقف فوراً كل نظام يستعمله، ويرجع `401 revoked_api_key`. المصدر: https://docs.kosoor.com.sa/ar/onboarding --- ## البناء بمساعدة الذكاء الاصطناعي > نسخ Markdown والملفان llms.txt و llms-full.txt التي يقدمها الموقع لأدوات الذكاء الاصطناعي، والأزرار في كل صفحة، والـ Agent skill التي يمكنك تثبيتها. كل صفحة في هذا الموقع متاحة أيضاً بصيغة Markdown، حتى يقرأ وكيل البرمجة (AI coding agent) أو المساعد في المحادثة التوثيق دون تخمين. ### ملفات لأدوات الذكاء الاصطناعي | الملف | ماذا فيه | |---|---| | [`/ar/llms.txt`](https://docs.kosoor.com.sa/ar/llms.txt) | فهرس لكل الصفحات، وفي أوله تعليمات لوكلاء الذكاء الاصطناعي | | [`/ar/llms-full.txt`](https://docs.kosoor.com.sa/ar/llms-full.txt) | كل الأدلة وكل عقود الـ Endpoints (المسارات والـ Parameters وحقول الطلب والرد) في ملف واحد | | أي صفحة مع إضافة `.md` | الصفحة بصيغة Markdown، مثل [`/ar/record-a-sale.md`](https://docs.kosoor.com.sa/ar/record-a-sale.md)، والصفحة الرئيسية هي `/ar/index.md` | | [`/skill/kosoor-merchant-api/SKILL.md`](https://docs.kosoor.com.sa/skill/kosoor-merchant-api/SKILL.md) | الـ Agent skill: قواعد هذا الـ API في ملف قصير واحد | وللموقع الإنجليزي نسخه الخاصة بدون `/ar`، مثل `/llms.txt`. أما الـ Skill فملف واحد بالإنجليزية. ### في كل صفحة فوق كل صفحة أربعة أزرار: - **نسخ الصفحة بصيغة Markdown** ينسخ الصفحة جاهزة للصقها في محادثة. - **عرض بصيغة Markdown** يفتح نسخة الصفحة بصيغة `.md`. - **فتح في Claude** و **فتح في ChatGPT** يبدآن محادثة تقرأ الصفحة. ### تثبيت الـ Agent skill تعطي الـ Skill وكيل البرمجة أهم القواعد عندما يكتب كوداً يتعامل مع هذا الـ API: أن يرسل `Idempotency-Key`، وأن يعتمد على `code`، وأن يبني بمفتاح اختبار، وأن يأخذ المعرّفات من الكتالوج، وأين يقرأ الباقي. في Claude Code، احفظها كـ Skill للمشروع، فيحصل عليها كل من يعمل على المستودع: ```bash mkdir -p .claude/skills/kosoor-merchant-api curl -fsSL https://docs.kosoor.com.sa/skill/kosoor-merchant-api/SKILL.md \ -o .claude/skills/kosoor-merchant-api/SKILL.md ``` أو كـ Skill شخصية لكل المشاريع على جهازك: ```bash mkdir -p ~/.claude/skills/kosoor-merchant-api curl -fsSL https://docs.kosoor.com.sa/skill/kosoor-merchant-api/SKILL.md \ -o ~/.claude/skills/kosoor-merchant-api/SKILL.md ``` يحمّل Claude Code الـ Skill عندما تتعلق المهمة بـ Kosoor API. ولأي وكيل آخر، أضف محتوى الملف إلى التعليمات التي يقرؤها، أو وجّهه إلى `https://docs.kosoor.com.sa/ar/llms.txt`. ### أعطِ الوكيل مفتاح اختبار دع الوكيل يبني ويجرّب على بيئة الاختبار بمفتاح اختبار (`ksr_test_...`)، وليس بمفتاح فعلي أبداً. وضع المفتاح في متغير بيئة (Environment variable) أو في ملف محلي يتجاهله المستودع، وليس في الـ Prompt ولا في الكود. التفاصيل في [وضع الاختبار](https://docs.kosoor.com.sa/ar/test-mode.md). المصدر: https://docs.kosoor.com.sa/ar/ai-agents --- ## تسجيل عملية بيع > خطوات عملية البيع كاملة مع كل طلب ورد، من قراءة الكتالوج والبحث عن العميل أو إنشائه، إلى إصدار الفاتورة واستلام المبلغ عند الإصدار أو لاحقاً. عملية البيع عن طريق الـ API أربع خطوات. وكل خطوة في الأسفل تعرض الطلب كاملاً والرد كاملاً، بمفتاح اختبار على بيئة الاختبار. 1. اقرأ الكتالوج: الفرع والأصناف وطريقة الدفع. 2. ابحث عن العميل، أو أنشئه. 3. أصدر الفاتورة، مع الدفع أو بدونه. 4. سجّل الدفعة لاحقاً إذا لم تُدفع الفاتورة عند الإصدار. يحتاج المفتاح الصلاحيات `branches:read` و `items:read` و `payment_methods:read` و `customers:write` و `sales_invoices:write`، و `payments:write` للخطوة 4. المعرّفات في الأمثلة من [البيانات الجاهزة في بيئة الاختبار](https://docs.kosoor.com.sa/ar/test-mode.md#fixtures) متى وُجدت. أما قيم `number` للفواتير وسندات القبض فمن تأليفنا، لأن كل تاجر يحدد ترقيمه بنفسه. ### 1. اقرأ الكتالوج تحدد الفاتورة فرعها، وصنف كل بند، وطريقة الدفع، بمعرّفاتها. خذ هذه المعرّفات من الكتالوج، ولا تخمّنها أبداً: | الـ Endpoint | الصلاحية | ماذا يعطيك | |---|---|---| | `GET /v1/branches` | `branches:read` | الفروع التي يعمل فيها مفتاحك: `branchId` | | `GET /v1/items` | `items:read` | الأصناف التي يبيعها التاجر: `lines[].itemId` | | `GET /v1/payment-methods` | `payment_methods:read` | طرق الدفع لدى التاجر: `payment.paymentMethodId` | تحتفظ بيئة الاختبار بالمعرّفات الفعلية للفروع والأصناف وطرق الدفع التي نسختها، فالمعرّفات التي تربطها في بيئة الاختبار صالحة على البيانات الفعلية. ### 2. ابحث عن العميل أو أنشئه كل فاتورة تحدد عميلاً، فلا يدعم الـ API البيع لعميل نقدي غير مسجّل. ابحث عن العميل برقم جواله، أو بقيمة `referenceId` التي أعطيته إياها: ```bash curl "https://api.kosoor.com.sa/v1/customers?phone=%2B966500000001" \ -H "Authorization: Bearer ksr_test_..." ``` إذا كانت `data` فارغة فأنشئ العميل بـ `POST /v1/customers`. وإذا أُرسل طلبان منك في الوقت نفسه وسُجّل الرقم بينهما، يرجع طلب الإنشاء `409 phone_taken`، ويحمل `existingId` معرّف العميل الذي تستخدمه. الطلبان وكل القواعد في صفحة [العملاء](https://docs.kosoor.com.sa/ar/customers.md). تستخدم الأمثلة التالية عميلين جاهزين في بيئة الاختبار: **Sandbox Person** (`...0001`) وهو فرد، و **Sandbox Standard Co** (`...0002`) وهو شركة. ### 3. أصدر الفاتورة يُنشئ `POST /v1/sales-invoices` الفاتورة ويصدرها في طلب واحد، بلا مسودة. وكل طلب `POST` يحمل `Idempotency-Key`: إذا ضاع الرد فأرسل الطلب نفسه بالمفتاح نفسه، فيرجع لك الرد على الفاتورة نفسها بدل أن تُنشأ فاتورة ثانية. التفاصيل في [منع تكرار الطلبات](https://docs.kosoor.com.sa/ar/idempotency.md). #### عميل فرد يدفع المبلغ كاملاً العميل فرد، فالفاتورة مبسطة. و `payment` بدون `amount` يدفع الفاتورة كلها. والسعر هنا شامل الضريبة، لذلك قيمة `pricesIncludeVat` هي `true`. ```bash curl https://api.kosoor.com.sa/v1/sales-invoices \ -H "Authorization: Bearer ksr_test_..." \ -H "Idempotency-Key: 4e9f2a7c-1b3d-4c5e-8f6a-9b0c1d2e3f4a" \ -H "Content-Type: application/json" \ -d '{ "customerId": "5a4db0c5-0000-4000-8000-000000000001", "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "pricesIncludeVat": true, "lines": [ { "itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "quantity": 1, "unitPrice": 115 } ], "payment": { "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9" }, "referenceId": "order-20417" }' ``` الرد `201 Created`: ```json { "id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "object": "salesInvoice", "number": "INV-1042", "issueDate": "2026-11-07", "supplyDate": null, "dueDate": null, "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "customerId": "5a4db0c5-0000-4000-8000-000000000001", "currency": "SAR", "subtotal": 100, "discountTotal": 0, "vatTotal": 15, "total": 115, "amountPaid": 115, "amountDue": 0, "lines": [ { "itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "name": "Full wash", "description": null, "quantity": 1, "unitPrice": 100, "unitDiscount": 0, "vatRate": 15, "vatAmount": 15, "lineTotal": 115 } ], "notes": null, "costCentreId": null, "referenceId": "order-20417", "metadata": {}, "createdVia": "api", "zatca": { "kind": "simplified", "status": "pending", "uuid": null, "counter": null, "hash": null, "qr": null, "submittedAt": null, "clearedAt": null, "reportedAt": null, "errors": [] } } ``` اقرأ من الرد: - **أسعار البنود قبل الضريبة.** الـ 115 التي أرسلتها شاملة الضريبة تظهر `unitPrice: 100` مع `vatAmount: 15`. - **`total` هو ما يدفعه العميل.** قارنه بما يتوقعه نظامك: الخصم الذي ينزل بالسعر عن الحد الأدنى للصنف يُخفَّض، والسعر الشامل للضريبة قد يغيّر الإجمالي بهللة. التفاصيل في [فواتير المبيعات](https://docs.kosoor.com.sa/ar/sales-invoices.md). - **قيمة `zatca.status` هي `pending`.** الفاتورة المبسطة تُبلَّغ بها هيئة الزكاة والضريبة والجمارك بعد الرد، ثم تصبح `reported`. وتبقى حقول المستند `null` حتى توقّعه كسور. التفاصيل في [نتائج الهيئة](https://docs.kosoor.com.sa/ar/zatca.md). ويبقى المبلغ على الفاتورة نفسها (`amountPaid`)، فالبيع لعميل فرد مدفوعاً عند الإصدار لا تُنشأ له دفعة منفصلة. #### شركة بالآجل العميل شركة، فالفاتورة ضريبية، وتعتمدها الهيئة قبل أن ترد كسور. وبدون `payment` تُسجَّل الفاتورة كلها على حساب الشركة. والأسعار هنا قبل الضريبة. ```bash curl https://api.kosoor.com.sa/v1/sales-invoices \ -H "Authorization: Bearer ksr_test_..." \ -H "Idempotency-Key: 0c5b7d3e-9f2a-4b1c-8d6e-3a4f5b6c7d8e" \ -H "Content-Type: application/json" \ -d '{ "customerId": "5a4db0c5-0000-4000-8000-000000000002", "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "pricesIncludeVat": false, "lines": [ { "itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a", "quantity": 2, "unitPrice": 100, "description": "Plates ABC 1234 and XYZ 5678" } ], "dueDate": "2026-12-07", "notes": "Fleet contract 2026-14", "referenceId": "order-20418", "metadata": { "fleetContract": "2026-14" } }' ``` الرد `201 Created`: ```json { "id": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "object": "salesInvoice", "number": "INV-1043", "issueDate": "2026-11-07", "supplyDate": null, "dueDate": "2026-12-07", "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "customerId": "5a4db0c5-0000-4000-8000-000000000002", "currency": "SAR", "subtotal": 200, "discountTotal": 0, "vatTotal": 30, "total": 230, "amountPaid": 0, "amountDue": 230, "lines": [ { "itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a", "name": "Fleet wash", "description": "Plates ABC 1234 and XYZ 5678", "quantity": 2, "unitPrice": 100, "unitDiscount": 0, "vatRate": 15, "vatAmount": 30, "lineTotal": 230 } ], "notes": "Fleet contract 2026-14", "costCentreId": null, "referenceId": "order-20418", "metadata": { "fleetContract": "2026-14" }, "createdVia": "api", "zatca": { "kind": "standard", "status": "cleared", "uuid": "e7f8a9b0-c1d2-4e3f-a4b5-c6d7e8f9a0b1", "counter": 18, "hash": "zXab+32ksuccBzrDiE27f65AmOvbZ2ixbnSSRnoRKzA=", "qr": "ARBTYW5kYm94IE1lcmNoYW50Ag8zOTk5OTk5OTk5MDAwMDMDFDIwMjYtMTEtMDdUMDk6NDE6MjJaBAYyMzAuMDAFBTMwLjAw", "submittedAt": "2026-11-07T09:41:21.884Z", "clearedAt": "2026-11-07T09:41:22.517Z", "reportedAt": null, "errors": [] } } ``` قيمة `zatca.status` هي `cleared`، ويحمل `zatca.qr` رمز QR الذي أرجعته الهيئة. وإذا لم ترد الهيئة في الوقت المحدد فالرد يبقى `201` لكن بـ `status: "pending"`، وتعتمدها كسور لاحقاً. وإذا رفضتها الهيئة فلا يُحفظ شيء، ويرجع الطلب `422 zatca_rejected`. الحالتان موضحتان في [نتائج الهيئة](https://docs.kosoor.com.sa/ar/zatca.md). البيع بالآجل للشركات فقط. والفاتورة لعميل فرد بدون `payment`، أو مع `payment.amount` أقل من الإجمالي، ترجع `422 on_account_not_allowed`. #### استلام جزء من المبلغ عند الإصدار إذا كانت `payment.amount` أقل من الإجمالي، يُدفع هذا المبلغ عند الإصدار، ويبقى الباقي على حساب الشركة: ```json { "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9", "amount": 100 } ``` والمبلغ الأكبر من الإجمالي يرجع `400 invalid_parameter`. ولدفع الفاتورة كاملة لا ترسل `amount`، بدل أن ترسل الإجمالي الذي حسبته بنفسك. ### 4. سجّل الدفعة لاحقاً عندما تدفع الشركة، سجّل المبلغ بـ `POST /v1/payments` وخصّصه للفاتورة: ```bash curl https://api.kosoor.com.sa/v1/payments \ -H "Authorization: Bearer ksr_test_..." \ -H "Idempotency-Key: 7a1b2c3d-4e5f-4a6b-9c7d-8e9f0a1b2c3d" \ -H "Content-Type: application/json" \ -d '{ "customerId": "5a4db0c5-0000-4000-8000-000000000002", "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9", "amount": 230, "date": "2026-11-09", "allocations": [ { "invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "amount": 230 } ], "referenceId": "bank-tx-55190" }' ``` الرد `201 Created`: ```json { "id": "d3e4f5a6-b7c8-4d9e-8f0a-2b3c4d5e6f7a", "object": "payment", "number": "RCT-0031", "date": "2026-11-09", "customerId": "5a4db0c5-0000-4000-8000-000000000002", "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9", "amount": 230, "discount": 0, "allocations": [ { "invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "amount": 230 } ], "unallocatedAmount": 0, "notes": null, "referenceId": "bank-tx-55190", "metadata": {}, "createdVia": "api" } ``` بعدها يعرض `GET /v1/sales-invoices/c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f` القيمتين `amountPaid: 230` و `amountDue: 0`. والمبلغ الذي لا تخصصه يبقى رصيداً دائناً للعميل، ويظهر في `unallocatedAmount`. كل القواعد في صفحة [المدفوعات](https://docs.kosoor.com.sa/ar/payments.md). ### 5. أعطِ العميل الفاتورة يرجع `GET /v1/sales-invoices/{id}/pdf` الفاتورة بمقاس A4. ملف PDF للفاتورة المبسطة جاهز فوراً، أما الفاتورة الضريبية فيرجع ملفها `409 invoice_not_cleared` حتى تعتمدها الهيئة. ### ما الذي قد يحدث | الخطأ | المعنى | ماذا تفعل | |---|---|---| | `422 customer_address_incomplete` | الفاتورة الضريبية تحتاج عنوان الشركة كاملاً مع الرقم الضريبي أو رقم الهوية، والحقل `missing` يوضح الناقص | أكمل الناقص بـ `PATCH /v1/customers/{id}`، ثم أرسل الفاتورة مرة أخرى | | `422 on_account_not_allowed` | عميل فرد لم يدفع المبلغ كاملاً | أرسل `payment` بدون `amount` | | `422 credit_limit_exceeded` | حساب الشركة سيتجاوز حد الائتمان | استلم المبلغ عند الإصدار، أو راجع التاجر | | `422 customer_inactive` | عطّل التاجر هذا العميل | راجع التاجر | | `422 item_not_sellable` | الصنف اشتراك، أو غير معروض للبيع | استخدم صنفاً آخر | | `400 missing_parameter` على `lines[i].unitPrice` | الصنف مسعّر حسب حجم السيارة | أرسل `unitPrice` | | `403 branch_not_allowed` | المفتاح مقصور على فروع أخرى | استخدم فرعاً من `GET /v1/branches` | | `422 branch_not_onboarded` | الفرع غير مربوط بالهيئة، فلا يستطيع إصدار فاتورة ضريبية | راجع التاجر | | `422 zatca_rejected` | رفضت الهيئة الفاتورة الضريبية، ولم يُحفظ شيء | اقرأ `zatcaErrors`، وصحّح البيانات، ثم أرسلها مرة أخرى بـ `Idempotency-Key` جديد | كل الأكواد في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). المصدر: https://docs.kosoor.com.sa/ar/record-a-sale --- ## استرجاع أو تصحيح عملية بيع > إصدار إشعار دائن على فاتورة مبيعات صادرة كلها أو جزء منها، مع إرجاع المبلغ للعميل أو خصمه من المستحق عليه، مع الطلبات والردود كاملة. فاتورة المبيعات بعد إصدارها لا تتغير ولا تُحذف. ولإرجاع مبلغ للعميل، أو خصمه مما عليه، أو تصحيح خطأ، تصدر عليها إشعاراً دائناً بـ `POST /v1/credit-notes`. ويحتاج المفتاح الصلاحية `credit_notes:write`. الإشعار الدائن يخص بنوداً من فاتورة واحدة، ويأخذ منها الفرع والعميل والأسعار، وترجع البضاعة المشمولة به إلى المخزون. ويذهب المبلغ بإحدى طريقتين: | ما ترسله | إجمالي الإشعار | |---|---| | `refund` مع `paymentMethodId` | يُرجع للعميل عن طريق طريقة الدفع هذه | | بدون `refund` | يُخصم مما على العميل في الفاتورة، وما يزيد على ذلك يبقى رصيداً دائناً له | قيم `number` للإشعارات في الأمثلة من تأليفنا، لأن كل تاجر يحدد ترقيمه بنفسه. ويوضح `reason` لهيئة الزكاة والضريبة والجمارك سبب الإشعار: `cancellation` (إلغاء البيع كله أو جزء منه)، أو `refund` (إرجاع منتجات أو خدمات وإعادة المبلغ)، أو `informationChange` (تصحيح بيانات خاطئة في الفاتورة). ### إرجاع المبلغ لعميل فرد العميل الفرد في [تسجيل عملية بيع](https://docs.kosoor.com.sa/ar/record-a-sale.md) دفع 115 ريالاً لغسلة لم تتم. أصدر إشعاراً على البند وأرجع له المبلغ: ```bash curl https://api.kosoor.com.sa/v1/credit-notes \ -H "Authorization: Bearer ksr_test_..." \ -H "Idempotency-Key: 5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f" \ -H "Content-Type: application/json" \ -d '{ "invoiceId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "reason": "refund", "lines": [ { "itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "quantity": 1 } ], "refund": { "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9" }, "notes": "Booking cancelled by the customer", "referenceId": "refund-20417" }' ``` الرد `201 Created`: ```json { "id": "e4f5a6b7-c8d9-4e0f-9a1b-3c4d5e6f7a8b", "object": "creditNote", "number": "CN-0007", "invoiceId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "customerId": "5a4db0c5-0000-4000-8000-000000000001", "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "issueDate": "2026-11-08", "reason": "refund", "currency": "SAR", "lines": [ { "itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "name": "Full wash", "description": null, "quantity": 1, "unitPrice": 100, "unitDiscount": 0, "vatRate": 15, "vatAmount": 15, "lineTotal": 115 } ], "subtotal": 100, "discountTotal": 0, "vatTotal": 15, "total": 115, "refund": { "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9", "amount": 115 }, "notes": "Booking cancelled by the customer", "referenceId": "refund-20417", "metadata": {}, "createdVia": "api", "zatca": { "kind": "simplified", "status": "pending", "uuid": null, "counter": null, "hash": null, "qr": null, "submittedAt": null, "clearedAt": null, "reportedAt": null, "errors": [] } } ``` الفاتورة مبسطة، فالإشعار مبسط أيضاً: تُبلَّغ به الهيئة بعد الرد، ثم يصبح `reported`. ويوضح `refund.amount` المبلغ الذي أُرجع. العميل الفرد لا يحمل رصيداً. فبدون `refund` لا يصدر له إشعار إلا في حدود المستحق عليه في الفاتورة، وما زاد على ذلك يرجع `400 invalid_parameter` على `refund`. لذلك العميل الفرد الذي دفع المبلغ كاملاً يُرجع له دائماً بـ `refund`. ### خصم المبلغ من حساب شركة الشركة في [تسجيل عملية بيع](https://docs.kosoor.com.sa/ar/record-a-sale.md) لم تدفع فاتورتها البالغة 230 ريالاً بعد، وأُلغيت إحدى الغسلتين. أصدر إشعاراً على وحدة واحدة بدون `refund`، فيُخصم مما على الشركة: ```bash curl https://api.kosoor.com.sa/v1/credit-notes \ -H "Authorization: Bearer ksr_test_..." \ -H "Idempotency-Key: 1f2e3d4c-5b6a-4978-8a9b-0c1d2e3f4a5b" \ -H "Content-Type: application/json" \ -d '{ "invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "reason": "cancellation", "lines": [ { "itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a", "quantity": 1 } ], "referenceId": "cancel-20418-1" }' ``` الرد `201 Created`: ```json { "id": "f5a6b7c8-d9e0-4f1a-8b2c-4d5e6f7a8b9c", "object": "creditNote", "number": "CN-0008", "invoiceId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f", "customerId": "5a4db0c5-0000-4000-8000-000000000002", "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "issueDate": "2026-11-08", "reason": "cancellation", "currency": "SAR", "lines": [ { "itemId": "8d9e0f1a-2b3c-4d4e-9f5a-6b7c8d9e0f1a", "name": "Fleet wash", "description": "Plates ABC 1234 and XYZ 5678", "quantity": 1, "unitPrice": 100, "unitDiscount": 0, "vatRate": 15, "vatAmount": 15, "lineTotal": 115 } ], "subtotal": 100, "discountTotal": 0, "vatTotal": 15, "total": 115, "refund": null, "notes": null, "referenceId": "cancel-20418-1", "metadata": {}, "createdVia": "api", "zatca": { "kind": "standard", "status": "cleared", "uuid": "a9b0c1d2-e3f4-4a5b-8c6d-7e8f9a0b1c2d", "counter": 19, "hash": "ZHXx9tKJ0Zr2b/RlF8NG4LYnWT15u5Rq0/+tYkccjjk=", "qr": "ARBTYW5kYm94IE1lcmNoYW50Ag8zOTk5OTk5OTk5MDAwMDMDFDIwMjYtMTEtMDhUMDg6MTU6MDRaBAYxMTUuMDAFBTE1LjAw", "submittedAt": "2026-11-08T08:15:03.902Z", "clearedAt": "2026-11-08T08:15:04.611Z", "reportedAt": null, "errors": [] } } ``` قيمة `refund` هي `null`، فلم يُرجع أي مبلغ. وتنقص قيمة `amountDue` في الفاتورة من 230 إلى 115. ولو كانت الشركة قد دفعت المبلغ كاملاً، لبقيت الـ 115 رصيداً دائناً لها. الفاتورة ضريبية، فاعتمدت الهيئة الإشعار قبل الرد. والفاتورة التي لم تعتمدها الهيئة بعد لا يصدر عليها إشعار، ويرجع الطلب `409 invoice_not_cleared`. أعد المحاولة عندما يعرض `GET /v1/sales-invoices/{id}` القيمة `zatca.status: "cleared"`. ### تصحيح خطأ الفاتورة التي فيها بيانات خاطئة، مثل عميل غير صحيح، تُصحَّح بطلبين: 1. أصدر إشعاراً على كل بنودها بـ `reason: "informationChange"`. وإذا كان العميل قد دفع فأرسل `refund` لإرجاع المبلغ، أو اتركه رصيداً دائناً إذا كان العميل شركة. 2. أصدر فاتورة جديدة بالبيانات الصحيحة، كما في [تسجيل عملية بيع](https://docs.kosoor.com.sa/ar/record-a-sale.md). ### الحد الأقصى للإشعار مجموع الإشعارات على الفاتورة لا يتجاوز إجماليها، ولا تتجاوز كمية أي صنف ما بيع منه. والاسترجاع من نقطة البيع أو من لوحة التحكم يُحسب أيضاً. وما زاد على ذلك يرجع `422 credit_exceeds_invoice`. وكذلك لا يتجاوز المبلغ المرجع ما حُصّل على الفاتورة، مطروحاً منه ما أُرجع منها سابقاً (`422 refund_exceeds_collected`). ### عندما يرفض الـ API الطلب | الخطأ | ماذا تفعل | |---|---| | `409 invoice_not_cleared` | الفاتورة الضريبية ما زالت `pending`، فأعد المحاولة بعد أن تصبح `cleared` | | `422 invoice_not_creditable` | رفضت الهيئة الفاتورة، أو كانت مستوردة، ويصححها التاجر من لوحة التحكم | | `422 loyalty_refund_blocked` | أعد المحاولة مرة واحدة بعد دقائق، فإن رُفض مرة أخرى يصدر التاجر الإشعار من لوحة التحكم | | `422 used_subscription_not_refundable` | البيع اشتراك استُخدمت منه زيارات، ويتولاه التاجر من لوحة التحكم | | `422 refund_method_not_supported` | دُفعت الفاتورة ببطاقة عبر المتجر الإلكتروني للتاجر، ولا يستطيع الـ API إرجاع المبلغ إلى البطاقة | | `409 invoice_busy` | طلب آخر على الفاتورة استمر أكثر من 30 ثانية، فأعد إرسال الطلب | كل القواعد وكل الأخطاء في صفحة [الإشعارات الدائنة](https://docs.kosoor.com.sa/ar/credit-notes.md). المصدر: https://docs.kosoor.com.sa/ar/refunds --- ## مزامنة نظام ERP > نسخ فواتير التاجر ومدفوعاته وإشعاراته الدائنة إلى نظامك بلا نقص ولا تكرار، والكتابة في كسور بأمان باستخدام معرّفاتك الخاصة. هذه الصفحة للأنظمة التي تنسخ مستندات التاجر، مثل أنظمة ERP أو برامج المحاسبة، وقد تكتب في كسور أيضاً. ### اقرأ الجديد كل قائمة ترجع أحدث السجلات أولاً، أياً كانت القناة التي أنشأتها: نقطة البيع، أو الكشك الذاتي، أو المتجر الإلكتروني، أو لوحة التحكم، أو الـ API. والسجل الذي يُنشأ أثناء تنقلك بين الصفحات يظهر في أول القائمة، فلا يغيّر الصفحات التي تقرؤها. التفاصيل في [تقسيم النتائج](https://docs.kosoor.com.sa/ar/pagination.md). لجلب الجديد منذ آخر مرة: 1. احفظ الـ `id` لأحدث سجل خزّنته من كل قائمة. 2. اطلب الصفحة الأولى: `GET /v1/sales-invoices?limit=100`. 3. خزّن السجلات واحداً واحداً حتى تصل إلى الـ `id` الذي حفظته، ثم توقف. 4. إذا لم تصل إليه وكانت قيمة `hasMore` هي `true`، فاطلب الصفحة التالية مع `startingAfter` بقيمة آخر `id` في هذه الصفحة، وارجع إلى الخطوة 3. 5. احفظ الـ `id` لأول سجل في الصفحة الأولى للمرة القادمة. ```bash curl "https://api.kosoor.com.sa/v1/sales-invoices?limit=100&startingAfter=c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f" \ -H "Authorization: Bearer ksr_live_..." ``` ```json { "data": [ { "id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "object": "salesInvoice", "number": "INV-1042", "issueDate": "2026-11-07", "supplyDate": null, "dueDate": null, "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "customerId": "5a4db0c5-0000-4000-8000-000000000001", "currency": "SAR", "subtotal": 100, "discountTotal": 0, "vatTotal": 15, "total": 115, "amountPaid": 115, "amountDue": 0, "notes": null, "costCentreId": null, "referenceId": "order-20417", "metadata": {}, "createdVia": "api", "zatca": { "kind": "simplified", "status": "reported", "uuid": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e", "counter": 17, "hash": "dZBGdHmJoIQ0SOi+mt6KcGC6mWlVmpjbPEJ4y/3tps4=", "qr": "ARBTYW5kYm94IE1lcmNoYW50Ag8zOTk5OTk5OTk5MDAwMDMDFDIwMjYtMTEtMDdUMDk6MzA6MTJaBAYxMTUuMDAFBTE1LjAw", "submittedAt": "2026-11-07T09:30:40.118Z", "clearedAt": null, "reportedAt": "2026-11-07T09:30:41.204Z", "errors": [] } } ], "hasMore": false } ``` القائمة لا ترجع بنود الفواتير (`lines`). أضف `expand=lines` لتحصل عليها، و `expand=customer` لتضمين بيانات العميل (ويحتاج `customers:read` أيضاً)، وكرّر الـ Parameter للاثنين معاً: `expand=lines&expand=customer`. والخطوات نفسها تصلح لـ `GET /v1/payments` و `GET /v1/credit-notes`. وقائمة الإشعارات الدائنة تشمل الاسترجاع من نقطة البيع ومن لوحة التحكم. ولتحميل البيانات السابقة مرة واحدة، استخدم فلاتر التاريخ: `issuedAfter` و `issuedBefore` لفواتير المبيعات والإشعارات الدائنة (لحظة زمنية بصيغة ISO 8601، مثل `2026-01-01T00:00:00Z`)، و `dateFrom` و `dateTo` للمدفوعات (أيام، مثل `2026-01-01`). ### التمييز بين القنوات يوضح `createdVia` القناة التي أنشأت السجل: `api`، أو `dashboard`، أو `pos` (نقطة البيع)، أو `kiosk`، أو `storefront` (المتجر الإلكتروني للتاجر)، أو `unknown` للسجل المحفوظ قبل أن تبدأ كسور بتسجيل القناة. والقيمة `api` تشمل كل مفتاح أصدره التاجر، وليس مفتاحك وحده. ولتتعرف على سجلاتك أنت، أعطها `referenceId` خاصاً بك، أو مفتاحاً في `metadata`. ### السجلات التي تتغير بعد تخزينها تعرض القائمة كل سجل مرة واحدة، عند إنشائه. لكن بعض الحقول تتغير لاحقاً: | الحقل | يتغير عندما | |---|---| | `zatca.status` وبقية حقول `zatca` | ترد الهيئة: تتحول `pending` إلى `cleared` أو `reported` أو `rejected` | | `amountPaid` و `amountDue` في الفاتورة | تُخصَّص لها دفعة، أو يخصم منها إشعار دائن | | أي حقل في العميل | يعدّله التاجر أو أحد المفاتيح | اقرأ السجل مرة أخرى بـ `GET` على الـ `id` الخاص به عندما تحتاج أحدث بياناته. والفلتر `zatcaStatus=pending` في `GET /v1/sales-invoices` و `GET /v1/credit-notes` يعرض المستندات التي ما زالت تنتظر هيئة الزكاة والضريبة والجمارك. ونتائج الهيئة التي تصل لاحقاً تُرسل إليك عن طريق Webhooks. التفاصيل في [Webhooks](https://docs.kosoor.com.sa/ar/webhooks.md). ### الكتابة بلا تكرار شيئان يحميان طلب الكتابة، ويعملان معاً: - **`Idempotency-Key`**: إرسال الطلب نفسه مرة أخرى بالمفتاح نفسه يرجع الرد الأول ولا يُنشئ شيئاً. وتحتفظ كسور بالمفتاح 30 يوماً. التفاصيل في [منع تكرار الطلبات](https://docs.kosoor.com.sa/ar/idempotency.md). - **`referenceId`**: معرّفك الخاص للسجل، مثل رقم المستند في نظام ERP عندك. ولا يتكرر بين سجلين من النوع نفسه: | المورد | `referenceId` لا يتكرر بين | |---|---| | العملاء | عملاء التاجر، ويُحدَّد مرة واحدة ولا يتغير | | فواتير المبيعات | فواتير المبيعات والإشعارات الدائنة لدى التاجر معاً | | المدفوعات | مدفوعات التاجر | | الإشعارات الدائنة | فواتير المبيعات والإشعارات الدائنة لدى التاجر معاً | فإذا توقف نظامك بعد إرسال فاتورة وضاع منه الـ `Idempotency-Key`، لن تُنشئ إعادة إرسالها بمفتاح جديد فاتورة ثانية، بل ترجع `409 reference_id_taken`. ويحمل `existingId` معرّف السجل الذي يستخدم المعرّف إذا كان من النوع نفسه ويملك مفتاحك صلاحية قراءته، وفي كل الأحوال تجده بـ `GET /v1/sales-invoices?referenceId=...`: ```json { "error": { "type": "invalid_request_error", "code": "reference_id_taken", "message": "قيمة referenceId مستخدمة لسجل آخر.", "param": "referenceId", "docUrl": "https://docs.kosoor.com.sa/ar/errors#reference_id_taken", "existingId": "c2d3e4f5-a6b7-4c8d-9e0f-1a2b3c4d5e6f" } } ``` ويمكنك أيضاً البحث عن المعرّف قبل الكتابة، فكل قائمة تقبل الفلتر `referenceId`. ### اربط الكتالوج مرة واحدة تُربط فروع نظامك وأصنافه وطرق الدفع فيه بمعرّفات كسور من `GET /v1/branches` و `GET /v1/items` و `GET /v1/payment-methods`. وتحتفظ بيئة الاختبار بالمعرّفات الفعلية للكتالوج الذي نسخته، فالربط الذي تبنيه في بيئة الاختبار يصلح على البيانات الفعلية. ويُستثنى العملاء، فلبيئة الاختبار عملاؤها الخاصون، فاربطهم من جديد على البيانات الفعلية بـ `referenceId` أو رقم الجوال أو الرقم الضريبي. المصدر: https://docs.kosoor.com.sa/ar/erp-sync --- ## نتائج الهيئة > معنى كل حالة لدى هيئة الزكاة والضريبة والجمارك في الفاتورة أو الإشعار الدائن، وماذا يفعل نظامك مع كل حالة، وكيف تصلك النتائج التي تأتي لاحقاً. كل فاتورة مبيعات وإشعار دائن يحمل الكائن `zatca`، ويوضح وضع المستند لدى هيئة الزكاة والضريبة والجمارك (ZATCA). وإذا كانت الهيئة جديدة عليك فاقرأ [ابدأ من هنا](https://docs.kosoor.com.sa/ar/start-here.md#zatca-in-five-minutes) أولاً. ```json { "kind": "standard", "status": "cleared", "uuid": "e7f8a9b0-c1d2-4e3f-a4b5-c6d7e8f9a0b1", "counter": 18, "hash": "zXab+32ksuccBzrDiE27f65AmOvbZ2ixbnSSRnoRKzA=", "qr": "ARBTYW5kYm94IE1lcmNoYW50Ag8zOTk5OTk5OTk5MDAwMDMDFDIwMjYtMTEtMDdUMDk6NDE6MjJaBAYyMzAuMDAFBTMwLjAw", "submittedAt": "2026-11-07T09:41:21.884Z", "clearedAt": "2026-11-07T09:41:22.517Z", "reportedAt": null, "errors": [] } ``` | الحقل | المعنى | |---|---| | `kind` | `standard` للعميل من نوع شركة، و `simplified` للعميل الفرد | | `status` | `pending` أو `cleared` أو `reported` أو `rejected` | | `uuid` | الـ UUID للمستند في سجلات الهيئة | | `counter` | عدّاد الفواتير لدى الهيئة | | `hash` | الـ Hash للمستند، ويرتبط به المستند الذي يليه | | `qr` | رمز QR بصيغة Base64، وفي المستند الضريبي هو الرمز الذي أرجعته الهيئة عند الاعتماد | | `submittedAt` | وقت توقيع كسور للمستند، بتوقيت UTC | | `clearedAt` | وقت اعتماد الهيئة للمستند الضريبي، بتوقيت UTC | | `reportedAt` | وقت قبول الهيئة التبليغ بالمستند المبسط، بتوقيت UTC | | `errors` | رسائل الهيئة إذا رفضت المستند، ولكل رسالة `code` و `category` و `message` | تبقى قيم `uuid` و `counter` و `hash` و `qr` و `submittedAt` بقيمة `null` حتى توقّع كسور المستند. ### ماذا تفعل مع كل حالة #### `pending` المستند صادر وصحيح في كسور، لكن الهيئة لم تقبله بعد. - **المبسط**: هذه الحالة الطبيعية بعد الإصدار مباشرة. تبلّغ كسور الهيئة بالمستند بعد الرد، ثم يصبح `reported`. وملف PDF جاهز فوراً، أما ملف XML فيرجع `409 invoice_not_cleared` حتى تقبل الهيئة التبليغ. - **الضريبي**: لم ترد الهيئة أثناء اعتماد كسور له. تعيد كسور إرساله لاحقاً، فيصبح `cleared` أو `rejected`. وحتى ذلك الحين يرجع ملفا PDF و XML القيمة `409 invoice_not_cleared`، وكذلك أي إشعار دائن عليه. لا تُصدر عملية البيع مرة أخرى، فالفاتورة الجديدة عملية بيع ثانية. وإذا انتهت مهلة طلبك ولم تعرف هل وصل أم لا، فأرسله مرة أخرى بالـ `Idempotency-Key` نفسه، فيرجع لك الرد الأول. #### `cleared` اعتمدت الهيئة هذا المستند الضريبي، وملفا PDF و XML جاهزان، ويحمل `qr` رمز QR الذي أرجعته الهيئة. لا يلزمك شيء. #### `reported` قبلت الهيئة التبليغ بهذا المستند المبسط، وملف XML جاهز. لا يلزمك شيء. #### `rejected` رفضت الهيئة المستند، ورسائلها في `errors`: ```json { "kind": "standard", "status": "rejected", "uuid": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f", "counter": 20, "hash": "PO9Je+Enxi/tc010iSgP6H+piJDaQh6zQZ41v6aDPsk=", "qr": "ARBTYW5kYm94IE1lcmNoYW50Ag8zOTk5OTk5OTk5MDAwMDMDFDIwMjYtMTEtMDdUMTA6MDI6MTFaBAYxMTUuMDAFBTE1LjAw", "submittedAt": "2026-11-07T10:02:11.430Z", "clearedAt": null, "reportedAt": null, "errors": [ { "code": "BR-KSA-63", "category": "KSA", "message": "Buyer street is missing" } ] } ``` يبقى المستند في كسور، لكن: - ملف XML يرجع `409 invoice_not_cleared` دائماً، وكذلك ملف PDF للمستند الضريبي؛ - الفاتورة التي رفضتها الهيئة لا يصدر عليها إشعار دائن عن طريق الـ API (`422 invoice_not_creditable`). اعرض رسائل الهيئة على التاجر، ويصحح التاجر عملية البيع من لوحة تحكم كسور. ### عندما ترفض الهيئة الفاتورة الضريبية وقت الإصدار الفاتورة الضريبية تُعتمد قبل أن ترد كسور. فإذا رفضتها الهيئة حينها لا يُحفظ أي شيء، ولا يُستهلك رقم فاتورة، ويرجع الطلب `422 zatca_rejected` ورسائل الهيئة في `zatcaErrors`: ```json { "error": { "type": "business_rule_error", "code": "zatca_rejected", "message": "رفضت هيئة الزكاة والضريبة والجمارك الفاتورة، ولم يُحفظ أي شيء. رسائل الهيئة موضحة في zatcaErrors.", "param": null, "docUrl": "https://docs.kosoor.com.sa/ar/errors#zatca_rejected", "zatcaErrors": [ { "code": "BR-KSA-63", "category": "KSA", "message": "Sandbox sample rejection: the buyer's street is missing" } ] } } ``` صحّح البيانات التي تذكرها الهيئة، وغالباً تكون في بيانات العميل فتعدّلها بـ `PATCH /v1/customers/{id}`، ثم أرسل الفاتورة مرة أخرى بـ `Idempotency-Key` جديد. والأمر نفسه ينطبق على الإشعار الدائن على فاتورة ضريبية. وفي بيئة الاختبار يعطي العميلان الجاهزان **Sandbox ZATCA Pending Co** و **Sandbox ZATCA Rejected Co** هاتين النتيجتين في كل مرة. التفاصيل في [وضع الاختبار](https://docs.kosoor.com.sa/ar/test-mode.md). ### النتائج التي تأتي لاحقاً المستند الذي رجع بحالة `pending` تصله نتيجته لاحقاً. وتُرسلها كسور إلى نظامك فور وصولها: سجّل Webhook Endpoint لأحداث `salesInvoice.zatca*` و `creditNote.zatca*`. التفاصيل في [Webhooks](https://docs.kosoor.com.sa/ar/webhooks.md). وبدون ذلك، اقرأ المستند مرة أخرى بـ `GET /v1/sales-invoices/{id}` أو `GET /v1/credit-notes/{id}` عندما يحتاج نظامك حالته، واستخدم الفلتر `zatcaStatus=pending` في القائمتين لتعرف المستندات التي ما زالت تنتظر. المصدر: https://docs.kosoor.com.sa/ar/zatca --- ## قائمة التحقق قبل التشغيل الفعلي > ما يجب التحقق منه في الربط قبل أن تمر عن طريقه أول عملية بيع حقيقية للتاجر. راجع هذه القائمة بمفتاح الاختبار قبل أن تنتقل إلى المفتاح الفعلي. ### المفاتيح - [ ] المفتاح محفوظ على السيرفر عندك فقط، وليس في تطبيق جوال أو متصفح أو مستودع كود. - [ ] المفتاح الفعلي يحمل الصلاحيات التي يستخدمها نظامك فقط، والفروع التي يعمل فيها فقط. - [ ] يقرأ نظامك المفتاح من الإعدادات، فلا يحتاج تغييره إلى إصدار جديد من نظامك. - [ ] تعرف من الشخص لدى التاجر الذي يبطل المفتاح ويصدر غيره، ونظامك يتعامل مع `401 revoked_api_key` و `401 expired_api_key` بتنبيه شخص مسؤول، وليس بإعادة المحاولة. - [ ] إذا كان للمفتاح تاريخ انتهاء، فهناك شخص مسؤول عن تجديده قبل ذلك اليوم. ### الطلبات - [ ] كل طلب `POST` و `PATCH` يرسل `Idempotency-Key`، مفتاحاً واحداً لكل عملية، وإعادة المحاولة ترسل المفتاح نفسه. - [ ] لا تعيد المحاولة إلا عند انتهاء المهلة، أو انقطاع الاتصال، أو `409 idempotency_in_progress`، أو `5xx`، وبالمفتاح نفسه. التفاصيل في [منع تكرار الطلبات](https://docs.kosoor.com.sa/ar/idempotency.md). - [ ] الكود عندك يعتمد على `code` وليس على `message`. - [ ] تسجّل الـ `Request-Id` لكل طلب، لترسله إلى الدعم عند الحاجة. - [ ] معرّفات الفروع والأصناف وطرق الدفع تأتي من `GET /v1/branches` و `GET /v1/items` و `GET /v1/payment-methods`، وليست ثابتة في الكود. - [ ] كل فاتورة مبيعات ودفعة وإشعار دائن يحمل `referenceId` خاصاً بك، ونظامك يتعامل مع `409 reference_id_taken` بقراءة `existingId`. - [ ] رقم الجوال في الـ Query String مكتوب بالشكل `%2B966...` وليس `+966...`. ### المبالغ - [ ] تقرأ `total` و `lines[].unitDiscount` و `amountPaid` و `amountDue` من الرد، وليس من طلبك. - [ ] لدفع الفاتورة كاملة ترسل `payment` بدون `amount`. - [ ] البيع للعميل الفرد يُدفع عند الإصدار، والبيع بالآجل للشركات فقط. ### هيئة الزكاة والضريبة والجمارك - [ ] يعرض نظامك أو يخزّن `zatca.status` لكل فاتورة وإشعار دائن، ويتعامل مع `pending` على أنها صادرة وليست فاشلة. - [ ] المستند المرفوض (`rejected`) يصل إلى شخص لدى التاجر يستطيع تصحيحه. - [ ] نظامك يتعامل مع `422 zatca_rejected` و `422 customer_address_incomplete` في الفواتير الضريبية، واختبرتهما بالعملاء الجاهزين في بيئة الاختبار. - [ ] لا تطلب ملف PDF للفاتورة الضريبية إلا بعد أن تصبح `cleared`. ### الانتقال إلى التشغيل الفعلي - [ ] أصدر التاجر مفتاحاً فعلياً بالصلاحيات والفروع نفسها التي في مفتاح الاختبار. - [ ] يتحقق نظامك من الـ Header `Kosoor-Mode`: موجود بقيمة `test` في بيئة الاختبار، وغير موجود في البيانات الفعلية. - [ ] أرسلت طلباً فعلياً واحداً، ووجده التاجر في **الإعدادات ← المطوّرون ← سجل الطلبات** تحت **فعلي**. - [ ] العملاء الذين أنشأتهم في بيئة الاختبار تنشئهم من جديد، أو تجدهم، في البيانات الفعلية، فعملاء بيئة الاختبار خاصون بها. ### الدعم دعم كسور متاح عبر واتساب فقط على الرقم [+966 12 606 1770](https://wa.me/966126061770)، بلا مكالمات ولا بريد إلكتروني. أرسل الـ `Request-Id` للطلب، وما كنت تتوقعه. المصدر: https://docs.kosoor.com.sa/ar/go-live --- ## العملاء > إنشاء عميل فرد أو شركة، والحقول المطلوبة لكل نوع، وأنواع الهوية، والبحث عن العميل برقم الجوال أو بمعرّفك الخاص، وقواعد التعديل. كل فاتورة مبيعات ودفعة تُنشأ عن طريق الـ API تحدد عميلاً. والعميل إما فرد أو شركة، ونوعه يحدد نوع الفاتورة: الشركة تصدر لها فاتورة ضريبية تعتمدها هيئة الزكاة والضريبة والجمارك، والفرد تصدر له فاتورة مبسطة. التفاصيل في [ابدأ من هنا](https://docs.kosoor.com.sa/ar/start-here.md#standard-and-simplified). | الـ Endpoint | الصلاحية | |---|---| | `POST /v1/customers` | `customers:write` | | `GET /v1/customers` | `customers:read` | | `GET /v1/customers/{id}` | `customers:read` | | `PATCH /v1/customers/{id}` | `customers:write` | ### إنشاء عميل فرد العميل الفرد يحتاج `type` و `phone`. ورقم الجوال بصيغة E.164: علامة `+` ثم رمز الدولة ثم الرقم، مثل `+966501234567`. ```bash curl https://api.kosoor.com.sa/v1/customers \ -H "Authorization: Bearer ksr_test_..." \ -H "Idempotency-Key: 6f1d2c3b-4a5e-4f60-9b7a-8c9d0e1f2a3b" \ -H "Content-Type: application/json" \ -d '{ "type": "person", "firstName": "Sara", "lastName": "Al-Harbi", "phone": "+966501234567", "email": "sara@example.com", "referenceId": "crm-88412" }' ``` الرد `201 Created`: ```json { "id": "0f8fad5b-d9cb-469f-a165-70867728950e", "object": "customer", "type": "person", "referenceId": "crm-88412", "firstName": "Sara", "lastName": "Al-Harbi", "phone": "+966501234567", "email": "sara@example.com", "isActive": true, "branchId": null, "company": null, "address": { "street": null, "buildingNumber": null, "postalCode": null, "district": null, "city": null, "country": null }, "metadata": {}, "standardInvoiceMissing": null, "createdAt": "2026-11-07T09:30:12.512Z" } ``` ويمكن للعميل الفرد أن يرسل أيضاً `address.street` و `address.district` و `address.city` و `address.country`. أما `buildingNumber` و `postalCode` والكائن `company` فللشركات فقط، وكذلك الاستغناء عن رقم الجوال. ### إنشاء عميل من نوع شركة الشركة تحتاج `type` و `company.name` و `address.country`. والدولة رمز من حرفين حسب ISO 3166، مثل `SA`. ولا تحتاج الشركة رقم جوال، ولا تقبل `firstName` ولا `lastName`، فاسمها في `company.name`. ```bash curl https://api.kosoor.com.sa/v1/customers \ -H "Authorization: Bearer ksr_test_..." \ -H "Idempotency-Key: 2b8e4f9a-1c3d-4e5f-8a6b-7c8d9e0f1a2b" \ -H "Content-Type: application/json" \ -d '{ "type": "company", "phone": "+966112345678", "company": { "name": "Al Noor Logistics", "vatNumber": "310123456700003" }, "address": { "street": "Prince Sultan Road", "buildingNumber": "7421", "postalCode": "23521", "district": "Al Rawdah", "city": "Jeddah", "country": "SA" }, "referenceId": "crm-90017" }' ``` الرد `201 Created`: ```json { "id": "3f1c2a6e-8b4d-4e2a-9c1f-5d7e8a9b0c12", "object": "customer", "type": "company", "referenceId": "crm-90017", "firstName": null, "lastName": null, "phone": "+966112345678", "email": null, "isActive": true, "branchId": null, "company": { "name": "Al Noor Logistics", "vatNumber": "310123456700003", "idType": null, "idValue": null, "creditLimit": 0, "creditPeriodDays": null }, "address": { "street": "Prince Sultan Road", "buildingNumber": "7421", "postalCode": "23521", "district": "Al Rawdah", "city": "Jeddah", "country": "SA" }, "metadata": {}, "standardInvoiceMissing": [], "createdAt": "2026-11-07T09:31:05.208Z" } ``` #### ما تحتاجه الفاتورة الضريبية يمكن إنشاء الشركة باسمها ودولتها فقط، لكن الفاتورة الضريبية تحتاج أكثر من ذلك. ويعرض `standardInvoiceMissing` الحقول التي ما زالت ناقصة لهذه الشركة بمساراتها، ويكون فارغاً إذا لم ينقص شيء: | المطلوب | متى | |---|---| | `company.vatNumber`، أو `company.idType` مع `company.idValue` | دائماً | | `address.street` و `address.city` و `address.country` | دائماً | | `address.buildingNumber` و `address.postalCode` و `address.district` | إذا كانت الدولة `SA` | والفاتورة لشركة ما زال ينقصها أي منها ترجع `422 customer_address_incomplete`، والقائمة نفسها في `missing`. تحقق من `standardInvoiceMissing` قبل إصدار الفاتورة، وأكمل الناقص بطلب `PATCH`. #### قواعد الشركة | الحقل | القاعدة | |---|---| | `company.vatNumber` | 15 رقماً تبدأ بـ 3 وتنتهي بـ 3 (`400 invalid_vat_number`)، ولا يكون لعميل آخر (`409 vat_number_taken`) | | `company.idType` و `company.idValue` | يُرسلان معاً، إما كلاهما بقيمة أو كلاهما `null`، وقيمة `idValue` من 1 إلى 50 حرفاً ورقماً | | `address.buildingNumber` | 4 أرقام إذا كانت الدولة `SA`، وحتى 20 حرفاً في غيرها | | `address.postalCode` | 5 أرقام إذا كانت الدولة `SA`، وحتى 20 حرفاً في غيرها | | `company.creditLimit` و `company.creditPeriodDays` | في الرد فقط، ويحددهما التاجر من لوحة التحكم. حد الائتمان 0 يعني بلا حد. | #### أنواع الهوية إذا لم يكن للشركة رقم ضريبي، أو إلى جانبه، يحدد `idType` نوع الرقم الموجود في `idValue`. وهذه رموز الهيئة لهوية المشتري: | `idType` | الرقم | |---|---| | `CRN` | السجل التجاري | | `MOM` | ترخيص وزارة الشؤون البلدية والقروية | | `MLS` | ترخيص وزارة الموارد البشرية | | `700` | الرقم الموحد 700 | | `SAG` | ترخيص الاستثمار (الهيئة العامة للاستثمار سابقاً، وزارة الاستثمار حالياً) | | `NAT` | الهوية الوطنية | | `GCC` | هوية خليجية | | `IQA` | الإقامة | | `PAS` | جواز السفر | | `OTH` | أي هوية أخرى | ### الحقول المشتركة لكل عميل | الحقل | القاعدة | |---|---| | `type` | `person` أو `company`، مطلوب عند الإنشاء ولا يتغير أبداً | | `phone` | بصيغة E.164، ولا يكون لعميل آخر (`409 phone_taken`) | | `firstName` و `lastName` | اسم العميل الفرد، حتى 100 حرف لكل منهما | | `email` | بريد إلكتروني صحيح، حتى 256 حرفاً | | `referenceId` | معرّفك الخاص للعميل، حتى 100 حرف، ولا يكون لعميل آخر (`409 reference_id_taken`)، ويُحدَّد مرة واحدة فقط | | `branchId` | الفرع الذي يتبعه العميل، ويجب أن يكون فرعاً يملك مفتاحك صلاحية عليه (`403 branch_not_allowed`) | | `isActive` | قيمته `true` ما لم ترسل `false`، والعميل المعطّل ترجع فواتيره ودفعاته `422 customer_inactive` | | `metadata` | حتى 20 مفتاحاً، طول كل مفتاح حتى 40 حرفاً، وكل قيمة حتى 500 حرف | عندما تكون القيمة مستخدمة، يحمل الحقل `existingId` في الخطأ معرّف العميل الذي يستخدمها. استخدمه بدل أن تنشئ عميلاً ثانياً: ```json { "error": { "type": "invalid_request_error", "code": "phone_taken", "message": "رقم الجوال مسجّل لعميل آخر.", "param": "phone", "docUrl": "https://docs.kosoor.com.sa/ar/errors#phone_taken", "existingId": "0f8fad5b-d9cb-469f-a165-70867728950e" } } ``` ### البحث عن عميل يقبل `GET /v1/customers` فلاتر تطابق القيمة تماماً، وتُجمع معاً بـ AND: | الـ Parameter | يطابق | |---|---| | `phone` | رقم الجوال بصيغة E.164 | | `vatNumber` | `company.vatNumber` | | `idValue` | `company.idValue` | | `referenceId` | معرّفك الخاص | اكتب علامة `+` في رقم الجوال بالشكل `%2B`، لأن `+` في الـ Query String تُقرأ مسافة. ```bash curl "https://api.kosoor.com.sa/v1/customers?phone=%2B966501234567" \ -H "Authorization: Bearer ksr_test_..." ``` ```json { "data": [ { "id": "0f8fad5b-d9cb-469f-a165-70867728950e", "object": "customer", "type": "person", "referenceId": "crm-88412", "firstName": "Sara", "lastName": "Al-Harbi", "phone": "+966501234567", "email": "sara@example.com", "isActive": true, "branchId": null, "company": null, "address": { "street": null, "buildingNumber": null, "postalCode": null, "district": null, "city": null, "country": null }, "metadata": {}, "standardInvoiceMissing": null, "createdAt": "2026-11-07T09:30:12.512Z" } ], "hasMore": false } ``` إذا كانت `data` فارغة فلا يوجد عميل مطابق. وبدون فلاتر تعرض القائمة كل العملاء صفحة بعد صفحة، الأحدث أولاً. التفاصيل في [تقسيم النتائج](https://docs.kosoor.com.sa/ar/pagination.md). ### تعديل عميل يغيّر `PATCH /v1/customers/{id}` الحقول الموجودة في الـ Body فقط، وتبقى الحقول الأخرى على قيمها. وكذلك داخل `company` و `address`: لا يتغير إلا ما ترسله. ```bash curl -X PATCH https://api.kosoor.com.sa/v1/customers/3f1c2a6e-8b4d-4e2a-9c1f-5d7e8a9b0c12 \ -H "Authorization: Bearer ksr_test_..." \ -H "Idempotency-Key: 8d7c6b5a-4f3e-4d2c-9b1a-0f9e8d7c6b5a" \ -H "Content-Type: application/json" \ -d '{ "email": "accounts@alnoor.example", "address": { "street": "King Abdulaziz Road" } }' ``` والرد هو العميل كاملاً، كما يرجعه `GET`. | ما ترسله | النتيجة | |---|---| | حقل بقيمة | يأخذ الحقل القيمة الجديدة | | `email` أو `phone` بقيمة `null`، أو `firstName` أو `lastName` للعميل الفرد | يُمسح الحقل، ولا يمكن مسح رقم جوال العميل الفرد | | `metadata` | تستبدل القائمة كلها، و `{}` أو `null` تفرغها | | `type` | يجب أن يساوي النوع الحالي، فالعميل لا يتغير نوعه | | `referenceId` | يُقبل مرة واحدة فقط ما دام العميل بلا `referenceId`، وبعدها ترجع أي قيمة مختلفة `400 invalid_parameter` | | `company` أو `address` بقيمة `null` | `400 invalid_parameter`، فأرسل الحقول التي تريد تغييرها بدلاً من ذلك | | `isActive` بقيمة `null` | `400 invalid_parameter`، فأرسل `true` أو `false` | وإذا خالف الطلب أي قاعدة فلا يتغير شيء. المصدر: https://docs.kosoor.com.sa/ar/customers --- ## فواتير المبيعات > إصدار فاتورة المبيعات في طلب واحد، والحقول المطلوبة، والفاتورة الضريبية والمبسطة، والأسعار والضريبة، والدفع عند الإصدار، وقراءة الفواتير. يُنشئ `POST /v1/sales-invoices` فاتورة المبيعات ويصدرها في طلب واحد. لا توجد مرحلة مسودة، والفاتورة بعد إصدارها لا تُصحَّح إلا بإشعار دائن. > **الخصم لا ينزل بالسعر عن الحد الأدنى للمنتج** > > إذا كان للمنتج حد أدنى لسعر البيع في كسور، فأي `unitDiscount` ينزل بسعر الوحدة عنه **يُخفَّض** تلقائياً إلى هذا الحد. والطلب ينجح مع ذلك، دون أي خطأ. > > مثال: منتج سعره 100 ريال قبل الضريبة، وحده الأدنى 92 ريالاً شاملة الضريبة (80 ريالاً قبل الضريبة)، أقصى خصم عليه 20 ريالاً. إذا أرسلت `unitDiscount: 50` فالفاتورة تطبّق 20 فقط. > > اعتمد دائماً على قيمتي `lines[].unitDiscount` و `total` في الرد، وليس على ما أرسلته في الطلب. وإذا أرسلت `payment.amount` فقارنه بقيمة `total` في الرد. ### الحقول المطلوبة كل طلب يرسل `customerId` و `branchId` و `pricesIncludeVat` و `lines`، وكل بند يرسل `itemId` و `quantity`. وإذا أرسلت `payment` فيجب أن يحمل `paymentMethodId`. ويجب أن تحمل كل فاتورة عميلاً محدداً، فلا يدعم الـ API البيع لعميل نقدي غير مسجّل. ### فاتورة ضريبية أم مبسطة نوع العميل هو الذي يحدد نوع الفاتورة: | العميل | الفاتورة | هيئة الزكاة والضريبة والجمارك | |---|---|---| | شركة | فاتورة ضريبية (standard) | تعتمدها **قبل** الرد | | فرد | فاتورة مبسطة (simplified) | تُبلَّغ بها الهيئة بعد الرد | في الفاتورة الضريبية: - إذا اعتمدتها الهيئة: يرجع `201` مع `zatca.status: "cleared"` ورمز QR الذي أرجعته الهيئة. - إذا رفضتها الهيئة: يرجع `422 zatca_rejected` ورسائل الهيئة في `zatcaErrors`، ولا يُحفظ أي شيء. - إذا لم ترد الهيئة في الوقت المحدد: يرجع `201` مع `zatca.status: "pending"`، وتعيد كسور إرسالها لاحقاً، فتصبح `cleared`، أو `rejected` مع رسائل الهيئة في `zatca.errors`. وفي الحالتين تبقى الفاتورة صادرة. الفاتورة الضريبية تحتاج أيضاً عنوان العميل (الشركة) كاملاً مع الرقم الضريبي أو رقم الهوية (الخطأ `customer_address_incomplete` يوضح الناقص)، وفرعاً مربوطاً بالهيئة (`branch_not_onboarded`). وملفا PDF و XML يرجعان `409 invoice_not_cleared` حتى تعتمدها الهيئة، ودائماً إذا رفضتها. أما الفاتورة المبسطة فملف PDF جاهز فوراً، وملف XML يرجع `409 invoice_not_cleared` حتى تقبل الهيئة التبليغ بها، ودائماً إذا رفضتها. ### الأسعار يحدد `pricesIncludeVat` هل كل قيم `unitPrice` و `unitDiscount` في الطلب شاملة الضريبة. البند الذي لا يحمل `unitPrice` يأخذ سعر المنتج في الكتالوج، أما المنتج الذي يختلف سعره حسب حجم السيارة فليس له سعر واحد في الكتالوج، فيجب إرسال `unitPrice` في بنده، وإلا يرجع `400 missing_parameter` على `lines[i].unitPrice`. تحفظ كسور كل سعر قبل الضريبة مقرّباً إلى الهللة، وتقرّب الضريبة مرة واحدة لكل نسبة على مستوى الفاتورة، بنفس طريقة الهيئة في حساب الإجمالي. والسعر الشامل للضريبة يُحوَّل أولاً، لذلك قد يختلف الإجمالي عمّا أرسلته بهللة: 12.00 شاملة الضريبة تصبح 10.43 قبل الضريبة وإجماليها 11.99. والرد يعرض مبالغ البنود دائماً قبل الضريبة. ولدفع فاتورة كهذه بالكامل أرسل `payment` بدون `amount`. ### الدفع | ما ترسله | الفاتورة | |---|---| | بدون `payment` | تُسجَّل على حساب العميل (للعميل من نوع شركة فقط، والعميل الفرد يرجع له `422 on_account_not_allowed`) | | `payment` بدون `amount` | مدفوعة بالكامل | | `payment.amount` أقل من الإجمالي | مدفوعة جزئياً، والباقي على حساب العميل (للعميل من نوع شركة فقط، والعميل الفرد يرجع له `422 on_account_not_allowed`) | | `payment.amount` أكبر من الإجمالي | تُرفض: `400 invalid_parameter` | تقبل `payment.amount` منزلتين عشريتين على الأكثر. وقيمتا `amountPaid` و `amountDue` في الرد توضحان حالة الفاتورة. ### قراءة الفواتير يعرض `GET /v1/sales-invoices` كل فاتورة صادرة في فروع المفتاح، أياً كانت القناة التي أنشأتها. ويضيف `expand=customer` بيانات العميل، ويحتاج صلاحية `customers:read` إلى جانب `sales_invoices:read`. المصدر: https://docs.kosoor.com.sa/ar/sales-invoices --- ## المدفوعات > تسجيل دفعة من العميل، وتخصيصها على الفواتير، وحالات الرفض، وقراءة المدفوعات. يسجّل `POST /v1/payments` المبلغ الذي دفعه العميل ويصدر سند القبض في طلب واحد. لا توجد مرحلة مسودة، والدفعة بعد إصدارها لا تُلغى، وإرجاع المال للعميل يكون بإشعار دائن. ### الحقول المطلوبة كل طلب يرسل `customerId` و `branchId` و `paymentMethodId` و `amount`. وقيمة `amount` أكبر من 0 وبمنزلتين عشريتين على الأكثر. يمثّل `branchId` الفرع الذي استلم المبلغ. وتُرحّل طريقة الدفع إلى الخزينة المحددة لهذا الفرع، أو إلى خزينتها الافتراضية إذا لم يكن للفرع خزينة محددة. وإذا كانت هذه الخزينة تخدم فرعاً آخر فقط، فستُسجَّل الدفعة في ذلك الفرع، لذلك يُرفض الطلب بـ `422 payment_method_not_in_branch`. أما `date` فاختياري، وإذا لم يُرسل يُستخدم تاريخ اليوم بتوقيت السعودية. ويُقبل التاريخ السابق ما لم يكن التاجر قد أقفل تلك الفترة المحاسبية (`422 period_locked`)، أو كان قبل تاريخ فاتورة تسددها الدفعة (`400 invalid_parameter` على `date`). ويقبل `notes` حتى 500 حرف، و `referenceId` حتى 100 حرف. ### التخصيص على الفواتير يحدد `allocations` الفواتير التي تسددها الدفعة، بحد أقصى 100 فاتورة: ```json { "customerId": "3f1c2a6e-8b4d-4e2a-9c1f-5d7e8a9b0c12", "branchId": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d", "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9", "amount": 150, "allocations": [{ "invoiceId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "amount": 115 }] } ``` | القاعدة | الخطأ | |---|---| | مجموع المبالغ المخصصة لا يزيد على `amount` | `400 invalid_parameter` على `allocations` | | الفاتورة لا تتكرر | `400 invalid_parameter` على `allocations[i].invoiceId` | | الفاتورة موجودة، وهي فاتورة مبيعات صادرة لهذا العميل، وفي فرع يملك مفتاحك صلاحية عليه | `422 invoice_not_payable` على `allocations[i].invoiceId` | | قيمة `date` للدفعة ليست قبل تاريخ الفاتورة | `400 invalid_parameter` على `date` | | المبلغ لا يزيد على المتبقي على الفاتورة | `422 allocation_exceeds_due` على `allocations[i].amount` | ما يتبقى بعد التخصيص، أو المبلغ كله إذا لم ترسل `allocations`، يبقى رصيداً دائناً للعميل، ويظهر في الرد في `unallocatedAmount`. وبعد الطلب تشمل قيمتا `amountPaid` و `amountDue` في `GET /v1/sales-invoices/{id}` المبلغ المخصص. > **المفتاح المحدود ببعض الفروع يسدد فواتير هذه الفروع فقط** > > المفتاح المحدود ببعض الفروع لا يخصص الدفعة إلا لفواتير صادرة في هذه الفروع. والفاتورة من أي فرع آخر يرجع لها `422 invoice_not_payable`، تماماً مثل معرّف فاتورة غير موجود، فلا يعرف المفتاح عنها أي شيء. > > ولسداد فاتورة من فرع آخر، سجّل الدفعة بمفتاح يملك صلاحية على ذلك الفرع. ### حالات رفض أخرى | الخطأ | متى يرجع | |---|---| | `422 referenced_resource_not_found` | العميل أو الفرع أو طريقة الدفع غير موجود، أو طريقة الدفع معطّلة، ويحدد `param` الحقل | | `422 customer_inactive` | العميل معطّل | | `422 payment_method_not_in_branch` | خزينة طريقة الدفع تخدم فرعاً آخر | | `409 invoice_busy` على `allocations[i].invoiceId` | طلب آخر على هذه الفاتورة استمر أكثر من 30 ثانية، فأعد إرسال الطلب | | `409 invoice_busy` بدون `param` | تسجيل دفعات أخرى استمر أكثر من 30 ثانية، فأعد إرسال الطلب | | `409 reference_id_taken` | قيمة `referenceId` مستخدمة لدفعة أخرى، ويحمل `existingId` معرّفها إذا كان مفتاحك يملك صلاحية قراءتها | | `422 payment_not_recorded` | رفضتها قاعدة ليس لها كود خاص، والرسالة توضح القاعدة | الدفعة المرفوضة لا يُسجَّل منها أي شيء. ### قراءة المدفوعات يعرض `GET /v1/payments` كل دفعة صادرة في فروع المفتاح، أياً كانت القناة التي سجّلتها، ويوضح `createdVia` القناة. ويمكن التصفية بـ `customerId` و `branchId` و `invoiceId` و `referenceId` و `dateFrom` و `dateTo`، ويرجع `invoiceId` الدفعات التي خُصص منها مبلغ لتلك الفاتورة. وتشمل القائمة الدفعة المسجّلة مع البيع (`POST /v1/sales-invoices` مع `payment`) لعميل من نوع شركة. أما البيع لعميل فرد مدفوعاً عند الإصدار فيبقى المبلغ فيه على الفاتورة نفسها، ويظهر في `amountPaid`، ولا تُنشأ له دفعة. والدفعة المسجّلة من لوحة التحكم قد تحمل خصم سداد، وهو جزء من الدين تنازل عنه التاجر إضافة إلى المبلغ المدفوع. ويظهر في الرد في `discount`، ويمكن أن يصل مجموع التخصيص فيها إلى `amount` مع `discount`. وقيمة `unallocatedAmount` هي `amount` مع `discount` مطروحاً منها التخصيص. أما الدفعة المسجّلة عن طريق الـ API فقيمة `discount` فيها دائماً 0. والدفعة المسجّلة بدون فرع تكون قيمة `branchId` فيها null، ولا يراها إلا مفتاح يملك صلاحية على كل الفروع. المصدر: https://docs.kosoor.com.sa/ar/payments --- ## الإشعارات الدائنة > إصدار إشعار دائن على فاتورة مبيعات صادرة، وإرجاع المبلغ أو خصمه من المستحق، والحد الأقصى للإشعار، ونتيجة هيئة الزكاة والضريبة والجمارك، وحالات الرفض، وقراءة الإشعارات. يُصدر `POST /v1/credit-notes` إشعاراً دائناً على بنود من فاتورة مبيعات صادرة، في طلب واحد. لا توجد مرحلة مسودة، والإشعار الدائن بعد إصداره لا يُلغى. وترجع البضاعة المشمولة بالإشعار إلى المخزون. ### الحقول المطلوبة كل طلب يرسل `invoiceId` و `reason` و `lines`، ومعها الـ Header [`Idempotency-Key`](https://docs.kosoor.com.sa/ar/idempotency.md) مثل أي طلب `POST`: ```json { "invoiceId": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "reason": "refund", "lines": [{ "itemId": "7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "quantity": 1 }], "refund": { "paymentMethodId": "5e6f7a8b-9c0d-4e1f-a2b3-c4d5e6f7a8b9" } } ``` يأخذ الإشعار الفرع والعميل والأسعار من الفاتورة. وكل بند يحدد منتجاً مُباعاً في تلك الفاتورة، مرة واحدة فقط، بكمية `quantity` عددها صحيح ولا تزيد على المتبقي من هذا المنتج. وتُحسب الوحدة بحصتها مما احتسبته الفاتورة للمنتج. وفي الفاتورة الصادرة عن طريق الـ API يُقرَّب كل بند إلى الهللة بالطريقة التي تحسب بها هيئة الزكاة والضريبة والجمارك إجمالي الإشعار، لذلك قد يزيد مجموع الإشعارات على الوحدات واحدةً واحدة بهللة عن إشعار واحد عليها معاً. ولهذا يُسمح للإشعار على هذه الفاتورة بأن يزيد على المتبقي منها بهللة عن كل بند صدر قبله في إشعار سابق، وبهللة واحدة على الأقل، حتى يمكن دائماً إصدار الإشعار على آخر الوحدات. الرد كاملاً موضح في [مرجع الـ API](https://docs.kosoor.com.sa/ar/api/create-credit-note.md). تُرسل قيمة `reason` إلى الهيئة سبباً للإشعار: | `reason` | متى تستخدمها | |---|---| | `cancellation` | إلغاء البيع كله أو جزء منه | | `refund` | إرجاع منتجات أو خدمات وإعادة المبلغ للعميل | | `informationChange` | تصحيح بيانات خاطئة في الفاتورة | ويقبل `notes` حتى 500 حرف ويُطبع على الإشعار. ويقبل `referenceId` حتى 100 حرف، ولا يتكرر في أي فاتورة أو إشعار آخر لدى التاجر. ### إرجاع المبلغ مع `refund` يُرجع إجمالي الإشعار عن طريق `refund.paymentMethodId`، من خزينة طريقة الدفع هذه في فرع الفاتورة: - طريقة الدفع التي تخدم خزينتها فرعاً آخر فقط تُرفض (`422 payment_method_not_in_branch`). - لا يزيد المبلغ المرجع على ما حُصّل على الفاتورة، مطروحاً منه ما أُرجع منها سابقاً (`422 refund_exceeds_collected`). وبدون `refund` يُخصم الإجمالي من المبلغ المستحق على العميل، فتنقص قيمة `amountDue` في فاتورة المبيعات بمقداره دون أن تقل عن 0، وما يزيد على ذلك يبقى رصيداً دائناً للعميل. ولا يكون ذلك إلا لعميل يمكن أن يُحفظ له رصيد: - العميل من نوع شركة يمكن له ذلك دائماً. - العميل الفرد لا يحمل رصيداً، فلا يكون ذلك له إلا في حدود المستحق على الفاتورة. وإذا زاد الإشعار على ذلك فأرسل `refund` (`400 invalid_parameter` على `refund`). - الفاتورة التي ليس لها عميل يجب إرسال `refund` معها (`400 invalid_parameter` على `refund`). وهذا ينطبق أيضاً على البيع المدفوع ببطاقة في نقطة البيع: بدون `refund` يذهب الإشعار إلى رصيد العميل، ولا يُرجع أي مبلغ إلى البطاقة. يظهر المبلغ المرجع في الرد في `refund.amount`، وتكون قيمة `refund` هي null إذا ذهب الإشعار إلى رصيد العميل. ### الحد الأقصى للإشعار مجموع الإشعارات على الفاتورة لا يتجاوز إجماليها، أياً كانت القناة. فالاسترجاع من نقطة البيع أو من لوحة التحكم، ومرتجعات المبيعات، والإشعارات الصادرة عن طريق الـ API، كلها تُحسب من الإجمالي نفسه ومن الكمية المتبقية من كل منتج. والإشعار الذي يتجاوز المتبقي يرجع له `422 credit_exceeds_invoice`: - تكون قيمة `param` هي `lines[i].quantity` إذا طلب البند كمية أكثر من المتبقي من منتجه. - وتكون `lines` إذا كان إجمالي الإشعار أكثر من المتبقي على الفاتورة. وإذا وصل إشعاران على الفاتورة نفسها في اللحظة نفسها فإنهما يصدران واحداً بعد الآخر، فيُتحقَّق من الثاني مقابل ما بقي بعد الأول. وإذا استمر إصدار الأول أكثر من 30 ثانية، كأن ينتظر رد الهيئة، يرجع للثاني `409 invoice_busy`، فأعد إرساله. ### هيئة الزكاة والضريبة والجمارك نوع الإشعار لدى الهيئة هو نوع فاتورته نفسها: - **الفاتورة الضريبية (standard)**: - تعتمد الهيئة الإشعار قبل الرد. - إذا رفضته لا يُحفظ أي شيء، ويرجع `422 zatca_rejected` مع رسائل الهيئة في `zatcaErrors`. - إذا تأخر رد الهيئة يرجع `201` بقيمة `pending` في `zatca.status`، ثم تعيد كسور إرساله لاحقاً، فتصبح `cleared`، أو `rejected` مع رسائل الهيئة في `zatca.errors`. وفي الحالتين يبقى الإشعار صادراً. - الفاتورة التي لم تعتمدها الهيئة بعد لا يصدر عليها إشعار (`409 invoice_not_cleared` على `invoiceId`). أعد المحاولة عندما تصبح قيمة `zatca.status` هي `cleared` في `GET /v1/sales-invoices/{id}`، وهذا الطلب يحتاج صلاحية `sales_invoices:read`. ولا يُحفظ أي رد `4xx`، لذلك يمكن أن تستخدم إعادة المحاولة المفتاح `Idempotency-Key` نفسه. - **الفاتورة المبسطة (simplified)**: يُبلَّغ بالإشعار للهيئة بعد الرد. وتبقى قيمة `zatca.status` هي `pending` حتى يتم التبليغ، ثم تصبح `reported`، أو `rejected` مع رسائل الهيئة في `zatca.errors`. والفاتورة التي رفضتها الهيئة لا يصدر عليها إشعار عن طريق الـ API (`422 invoice_not_creditable`)، فيجب أن يصحح التاجر البيع أولاً. ويرجع `GET /v1/credit-notes/{id}/pdf` و `GET /v1/credit-notes/{id}/xml` ملفات الإشعار: - الإشعار الضريبي (standard) يرجع ملفاه `409 invoice_not_cleared` حتى تعتمده الهيئة، ودائماً إذا رفضته. - الإشعار المبسط (simplified) يكون ملف PDF الخاص به جاهزاً فوراً، أما ملف XML فيرجع `409 invoice_not_cleared` حتى تقبل الهيئة التبليغ، ودائماً إذا رفضته. ### حالات الرفض | الخطأ | متى يرجع | |---|---| | `422 referenced_resource_not_found` | الفاتورة غير موجودة أو في فرع لا يملك مفتاحك صلاحية عليه، أو طريقة الدفع في `refund` غير موجودة أو معطّلة، ويحدد `param` الحقل | | `422 invoice_not_creditable` | الفاتورة ليست فاتورة مبيعات صادرة، أو مستوردة، أو رفضتها الهيئة | | `409 invoice_not_cleared` على `invoiceId` | الفاتورة ضريبية ولم تعتمدها الهيئة بعد | | `400 invalid_parameter` على `lines[i].itemId` | المنتج غير مُباع في هذه الفاتورة | | `422 credit_exceeds_invoice` | كمية البند أكثر من المتبقي من المنتج، أو الإجمالي أكثر من المتبقي على الفاتورة | | `400 invalid_parameter` على `refund` | لم يُرسل `refund`، والعميل لا يمكن أن يُحفظ له الإشعار رصيداً | | `422 payment_method_not_in_branch` | خزينة طريقة الدفع في `refund` تخدم فرعاً آخر فقط | | `422 refund_exceeds_collected` | المبلغ المرجع أكثر مما حُصّل على الفاتورة | | `422 used_subscription_not_refundable` | الفاتورة لاشتراك استُخدمت منه زيارات | | `422 refund_method_not_supported` | دُفعت الفاتورة ببطاقة عبر المتجر الإلكتروني للتاجر، ولا يمكن بدء استرجاع إلى البطاقة عن طريق الـ API | | `422 loyalty_refund_blocked` | تعذّر إصدار الإشعار بسبب منصة الولاء: إما أن النقاط أو الأختام المكتسبة من الفاتورة استُخدمت، أو تعذّر الوصول إلى منصة الولاء، أو لم تصلها الفاتورة بعد، والرسالة توضح السبب. أعد المحاولة مرة واحدة بعد دقائق، فإن رُفض مرة أخرى يمكن للتاجر إصداره من لوحة التحكم حيث يستطيع المشرف تجاوز الرفض | | `409 reference_id_taken` | قيمة `referenceId` مستخدمة لفاتورة أو إشعار آخر، ويحمل `existingId` معرّفه فقط إذا كان إشعاراً دائناً يملك مفتاحك صلاحية قراءته | | `409 invoice_busy` على `invoiceId` | طلب آخر على الفاتورة نفسها استمر أكثر من 30 ثانية، فأعد إرسال الطلب | | `422 zatca_rejected` | رفضت الهيئة إشعار الفاتورة الضريبية، ولم يُحفظ أي شيء، ورسائل الهيئة في `zatcaErrors` | | `422 credit_note_not_issued` | رفضته قاعدة ليس لها كود خاص، والرسالة توضح القاعدة | إذا رُفض الطلب فلا يصدر أي إشعار ولا يُحفظ أي شيء. ### قراءة الإشعارات الدائنة يعرض `GET /v1/credit-notes` كل إشعار دائن صادر في فروع المفتاح، أياً كانت القناة التي أصدرته، ومنها الاسترجاع من نقطة البيع أو من لوحة التحكم، ويوضح `createdVia` القناة. ويمكن التصفية بـ `invoiceId` و `customerId` و `branchId` و `referenceId` و `issuedAfter` و `issuedBefore` و `zatcaStatus`. ولا تظهر في القائمة مرتجعات المبيعات والإشعارات الدائنة التي تُنشأ من شاشات المحاسبة في لوحة التحكم، مع أنها تُحسب من المتبقي على الفاتورة. وتكون قيمة `refund.paymentMethodId` هي null إذا لم يحدد الاسترجاع طريقة دفع، وهو الغالب في الاسترجاع من نقطة البيع: فقد أُرجع المبلغ من صندوق الفرع نفسه، أو إلى البطاقة التي دُفعت بها الفاتورة. والاسترجاع من نقطة البيع الموزع بين النقد والبطاقة يظهر استرجاعاً واحداً بمجموع المبلغين. والاسترجاع الذي صدر قبل أن تُسجَّل أسباب الإشعارات تكون فيه قيمة `reason` هي `cancellation`، وهو السبب الذي أُرسل به إلى الهيئة. المصدر: https://docs.kosoor.com.sa/ar/credit-notes --- ## المصادقة > كيف ترسل مفتاح الـ API، والصلاحيات التي يحملها المفتاح، وحصره في فروع معيّنة، ومتى يتوقف المفتاح عن العمل. أرسل مفتاح الـ API في الـ Header `Authorization` بصيغة Bearer مع كل طلب: ```http Authorization: Bearer ksr_live_3f9a... ``` يبدأ المفتاح بـ `ksr_live_` وبعده 64 حرفاً بالنظام الست عشري (hex)، أما مفتاح الاختبار فيبدأ بـ `ksr_test_` ولا يصل إلا إلى بيئة الاختبار (راجع [وضع الاختبار](https://docs.kosoor.com.sa/ar/test-mode.md)). احفظه على السيرفر عندك فقط، لأن أي شخص يحصل عليه يستطيع التصرف باسم التاجر. ### الصلاحيات يحدد التاجر لكل مفتاح مستوى صلاحية على كل مورد، بالصيغة `resource:level`: | الصلاحية | ماذا تتيح | |---|---| | `customers:read` | عرض قائمة العملاء وبيانات كل عميل | | `customers:write` | كل ما سبق، بالإضافة إلى إنشاء العملاء وتعديلهم | | `sales_invoices:read` | عرض فواتير المبيعات وتحميلها بصيغة PDF و XML | | `sales_invoices:write` | كل ما سبق، بالإضافة إلى إصدار فواتير المبيعات | | `payments:read` | عرض المدفوعات وبيانات كل دفعة | | `payments:write` | كل ما سبق، بالإضافة إلى تسجيل المدفوعات | | `credit_notes:read` | عرض الإشعارات الدائنة وبيانات كل إشعار، مع ملفي PDF و XML | | `credit_notes:write` | كل ما سبق، بالإضافة إلى إصدار الإشعارات الدائنة | صلاحية `write` تشمل `read` تلقائياً. إذا أرسلت طلباً لا يملك المفتاح صلاحيته، يرجع الخطأ `403 missing_permission`، ويوضح الحقل `requiredPermission` الصلاحية المطلوبة. ستُضاف موارد أخرى مع توسّع الـ API. ### الفروع قد يحصر التاجر المفتاح في فروع معيّنة. أي طلب يخص فرعاً خارجها يرجع بالخطأ `403 branch_not_allowed`. ### متى يُرفض المفتاح | الكود | الحالة | السبب | |---|---|---| | `missing_api_key` | 401 | الـ Header `Authorization: Bearer` غير موجود | | `invalid_api_key` | 401 | صيغة المفتاح خاطئة أو المفتاح غير موجود | | `revoked_api_key` | 401 | ألغى التاجر المفتاح | | `expired_api_key` | 401 | انتهت صلاحية المفتاح | | `tenant_suspended` | 403 | حساب التاجر في كسور موقوف | إعادة المحاولة لن تحل أياً من هذه الحالات. اطلب من التاجر مفتاحاً جديداً، أو أن يعيد تفعيل حسابه. المصدر: https://docs.kosoor.com.sa/ar/authentication --- ## منع تكرار الطلبات (Idempotency) > كيف يجعل الـ Header Idempotency-Key إعادة محاولة طلبات POST و PATCH آمنة، وماذا ترجع إعادة المحاولة، ومتى تعيد المحاولة. قد ينقطع الاتصال بعد أن تحفظ كسور طلبك وقبل أن يصلك الرد. في هذه الحالة يجب ألا تؤدي إعادة المحاولة إلى إنشاء العميل أو الفاتورة مرة ثانية. لهذا يجب أن يحمل كل طلب `POST` و`PATCH` الـ Header `Idempotency-Key`، وعند إعادة المحاولة ترسل المفتاح نفسه. ```bash curl https://api.kosoor.com.sa/v1/customers \ -H "Authorization: Bearer ksr_live_..." \ -H "Idempotency-Key: 9b2c4c0e-6a51-4c0b-8a43-3c2f2f1a7d10" \ -H "Content-Type: application/json" \ -d '{"type":"person","firstName":"Sara","phone":"+966501234567"}' ``` ### شروط المفتاح - من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أسهل خيار هو UUID جديد لكل عملية. - المفاتيح مرتبطة بمفتاح الـ API الذي أرسلها، فلا مشكلة إذا استخدم مفتاح API آخر القيمة نفسها. - تحتفظ كسور بالمفتاح لمدة 30 يوماً. ### ماذا يحدث عند إعادة المحاولة | ما ترسله | ما ترجعه كسور | |---|---| | المفتاح نفسه مع نفس الـ Method والمسار والـ Body، بعد طلب ناجح | الرد المحفوظ نفسه مع `Idempotent-Replayed: true`، دون تنفيذ أي شيء من جديد | | المفتاح نفسه مع طلب مختلف | `422 idempotency_key_reused` | | المفتاح نفسه والطلب الأول لم ينتهِ بعد | `409 idempotency_in_progress` مع `Retry-After: 1` | | المفتاح نفسه بعد طلب فاشل | يُنفَّذ الطلب من جديد، لأن الردود الناجحة فقط هي التي تُحفظ | | بدون مفتاح | `400 missing_idempotency_key` | إذا توقف طلب في منتصف التنفيذ، يبقى مفتاحه محجوزاً لمدة 5 دقائق، وبعدها تنفّذه إعادة المحاولة بالمفتاح نفسه. ### متى تعيد المحاولة أعد المحاولة بالمفتاح نفسه عند انتهاء مهلة الطلب (Timeout)، أو انقطاع الاتصال، أو وصول `409 idempotency_in_progress`، أو أي خطأ `5xx`. أما أخطاء `4xx` فلا تعِد الطلب كما هو، لأنه سيرجع بالخطأ نفسه: صحّح الطلب ثم أرسله بمفتاح جديد. ويُستثنى من ذلك ثلاثة أخطاء تعتمد على الوقت، فأعد المحاولة فيها لاحقاً بالمفتاح نفسه، لأن ردود `4xx` لا تُحفظ: `409 invoice_not_cleared` بعد أن تعتمد هيئة الزكاة والضريبة والجمارك الفاتورة، و `409 invoice_busy` بعد ثوانٍ، و `422 loyalty_refund_blocked` مرة واحدة بعد دقائق، فإن رجع بالخطأ نفسه يصدر التاجر الإشعار من لوحة التحكم حيث يستطيع المشرف تجاوز الرفض. المصدر: https://docs.kosoor.com.sa/ar/idempotency --- ## تقسيم النتائج (Pagination) > كيف ترجع القوائم أحدث السجلات أولاً، صفحة في كل طلب، باستخدام limit و startingAfter و hasMore. ترجع الـ Endpoints التي تعرض قوائم أحدث السجلات أولاً، صفحة في كل طلب. ```bash curl "https://api.kosoor.com.sa/v1/customers?limit=50" \ -H "Authorization: Bearer ksr_live_..." ``` ```json { "data": [ { "id": "0f8fad5b-d9cb-469f-a165-70867728950e" } ], "hasMore": true } ``` | الـ Parameter | الوصف | |---|---| | `limit` | من 1 إلى 100، والقيمة الافتراضية 20 | | `startingAfter` | الـ `id` لآخر سجل في الصفحة السابقة | طالما أن `hasMore` قيمته `true`، اطلب الصفحة التالية وضع في `startingAfter` آخر `id` وصلك. وإذا أُضيف سجل جديد أثناء تنقلك بين الصفحات، فإنه يظهر في بداية القائمة، ولا يغيّر محتوى الصفحات التي تقرؤها. المصدر: https://docs.kosoor.com.sa/ar/pagination --- ## Webhooks > اجعل كسور ترسل نتائج هيئة الزكاة والضريبة والجمارك إلى نظامك فور وصولها، وتحقّق من كل طلب، وتعامل مع إعادة الإرسال والتكرار. فاتورة المبيعات أو الإشعار الدائن الذي يرجع بـ `zatca.status: "pending"` تصله نتيجة الهيئة لاحقاً. بدلاً من أن تسأل عنها كل فترة، سجّل Webhook Endpoint، وسترسل كسور إلى نظامك طلب `POST` عبر HTTPS لحظة وصول النتيجة. تتبع كسور معيار [Standard Webhooks](https://www.standardwebhooks.com/)، فمكتبات `standardwebhooks` المفتوحة المصدر تتحقق من طلباتها كما هي. ### تسجيل Endpoint ```bash curl https://api.kosoor.com.sa/v1/webhook-endpoints \ -H "Authorization: Bearer ksr_live_..." \ -H "Idempotency-Key: 6f1d2c7e-hooks-erp" \ -H "Content-Type: application/json" \ -d '{"url":"https://erp.example.com/kosoor/webhooks","events":["salesInvoice.zatcaCleared","salesInvoice.zatcaReported","salesInvoice.zatcaRejected"]}' ``` ```json { "id": "4d7b2e91-3c6a-4f85-9e1d-0a8c5b7f2e63", "object": "webhookEndpoint", "url": "https://erp.example.com/kosoor/webhooks", "events": ["salesInvoice.zatcaCleared", "salesInvoice.zatcaReported", "salesInvoice.zatcaRejected"], "status": "enabled", "disabledReason": null, "secret": "whsec_IhPDzwacK7vjZSdgdWIEKuvhvZqOrOozQwbRj2XIT9w=", "createdAt": "2026-11-07T09:30:00Z" } ``` **احفظ الـ `secret` الآن.** لا يظهر إلا في هذا الرد وفي رد تغيير السر، وتحتاجه للتحقق من كل طلب. - يحتاج المفتاح الصلاحية `webhooks:write` لإدارة الـ Endpoints (و `webhooks:read` لعرضها)، وصلاحية قراءة ما تتحدث عنه الأحداث: `sales_invoices:read` لأحداث `salesInvoice.*`، و `credit_notes:read` لأحداث `creditNote.*`. - الـ Endpoint تابع للمفتاح الذي أنشأه، فلا تراه المفاتيح الأخرى، ويتوقف عن الاستقبال إذا أُبطل المفتاح أو انتهت صلاحيته. والمفتاح المحدود بفروع معينة لا تصله إلا أحداث مستندات تلك الفروع. - الـ Endpoints الخاصة بمفتاح الاختبار موجودة في بيئة الاختبار، ولا تصلها إلا أحداث بيئة الاختبار، وتبقى بعد إعادة تعيينها. - يجب أن يكون الـ URL بـ `https`، بدون اسم مستخدم أو كلمة مرور، وأن يشير اسم النطاق إلى عنوان عام (Public). - لكل مفتاح 10 Endpoints على الأكثر، والحادي عشر يرجع `422 webhook_endpoint_limit`. | | | |---|---| | `GET /v1/webhook-endpoints` | عرض الـ Endpoints الخاصة بالمفتاح (بدون الأسرار) | | `GET /v1/webhook-endpoints/{id}` | قراءة Endpoint واحد | | `DELETE /v1/webhook-endpoints/{id}` | حذفه، ويُلغى كل ما كان ينتظر الإرسال إليه | | `POST /v1/webhook-endpoints/{id}/rotate-secret` | إصدار سر جديد (انظر [تغيير السر](#تغيير-السر)) | | `POST /v1/webhook-endpoints/{id}/test` | إرسال حدث `webhookEndpoint.test` الآن | كل طلب `POST` و `DELETE` يحمل `Idempotency-Key` مثل بقية الـ API. ### الأحداث | الحدث | متى يُرسل | |---|---| | `salesInvoice.zatcaCleared` | اعتمدت الهيئة فاتورة ضريبية (B2B) كانت قد رجعت بـ `pending` | | `salesInvoice.zatcaReported` | قبلت الهيئة الإبلاغ عن فاتورة مبسطة (B2C) | | `salesInvoice.zatcaRejected` | رفضت الهيئة الفاتورة | | `creditNote.zatcaCleared` | الأحداث الثلاثة نفسها للإشعار الدائن | | `creditNote.zatcaReported` | | | `creditNote.zatcaRejected` | | | `webhookEndpoint.test` | استدعيت `POST /v1/webhook-endpoints/{id}/test`، ويُرسل مهما كانت الأحداث المشترك فيها | النتيجة التي رجعت في رد طلب الإنشاء نفسه (فاتورة ضريبية اعتمدتها الهيئة عند الإصدار) لا يُرسل لها حدث، لأنها وصلتك في الرد. ### ماذا ترسل كسور ```http POST /kosoor/webhooks HTTP/1.1 Content-Type: application/json User-Agent: Kosoor-Webhooks/1.0 webhook-id: 019a5f3c-7e21-7b4d-8a6f-2c9e1d0b5a74 webhook-timestamp: 1794043800 webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= ``` ```json { "id": "019a5f3c-7e21-7b4d-8a6f-2c9e1d0b5a74", "object": "event", "type": "salesInvoice.zatcaReported", "createdAt": "2026-11-07T09:31:12Z", "data": { "id": "5c8a1f9e-2d7b-4c3a-9e6f-1a4b8d0c7e52", "object": "salesInvoice", "number": "INV-10045", "referenceId": "order-10045", "zatca": { "kind": "simplified", "status": "reported", "uuid": "b7c1e0d2-4f3a-4c8e-9a1b-2d5e6f7a8b9c", "counter": 1287, "hash": "...", "qr": "...", "submittedAt": "2026-11-07T09:31:10Z", "clearedAt": null, "reportedAt": "2026-11-07T09:31:12Z", "errors": [] } } } ``` - الـ `data` يحدد المستند ويحمل كتلة `zatca` الخاصة به، وهي نفس الكتلة التي يرجعها `GET`. اقرأ المستند كاملاً بـ `GET /v1/sales-invoices/{id}` أو `GET /v1/credit-notes/{id}` إذا احتجت أكثر من ذلك. - الـ `id` هو معرّف الحدث، ويساوي الـ Header `webhook-id`، ولا يتغير في أي إعادة إرسال. - الـ `createdAt` هو وقت وقوع الحدث بتوقيت UTC، أما `webhook-timestamp` فهو وقت إرسال هذه المحاولة بثواني Unix. ### تحقّق من كل طلب أي أحد يستطيع إرسال `POST` إلى الـ URL الخاص بك، فتحقّق من التوقيع قبل أن تثق بالـ Body، وتحقّق منه على **بايتات الـ Body كما وصلت**: إذا أعاد JSON Parser بناء الـ Body فستتغير البايتات. التوقيع هو HMAC-SHA256، مفتاحه بايتات السر (الجزء بعد `whsec_` بعد فك ترميز base64)، ويُحسب على `webhook-id` و `webhook-timestamp` والـ Body مفصولة بنقاط. يحمل `webhook-signature` قيمة `v1,` واحدة أو أكثر مفصولة بمسافات، فاقبل الطلب إذا طابقت أي واحدة منها. وارفض `webhook-timestamp` الذي يبعد أكثر من 5 دقائق عن ساعتك، حتى لا يُعاد إرسال طلب مسروق لاحقاً. #### Node.js ```bash npm install standardwebhooks ``` ```js import express from 'express'; import { Webhook } from 'standardwebhooks'; const webhook = new Webhook(process.env.KOSOOR_WEBHOOK_SECRET); // "whsec_..." const app = express(); app.post('/kosoor/webhooks', express.raw({ type: 'application/json' }), (req, res) => { let event; try { event = webhook.verify(req.body, req.headers); // checks the signature and the 5-minute window } catch { return res.status(400).end(); } res.status(204).end(); // answer first queueForProcessing(event); // then work }); ``` #### Python ```bash pip install standardwebhooks ``` ```python import os from flask import Flask, request from standardwebhooks import Webhook webhook = Webhook(os.environ["KOSOOR_WEBHOOK_SECRET"]) # "whsec_..." app = Flask(__name__) @app.post("/kosoor/webhooks") def kosoor_webhook(): try: event = webhook.verify(request.get_data(), dict(request.headers)) except Exception: return "", 400 queue_for_processing(event) return "", 204 ``` #### C# (ASP.NET Core) ```csharp using System.Security.Cryptography; using System.Text; app.MapPost("/kosoor/webhooks", async (HttpRequest request) => { using var reader = new StreamReader(request.Body, Encoding.UTF8); var body = await reader.ReadToEndAsync(); var id = request.Headers["webhook-id"].ToString(); var timestamp = request.Headers["webhook-timestamp"].ToString(); var signatures = request.Headers["webhook-signature"].ToString().Split(' '); if (!long.TryParse(timestamp, out var seconds) || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - seconds) > 300) return Results.BadRequest(); var secret = Environment.GetEnvironmentVariable("KOSOOR_WEBHOOK_SECRET")!; // "whsec_..." var key = Convert.FromBase64String(secret["whsec_".Length..]); var expected = Encoding.UTF8.GetBytes( "v1," + Convert.ToBase64String(HMACSHA256.HashData(key, Encoding.UTF8.GetBytes($"{id}.{timestamp}.{body}")))); if (!signatures.Any(s => CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(s), expected))) return Results.BadRequest(); QueueForProcessing(body); return Results.NoContent(); }); ``` #### التحقق يدوياً لترى طريقة الحساب باستخدام الـ Shell فقط: ```bash SECRET="whsec_IhPDzwacK7vjZSdgdWIEKuvhvZqOrOozQwbRj2XIT9w=" ID="019a5f3c-7e21-7b4d-8a6f-2c9e1d0b5a74" TS="1794043800" BODY='{"id":"019a5f3c-7e21-7b4d-8a6f-2c9e1d0b5a74","object":"event",...}' KEY_HEX=$(echo -n "${SECRET#whsec_}" | base64 -d | xxd -p -c 256) printf 'v1,%s\n' "$(printf '%s.%s.%s' "$ID" "$TS" "$BODY" \ | openssl dgst -sha256 -mac HMAC -macopt hexkey:"$KEY_HEX" -binary | base64)" ``` الناتج هو ما يحمله `webhook-signature` لهذا الـ Body. ### ردّ بسرعة ثم اعمل - ردّ بأي `2xx` خلال **15 ثانية**. الرد الأبطأ يُحسب فشلاً ويُعاد إرسال الحدث. - نفّذ العمل بعد الرد: ضع الحدث في Queue، وردّ بـ `204`، ثم عالجه من الـ Queue. - لا تتبع كسور الـ Redirects، فأي رد `3xx` يُحسب فشلاً. سجّل الـ URL النهائي. ### إعادة الإرسال تفشل المحاولة إذا ردّ الـ Endpoint بأي شيء غير `2xx`، أو تأخر أكثر من 15 ثانية، أو تعذّر الوصول إليه. وعندها تعيد كسور المحاولة بعد: | بعد المحاولة | الانتظار | |---|---| | 1 | 5 ثوانٍ | | 2 | 5 دقائق | | 3 | 30 دقيقة | | 4 | ساعتان | | 5 | 5 ساعات | | 6 | 10 ساعات | | 7 | 14 ساعة | | 8 | 20 ساعة | | 9 | 24 ساعة | يُزاد كل انتظار عشوائياً بما يصل إلى 10%، حتى لا تصلك الأحداث المتراكمة دفعة واحدة. وبعد المحاولة العاشرة، أي بعد 3 أيام تقريباً من الأولى، يُسجَّل الحدث فاشلاً. وإذا رددت بـ `429` أو `503` مع الـ Header `Retry-After`، تنتظر كسور هذه المدة على الأقل قبل المحاولة التالية. ### التكرار والترتيب - **قد يصلك الحدث نفسه مرتين**: إذا ضاع ردك في الطريق، أو أُعيد إرساله من لوحة التحكم. احفظ الـ `webhook-id` لكل حدث عالجته، وتجاهل الحدث الذي سبق أن رأيته. - **قد تصل الأحداث بغير ترتيبها.** فإعادة الإرسال تتداخل مع الأحداث الأحدث. اعتمد على قيمة `zatca.status` الحالية للمستند (واقرأها بـ `GET` إذا شككت)، لا على ترتيب وصول الأحداث. ### متى تتوقف كسور عن الإرسال | `disabledReason` | السبب | |---|---| | `gone` | ردّ الـ Endpoint بـ `410 Gone`، فتتوقف كسور فوراً | | `failing` | لم يُسلَّم شيء لمدة 5 أيام | | `merchant` | ألغى التاجر تفعيله من لوحة التحكم | يظهر الـ Endpoint المتوقف بـ `status: "disabled"` في `GET`، ويُلغى كل ما كان ينتظر الإرسال إليه. ويستطيع التاجر تفعيله من جديد من **المطوّرون ← Webhooks** في لوحة التحكم، وهناك تظهر كل الأحداث المرسلة مع محاولاتها وآخر رد من الـ Endpoint، وزر **إعادة إرسال** يرسل الحدث مرة أخرى بالـ `webhook-id` نفسه. ![تبويب Webhooks: الـ Endpoints، ثم كل حدث أُرسل مع محاولاته وآخر رد](https://docs.kosoor.com.sa/ar/img/onboarding/webhooks.ar.png.md) ### تغيير السر ```bash curl -X POST https://api.kosoor.com.sa/v1/webhook-endpoints/4d7b2e91-3c6a-4f85-9e1d-0a8c5b7f2e63/rotate-secret \ -H "Authorization: Bearer ksr_live_..." \ -H "Idempotency-Key: rotate-2026-11" ``` يحمل الرد الـ `secret` الجديد. ولمدة **24 ساعة** توقّع كسور كل طلب بالسر القديم والجديد معاً، فيحمل `webhook-signature` قيمتين `v1,`. انشر السر الجديد خلال هذه المدة، وسيستمر التحقق بأي واحد منهما. ### جرّبها 1. أنشئ مفتاح اختبار و Endpoint يشير إلى URL تستطيع مراقبته، مثل خدمات Request Bin. 2. أرسل `POST /v1/webhook-endpoints/{id}/test`، والـ `id` في الرد هو الـ `webhook-id` الذي سيصلك. 3. اشترك في `salesInvoice.zatcaCleared` وبِع للعميل **Sandbox ZATCA Pending Co** في بيئة الاختبار: ترجع الفاتورة بـ `pending`، ثم تعتمدها بيئة الهيئة التجريبية بعد قليل ويصلك الحدث. التفاصيل في [وضع الاختبار](https://docs.kosoor.com.sa/ar/test-mode.md). المصدر: https://docs.kosoor.com.sa/ar/webhooks --- ## وضع الاختبار > بناء الربط على بيئة الاختبار الخاصة بالتاجر بمفتاح اختبار، وماذا تحتوي، ومتى تكون مشغولة، والعملاء الجاهزون بنتائج معروفة. ابنِ الربط على بيئة اختبار قبل أن تلمس بيانات التاجر الفعلية. مفتاح الاختبار يعمل تماماً مثل المفتاح الفعلي، على نفس الـ Host ونفس المسارات، لكن كل طلب يصل إلى بيئة الاختبار الخاصة بالتاجر: نسخة منفصلة من بياناته الأساسية، فواتيرها تُوقَّع بشهادات الاختبار من هيئة الزكاة والضريبة والجمارك ولا تصل أبداً إلى سجلاته الفعلية في الهيئة. ### الحصول على مفتاح اختبار ينشئ التاجر بيئة الاختبار من لوحة التحكم في **الإعدادات ← المطوّرون ← بيئة الاختبار**، ثم يصدر مفتاح اختبار من **الإعدادات ← المطوّرون ← مفاتيح الربط**. يبدأ مفتاح الاختبار بـ `ksr_test_`: ```http Authorization: Bearer ksr_test_7c1e... ``` كل رد على طلب بمفتاح اختبار يحمل الـ Header `Kosoor-Mode: test`. تحقق منه في الكود عندك إذا أردت التأكد أن الطلب لم يصل إلى البيانات الفعلية. ### ماذا تحتوي بيئة الاختبار - فروع التاجر وأصنافه وطرق الدفع وشجرة الحسابات والمستودعات، منسوخة وقت بناء البيئة. - لا شيء من عملائه أو فواتيره أو مدفوعاته. أنشئ ما تحتاجه بنفسك، أو استخدم البيانات الجاهزة في الأسفل. - اسم التاجر وعنوانه، مع الرقم الضريبي التجريبي من الهيئة `399999999900003`. ملفات PDF من بيئة الاختبار تحمل علامة حمراء **نسخة اختبار — ليست فاتورة ضريبية**. ولا ترسل بيئة الاختبار أي رسائل SMS. ### عندما تكون البيئة مشغولة أثناء بناء بيئة الاختبار أو إعادة تعيينها، يرجع مفتاح الاختبار بالخطأ `503 sandbox_unavailable` مع الـ Header `Retry-After: 30`. انتظر ثم أرسل نفس الطلب مرة أخرى. إذا تعذّر بناء البيئة، يرجع مفتاح الاختبار بالخطأ `503 sandbox_failed` بدون `Retry-After`، ولن تنجح إعادة المحاولة حتى يعيد التاجر تعيين البيئة. وأثناء حذف البيئة، ترجع مفاتيح الاختبار بالخطأ `401 invalid_api_key`. إعادة التعيين تبني البيئة من جديد من بيانات التاجر الأساسية الحالية. مفاتيح الاختبار تبقى تعمل، وتُمسح كل المستندات وسجل الطلبات. أما حذف البيئة فيوقف كل مفاتيح الاختبار. ### البيانات الجاهزة كل بيئة اختبار فيها هؤلاء العملاء وصنف واحد بمعرّفات ثابتة، ولكل منهم نتيجة معروفة. استخدمهم لاختبار التعامل مع الأخطاء عندك دون أن تجهّز البيانات بنفسك. | المعرّف | الاسم | النتيجة | |---|---|---| | `5a4db0c5-0000-4000-8000-000000000001` | Sandbox Person | الفاتورة المبسطة تنجح | | `5a4db0c5-0000-4000-8000-000000000002` | Sandbox Standard Co | الفاتورة الضريبية تُعتمد من الهيئة | | `5a4db0c5-0000-4000-8000-000000000003` | Sandbox CRN Co | منشأة لها سجل تجاري وبلا رقم ضريبي، والفاتورة الضريبية تُعتمد | | `5a4db0c5-0000-4000-8000-000000000004` | Sandbox No Street Co | الفاتورة الضريبية تُرفض: `422 customer_address_incomplete` | | `5a4db0c5-0000-4000-8000-000000000005` | Sandbox Gulf Trading | منشأة في الإمارات، والفاتورة الضريبية تُعتمد | | `5a4db0c5-0000-4000-8000-000000000006` | Sandbox Inactive Person | أي فاتورة تُرفض: `422 customer_inactive` | | `5a4db0c5-0000-4000-8000-000000000007` | Sandbox Credit Limit Co | حد الائتمان 100، والفاتورة الآجلة فوقه ترجع `422 credit_limit_exceeded` | | `5a4db0c5-0000-4000-8000-000000000008` | Sandbox ZATCA Pending Co | الفاتورة الضريبية ترجع `201` و`zatca.status` قيمته `pending`، ثم تُعتمد بعدها بقليل | | `5a4db0c5-0000-4000-8000-000000000009` | Sandbox ZATCA Rejected Co | الفاتورة الضريبية تُرفض: `422 zatca_rejected`، مع مثال `BR-KSA-63` في `zatcaErrors` | | `5a4db0c5-0000-4000-8000-000000000101` | Test subscription (صنف) | أي سطر فيه هذا الصنف يُرفض: `422 item_not_sellable` | العميلان 8 و9 لا يصلان إلى الهيئة وقت إصدار الفاتورة: نتيجتهما تأتي من كسور نفسها، لذلك تتكرر بنفس الشكل في كل مرة. المصدر: https://docs.kosoor.com.sa/ar/test-mode --- ## أكواد الأخطاء > كل أكواد الأخطاء التي يرجعها Merchant API، مع الـ HTTP Status ومعنى كل كود، وشكل الخطأ الموحّد. جميع الأخطاء ترجع بالشكل نفسه. اعتمد على `code` في الكود عندك، أما `message` فهي نص موجّه للمستخدم وتتغيّر لغتها حسب الـ Header `Accept-Language`. ```json { "error": { "type": "invalid_request_error", "code": "phone_taken", "message": "رقم الجوال مسجّل لعميل آخر.", "param": "phone", "docUrl": "https://docs.kosoor.com.sa/ar/errors#phone_taken", "existingId": "0f8fad5b-d9cb-469f-a165-70867728950e" } } ``` | الحقل | الوصف | |---|---| | `type` | تصنيف الخطأ، وهو أحد العناوين في هذه الصفحة. | | `code` | كود الخطأ، ولا يتغيّر بين الإصدارات. | | `message` | وصف الخطأ بلغة الطلب. | | `param` | الحقل أو الـ Query Parameter أو الـ Header الذي سبّب الخطأ، بمسار مثل `company.vatNumber`. قيمته `null` إذا لم يكن الخطأ متعلقاً بحقل معيّن. | | `docUrl` | رابط شرح الكود في هذه الصفحة. | | `errors` | في أخطاء 400: قائمة بكل مشاكل الطلب، بما فيها المشكلة الأولى. | | `requiredPermission` | مع `missing_permission`: الصلاحية التي يحتاجها الطلب، مثل `customers:write`. | | `existingId` | مع الأكواد التي تنتهي بـ `_taken`: الـ `id` للسجل الذي يستخدم القيمة نفسها، إذا كان من نوع المورد نفسه ويملك مفتاحك صلاحية قراءته. | | `missing` | مع `customer_address_incomplete`: حقول العميل الناقصة لإصدار فاتورة ضريبية، مثل `address.street`. | | `zatcaErrors` | مع `zatca_rejected`: رسائل الهيئة كما أرسلتها، ولكل رسالة `code` و `category` و `message`. | ### المصادقة #### `missing_api_key` HTTP Status: **401** لم يتم إرسال مفتاح API. أرسله في الـ Header بالشكل: Authorization: Bearer . #### `invalid_api_key` HTTP Status: **401** مفتاح API غير صحيح. #### `revoked_api_key` HTTP Status: **401** تم إلغاء مفتاح API هذا. #### `expired_api_key` HTTP Status: **401** انتهت صلاحية مفتاح API هذا. ### الصلاحيات #### `tenant_suspended` HTTP Status: **403** حساب المنشأة موقوف. #### `missing_permission` HTTP Status: **403** مفتاح API لا يملك الصلاحية المطلوبة لهذا الطلب. #### `branch_not_allowed` HTTP Status: **403** مفتاح API غير مسموح له بالعمل على هذا الفرع. ### أخطاء في الطلب #### `invalid_json` HTTP Status: **400** الـ Body ليس JSON صحيحاً. #### `missing_parameter` HTTP Status: **400** أحد الحقول المطلوبة غير موجود. #### `invalid_parameter` HTTP Status: **400** قيمة أحد الحقول غير صحيحة. #### `unknown_parameter` HTTP Status: **400** الطلب يحتوي على حقل لا يقبله هذا الـ Endpoint. #### `too_many_errors` HTTP Status: **400** في الطلب أخطاء أخرى غير المذكورة هنا. #### `unsupported_media_type` HTTP Status: **415** أرسل الـ Body بصيغة application/json. #### `body_too_large` HTTP Status: **413** حجم الـ Body أكبر من الحد المسموح. #### `route_not_found` HTTP Status: **404** لا يوجد Endpoint بهذا الرابط. #### `method_not_allowed` HTTP Status: **405** هذا الـ Endpoint لا يقبل الـ HTTP Method المستخدم. #### `resource_not_found` HTTP Status: **404** السجل المطلوب في الرابط غير موجود. #### `reference_id_taken` HTTP Status: **409** قيمة referenceId مستخدمة لسجل آخر. #### `invoice_not_cleared` HTTP Status: **409** لم تقبل هيئة الزكاة والضريبة والجمارك هذه الفاتورة. راجع قيمة zatca.status. #### `invoice_busy` HTTP Status: **409** هناك طلب آخر يعدّل هذه الفاتورة الآن. أعد المحاولة بعد ثوانٍ. #### `invalid_vat_number` HTTP Status: **400** الرقم الضريبي يتكون من 15 رقماً، يبدأ بـ 3 وينتهي بـ 3. #### `vat_number_taken` HTTP Status: **409** الرقم الضريبي مسجّل لعميل آخر. #### `phone_taken` HTTP Status: **409** رقم الجوال مسجّل لعميل آخر. ### قواعد العمل #### `referenced_resource_not_found` HTTP Status: **422** الطلب يشير إلى سجل غير موجود. #### `invoice_not_issued` HTTP Status: **422** لم يتم إصدار الفاتورة. #### `on_account_not_allowed` HTTP Status: **422** العميل الفرد لا يشتري بالآجل. #### `credit_limit_exceeded` HTTP Status: **422** تجاوز الحد الائتماني. #### `customer_inactive` HTTP Status: **422** العميل غير نشط. #### `customer_address_incomplete` HTTP Status: **422** الفاتورة الضريبية تحتاج عنوان العميل (الشركة) كاملاً مع الرقم الضريبي أو رقم الهوية. الحقول الناقصة موضحة في missing. #### `item_not_sellable` HTTP Status: **422** لا يمكن بيع هذا المنتج في فاتورة مبيعات. #### `branch_not_onboarded` HTTP Status: **422** هذا الفرع غير مربوط بهيئة الزكاة والضريبة والجمارك، فلا يمكنه إصدار فاتورة ضريبية. #### `zatca_rejected` HTTP Status: **422** رفضت هيئة الزكاة والضريبة والجمارك الفاتورة، ولم يُحفظ أي شيء. رسائل الهيئة موضحة في zatcaErrors. #### `allocation_exceeds_due` HTTP Status: **422** المبلغ المخصص أكبر من المتبقي على هذه الفاتورة. #### `invoice_not_payable` HTTP Status: **422** لا يمكن سداد هذه الفاتورة، لأنها غير موجودة، أو لعميل آخر، أو في فرع لا يملك المفتاح صلاحية عليه، أو غير صادرة، أو ليست فاتورة مبيعات. #### `period_locked` HTTP Status: **422** هذا التاريخ في فترة محاسبية مقفلة. #### `payment_method_not_in_branch` HTTP Status: **422** خزينة طريقة الدفع هذه تخدم فرعاً آخر، فلا يمكن تسجيل الدفعة في الفرع المحدد. #### `payment_not_recorded` HTTP Status: **422** لم يتم تسجيل الدفعة. #### `webhook_endpoint_limit` HTTP Status: **422** وصل هذا المفتاح إلى الحد الأقصى لعدد الـ Webhook Endpoints، احذف واحداً لا تستخدمه أولاً. #### `credit_exceeds_invoice` HTTP Status: **422** مبلغ الإشعار أكبر من المتبقي القابل للإشعار على الفاتورة. #### `invoice_not_creditable` HTTP Status: **422** لا يمكن إصدار إشعار دائن لهذه الفاتورة، لأنها ليست فاتورة مبيعات صادرة، أو مستوردة، أو رفضتها الهيئة. #### `used_subscription_not_refundable` HTTP Status: **422** هذا الاشتراك استُخدمت منه زيارات، فلا يمكن إصدار إشعار دائن له عن طريق الـ API. #### `refund_exceeds_collected` HTTP Status: **422** مبلغ الاسترجاع أكبر مما حُصّل على الفاتورة. #### `refund_method_not_supported` HTTP Status: **422** دُفعت هذه الفاتورة ببطاقة عبر المتجر الإلكتروني، ولا يمكن بدء استرجاع إلى البطاقة عن طريق الـ API. #### `loyalty_refund_blocked` HTTP Status: **422** تعذّر إصدار هذا الإشعار بسبب منصة الولاء: إما أن النقاط أو الأختام المكتسبة من هذه الفاتورة استُخدمت، أو تعذّر الوصول إلى المنصة، أو لم تصلها الفاتورة بعد. #### `credit_note_not_issued` HTTP Status: **422** لم يتم إصدار الإشعار الدائن. ### منع تكرار الطلبات #### `missing_idempotency_key` HTTP Status: **400** أرسل الـ Header Idempotency-Key مع هذا الطلب. #### `invalid_idempotency_key` HTTP Status: **400** قيمة الـ Header Idempotency-Key غير صحيحة. #### `idempotency_key_reused` HTTP Status: **422** قيمة Idempotency-Key هذه استُخدمت من قبل مع طلب مختلف. #### `idempotency_in_progress` HTTP Status: **409** يوجد طلب بنفس قيمة Idempotency-Key لم ينتهِ تنفيذه بعد. ### تجاوز حد الطلبات #### `rate_limited` HTTP Status: **429** تجاوزت الحد المسموح من الطلبات. أعد المحاولة بعد المدة الموضحة في Retry-After. ### أخطاء السيرفر #### `internal_error` HTTP Status: **500** حدث خطأ من جهتنا. أعد المحاولة، وإذا تكرر الخطأ أرسل لنا قيمة Request-Id. #### `sandbox_unavailable` HTTP Status: **503** بيئة الاختبار قيد التجهيز. أعد المحاولة بعد قليل. #### `sandbox_failed` HTTP Status: **503** تعذّر تجهيز بيئة الاختبار، ولن تنجح إعادة المحاولة حتى يُعيد التاجر تعيينها من صفحة المطوّرين في لوحة التحكم. المصدر: https://docs.kosoor.com.sa/ar/errors --- ## سياسة الإصدارات > ما الذي يمكن أن يتغير في v1 دون إشعار، وما الذي لا يتغير أبداً، وكيف يأتي التغيير الذي يكسر التوافق. إصدار الـ API هو الجزء الأول من كل مسار: `/v1`. ### ما يمكن أن يتغير في v1 هذه التغييرات قد تُضاف إلى v1 في أي وقت، وكل منها يُذكر في [سجل التغييرات](https://docs.kosoor.com.sa/ar/changelog.md): - Endpoints وموارد جديدة، بصلاحياتها الخاصة؛ - حقول و Query Parameters اختيارية جديدة في الطلبات؛ - حقول جديدة في الردود؛ - أكواد أخطاء جديدة؛ - قيم جديدة في الحقول التي تحدد نوعاً أو قناة، مثل `createdVia`. لذلك يجب أن يتجاهل الكود عندك أي حقل لا يعرفه في الرد، وأن يتعامل مع أي قيمة لا يعرفها، مثل أن يعامل قيمة غير معروفة في `createdVia` كأنها `unknown`. ### ما لا يتغير في v1 - الحقل الموجود يحتفظ باسمه ونوعه ومعناه. - كود الخطأ يحتفظ بمعناه. اعتمد على `code`، أما `message` فنص موجّه للمستخدم وقد تتغير صياغته أو ترجمته في أي وقت. - الطلب الذي يعمل اليوم يبقى يعمل. ويبقى التحقق من الطلبات صارماً: أي حقل أو Query Parameter لا يعرفه الـ Endpoint يُرفض بـ `400 unknown_parameter`. والحقل الاختياري الجديد لا يُقبل إلا بعد إطلاقه وذكره في سجل التغييرات. ### التغيير الذي يكسر التوافق التغيير الذي يوقف عميلاً يعمل، مثل حذف حقل أو تغيير اسمه، يأتي في إصدار جديد بمسار خاص به، مثل `/v2`. المصدر: https://docs.kosoor.com.sa/ar/versioning --- ## سجل التغييرات > كل تغيير في Kosoor Merchant API، الأحدث أولاً. كل تغيير في الـ API يُذكر هنا، الأحدث أولاً. وما يمكن أن يتغير داخل v1 موضح في [سياسة الإصدارات](https://docs.kosoor.com.sa/ar/versioning.md). ### 7 نوفمبر 2026: الإصدار v1 أول إصدار من Merchant API. - **العملاء**: إنشاء الأفراد والشركات وقراءتهم وعرض قوائمهم وتعديلهم، والبحث عنهم برقم الجوال أو الرقم الضريبي أو رقم الهوية أو `referenceId` الخاص بك. الصلاحيتان `customers:read` و `customers:write`. التفاصيل في [العملاء](https://docs.kosoor.com.sa/ar/customers.md). - **فواتير المبيعات**: إصدار الفاتورة في طلب واحد، مع الدفع عند الإصدار أو بالآجل، والفواتير الضريبية تعتمدها هيئة الزكاة والضريبة والجمارك قبل الرد. وقراءة الفواتير من كل القنوات وعرض قوائمها، وتحميل ملفي PDF و XML الخاصين بالهيئة. الصلاحيتان `sales_invoices:read` و `sales_invoices:write`. التفاصيل في [فواتير المبيعات](https://docs.kosoor.com.sa/ar/sales-invoices.md). - **المدفوعات**: تسجيل الدفعة وتخصيصها على الفواتير، وقراءة المدفوعات من كل القنوات وعرض قوائمها. الصلاحيتان `payments:read` و `payments:write`. التفاصيل في [المدفوعات](https://docs.kosoor.com.sa/ar/payments.md). - **الإشعارات الدائنة**: إصدار إشعار على فاتورة صادرة مع إرجاع المبلغ أو خصمه من المستحق على العميل، وقراءة الإشعارات وعرض قوائمها بما فيها الاسترجاع من نقطة البيع ومن لوحة التحكم، وتحميل ملفي PDF و XML. الصلاحيتان `credit_notes:read` و `credit_notes:write`. التفاصيل في [الإشعارات الدائنة](https://docs.kosoor.com.sa/ar/credit-notes.md). - **الكتالوج**: قراءة الفروع والأصناف وطرق الدفع وعرض قوائمها، لتعرف المعرّفات التي تحتاجها الفاتورة. الصلاحيات `branches:read` و `items:read` و `payment_methods:read`. - **الـ Webhooks**: نتائج الهيئة التي تصل بعد الرد تُرسل إلى HTTPS Endpoint يسجّله المفتاح، موقّعة بطريقة Standard Webhooks، وتُعاد محاولة إرسالها لمدة 3 أيام تقريباً. الصلاحيتان `webhooks:read` و `webhooks:write`. التفاصيل في [Webhooks](https://docs.kosoor.com.sa/ar/webhooks.md). - **وضع الاختبار**: بيئة اختبار لكل تاجر، ومفاتيح اختبار تبدأ بـ `ksr_test_`، والـ Header `Kosoor-Mode: test`، وعملاء جاهزون بنتائج معروفة. التفاصيل في [وضع الاختبار](https://docs.kosoor.com.sa/ar/test-mode.md). المصدر: https://docs.kosoor.com.sa/ar/changelog --- # مرجع الـ API كل الـ Endpoints، مولّدة من ملف OpenAPI الذي ينشره الـ API نفسه. ### إنشاء عميل `POST https://api.kosoor.com.sa/v1/customers` يحتاج صلاحية customers:write. رقم الجوال والرقم الضريبي و referenceId يجب ألا تكون مستخدمة لعميل آخر. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Idempotency-Key` | string | نعم | من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أرسل المفتاح نفسه عند إعادة المحاولة. يُحفظ لمدة 30 يوماً، وإذا كان الطلب الأول لم ينتهِ بعد يرجع 409 مع Retry-After. Length 1–255. | | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### الـ Body `Content-Type: application/json` | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `type` | string: `person`, `company`, nullable | | يُحدَّد عند الإنشاء ولا يمكن تغييره. | | `referenceId` | string, nullable | | المعرّف الخاص بهذا العميل في نظامك، ولا يتكرر لدى التاجر نفسه. يُحدَّد مرة واحدة فقط. | | `firstName` | string, nullable | | | | `lastName` | string, nullable | | | | `phone` | string, nullable | | بصيغة E.164، مثل `+966501234567`. | | `email` | string, nullable | | | | `isActive` | boolean, nullable | | | | `branchId` | string (uuid), nullable | | | | `company` | object, nullable | | | | `company.name` | string, nullable | | | | `company.vatNumber` | string, nullable | | 15 رقماً يبدأ بـ 3 وينتهي بـ 3، ولا يتكرر لدى التاجر نفسه. | | `company.idType` | string: `CRN`, `MOM`, `MLS`, `700`, `SAG`, `NAT`, `GCC`, `IQA`, `PAS`, `OTH`, nullable | | | | `company.idValue` | string, nullable | | | | `address` | object, nullable | | | | `address.street` | string, nullable | | | | `address.buildingNumber` | string, nullable | | | | `address.postalCode` | string, nullable | | | | `address.district` | string, nullable | | | | `address.city` | string, nullable | | | | `address.country` | string, nullable | | رمز الدولة بحرفين حسب ISO 3166، وهو مطلوب للعميل من نوع شركة. | | `metadata` | map of string, nullable | | حتى 20 مفتاحاً، طول كل مفتاح حتى 40 حرفاً، وطول القيمة حتى 500 حرف. | #### الردود **201** تم الإنشاء (`application/json`) الـ Header `Idempotent-Replayed`: قيمته true إذا كان هذا الرد محفوظاً من طلب سابق بالمفتاح نفسه. | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | قيمته دائماً customer، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `type` | string: `person`, `company` | نعم | | | `referenceId` | string, nullable | نعم | | | `firstName` | string, nullable | نعم | | | `lastName` | string, nullable | نعم | | | `phone` | string, nullable | نعم | | | `email` | string, nullable | نعم | | | `isActive` | boolean | نعم | | | `branchId` | string (uuid), nullable | نعم | | | `company` | object, nullable | نعم | | | `company.name` | string, nullable | نعم | | | `company.vatNumber` | string, nullable | نعم | | | `company.idType` | string, nullable | نعم | | | `company.idValue` | string, nullable | نعم | | | `company.creditLimit` | number (double) | نعم | | | `company.creditPeriodDays` | integer (int32), nullable | نعم | | | `address` | object | نعم | | | `address.street` | string, nullable | نعم | | | `address.buildingNumber` | string, nullable | نعم | | | `address.postalCode` | string, nullable | نعم | | | `address.district` | string, nullable | نعم | | | `address.city` | string, nullable | نعم | | | `address.country` | string, nullable | نعم | | | `metadata` | map of string | نعم | | | `standardInvoiceMissing` | array of string, nullable | نعم | الحقول الناقصة لإصدار فاتورة ضريبية، وقيمته null للعميل الفرد. | | `createdAt` | string (date-time) | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **422** Refused by a business rule or a reused Idempotency-Key: error.code says which. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### قائمة العملاء `GET https://api.kosoor.com.sa/v1/customers` يحتاج صلاحية customers:read. ترجع الأحدث أولاً، مقسّمة إلى صفحات عن طريق limit و startingAfter. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Query parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `phone` | string | | بصيغة E.164. | | `vatNumber` | string | | | | `idValue` | string | | قيمة idValue للعميل من نوع شركة. | | `referenceId` | string | | | | `limit` | integer (int32) | | من 1 إلى 100، والقيمة الافتراضية 20. | | `startingAfter` | string (uuid) | | الـ id لآخر سجل في الصفحة السابقة. | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `data` | array of object | نعم | | | `data[].id` | string (uuid) | نعم | | | `data[].object` | string | نعم | قيمته دائماً customer، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `data[].type` | string: `person`, `company` | نعم | | | `data[].referenceId` | string, nullable | نعم | | | `data[].firstName` | string, nullable | نعم | | | `data[].lastName` | string, nullable | نعم | | | `data[].phone` | string, nullable | نعم | | | `data[].email` | string, nullable | نعم | | | `data[].isActive` | boolean | نعم | | | `data[].branchId` | string (uuid), nullable | نعم | | | `data[].company` | object, nullable | نعم | | | `data[].company.name` | string, nullable | نعم | | | `data[].company.vatNumber` | string, nullable | نعم | | | `data[].company.idType` | string, nullable | نعم | | | `data[].company.idValue` | string, nullable | نعم | | | `data[].company.creditLimit` | number (double) | نعم | | | `data[].company.creditPeriodDays` | integer (int32), nullable | نعم | | | `data[].address` | object | نعم | | | `data[].address.street` | string, nullable | نعم | | | `data[].address.buildingNumber` | string, nullable | نعم | | | `data[].address.postalCode` | string, nullable | نعم | | | `data[].address.district` | string, nullable | نعم | | | `data[].address.city` | string, nullable | نعم | | | `data[].address.country` | string, nullable | نعم | | | `data[].metadata` | map of string | نعم | | | `data[].standardInvoiceMissing` | array of string, nullable | نعم | الحقول الناقصة لإصدار فاتورة ضريبية، وقيمته null للعميل الفرد. | | `data[].createdAt` | string (date-time) | نعم | | | `hasMore` | boolean | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### بيانات عميل `GET https://api.kosoor.com.sa/v1/customers/{id}` يحتاج صلاحية customers:read. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | قيمته دائماً customer، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `type` | string: `person`, `company` | نعم | | | `referenceId` | string, nullable | نعم | | | `firstName` | string, nullable | نعم | | | `lastName` | string, nullable | نعم | | | `phone` | string, nullable | نعم | | | `email` | string, nullable | نعم | | | `isActive` | boolean | نعم | | | `branchId` | string (uuid), nullable | نعم | | | `company` | object, nullable | نعم | | | `company.name` | string, nullable | نعم | | | `company.vatNumber` | string, nullable | نعم | | | `company.idType` | string, nullable | نعم | | | `company.idValue` | string, nullable | نعم | | | `company.creditLimit` | number (double) | نعم | | | `company.creditPeriodDays` | integer (int32), nullable | نعم | | | `address` | object | نعم | | | `address.street` | string, nullable | نعم | | | `address.buildingNumber` | string, nullable | نعم | | | `address.postalCode` | string, nullable | نعم | | | `address.district` | string, nullable | نعم | | | `address.city` | string, nullable | نعم | | | `address.country` | string, nullable | نعم | | | `metadata` | map of string | نعم | | | `standardInvoiceMissing` | array of string, nullable | نعم | الحقول الناقصة لإصدار فاتورة ضريبية، وقيمته null للعميل الفرد. | | `createdAt` | string (date-time) | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### تعديل عميل `PATCH https://api.kosoor.com.sa/v1/customers/{id}` يحتاج صلاحية customers:write. يعدّل فقط الحقول المرسلة في الـ Body. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Idempotency-Key` | string | نعم | من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أرسل المفتاح نفسه عند إعادة المحاولة. يُحفظ لمدة 30 يوماً، وإذا كان الطلب الأول لم ينتهِ بعد يرجع 409 مع Retry-After. Length 1–255. | | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الـ Body `Content-Type: application/json` | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `type` | string: `person`, `company`, nullable | | يُحدَّد عند الإنشاء ولا يمكن تغييره. | | `referenceId` | string, nullable | | المعرّف الخاص بهذا العميل في نظامك، ولا يتكرر لدى التاجر نفسه. يُحدَّد مرة واحدة فقط. | | `firstName` | string, nullable | | | | `lastName` | string, nullable | | | | `phone` | string, nullable | | بصيغة E.164، مثل `+966501234567`. | | `email` | string, nullable | | | | `isActive` | boolean, nullable | | | | `branchId` | string (uuid), nullable | | | | `company` | object, nullable | | | | `company.name` | string, nullable | | | | `company.vatNumber` | string, nullable | | 15 رقماً يبدأ بـ 3 وينتهي بـ 3، ولا يتكرر لدى التاجر نفسه. | | `company.idType` | string: `CRN`, `MOM`, `MLS`, `700`, `SAG`, `NAT`, `GCC`, `IQA`, `PAS`, `OTH`, nullable | | | | `company.idValue` | string, nullable | | | | `address` | object, nullable | | | | `address.street` | string, nullable | | | | `address.buildingNumber` | string, nullable | | | | `address.postalCode` | string, nullable | | | | `address.district` | string, nullable | | | | `address.city` | string, nullable | | | | `address.country` | string, nullable | | رمز الدولة بحرفين حسب ISO 3166، وهو مطلوب للعميل من نوع شركة. | | `metadata` | map of string, nullable | | حتى 20 مفتاحاً، طول كل مفتاح حتى 40 حرفاً، وطول القيمة حتى 500 حرف. | #### الردود **200** تم بنجاح (`application/json`) الـ Header `Idempotent-Replayed`: قيمته true إذا كان هذا الرد محفوظاً من طلب سابق بالمفتاح نفسه. | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | قيمته دائماً customer، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `type` | string: `person`, `company` | نعم | | | `referenceId` | string, nullable | نعم | | | `firstName` | string, nullable | نعم | | | `lastName` | string, nullable | نعم | | | `phone` | string, nullable | نعم | | | `email` | string, nullable | نعم | | | `isActive` | boolean | نعم | | | `branchId` | string (uuid), nullable | نعم | | | `company` | object, nullable | نعم | | | `company.name` | string, nullable | نعم | | | `company.vatNumber` | string, nullable | نعم | | | `company.idType` | string, nullable | نعم | | | `company.idValue` | string, nullable | نعم | | | `company.creditLimit` | number (double) | نعم | | | `company.creditPeriodDays` | integer (int32), nullable | نعم | | | `address` | object | نعم | | | `address.street` | string, nullable | نعم | | | `address.buildingNumber` | string, nullable | نعم | | | `address.postalCode` | string, nullable | نعم | | | `address.district` | string, nullable | نعم | | | `address.city` | string, nullable | نعم | | | `address.country` | string, nullable | نعم | | | `metadata` | map of string | نعم | | | `standardInvoiceMissing` | array of string, nullable | نعم | الحقول الناقصة لإصدار فاتورة ضريبية، وقيمته null للعميل الفرد. | | `createdAt` | string (date-time) | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **422** Refused by a business rule or a reused Idempotency-Key: error.code says which. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### إصدار فاتورة مبيعات `POST https://api.kosoor.com.sa/v1/sales-invoices` يحتاج صلاحية sales_invoices:write. ينشئ الفاتورة ويصدرها في طلب واحد. إذا كان العميل شركة فالفاتورة ضريبية (standard) تعتمدها هيئة الزكاة والضريبة والجمارك قبل الرد، فإذا رفضتها لا يُحفظ أي شيء، وإذا تأخر ردها ترجع الفاتورة بقيمة pending في zatca.status. وإذا كان الخصم ينزل بسعر الوحدة عن الحد الأدنى لسعر المنتج يُخفَّض إلى هذا الحد، لذا اعتمد على قيمتي unitDiscount و total في الرد. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Idempotency-Key` | string | نعم | من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أرسل المفتاح نفسه عند إعادة المحاولة. يُحفظ لمدة 30 يوماً، وإذا كان الطلب الأول لم ينتهِ بعد يرجع 409 مع Retry-After. Length 1–255. | | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### الـ Body `Content-Type: application/json` | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `customerId` | string (uuid) | نعم | مطلوب، فلا يدعم الـ API البيع لعميل نقدي غير مسجّل. العميل من نوع شركة تصدر له فاتورة ضريبية (standard) تعتمدها الهيئة قبل الرد، والعميل الفرد تصدر له فاتورة مبسطة (simplified). | | `branchId` | string (uuid) | نعم | مطلوب. | | `pricesIncludeVat` | boolean | نعم | مطلوب. true إذا كانت كل قيم unitPrice و unitDiscount شاملة الضريبة، وتُحوَّل هذه الأسعار إلى ما قبل الضريبة مقرّبة إلى الهللة. | | `lines` | array of object | نعم | مطلوب. من 1 إلى 500 بند، ولا يتكرر المنتج في أكثر من بند. | | `lines[].itemId` | string (uuid) | نعم | مطلوب. | | `lines[].quantity` | integer | نعم | مطلوب. عدد صحيح، 1 أو أكثر. | | `lines[].unitPrice` | number (double), nullable | | حسب ما تحدده pricesIncludeVat، ومقرّب إلى الهللة. إذا لم يُرسل يُستخدم سعر المنتج في الكتالوج، والمنتج الذي يختلف سعره حسب حجم السيارة ليس له سعر في الكتالوج فيجب إرساله. | | `lines[].unitDiscount` | number (double), nullable | | مبلغ الخصم على كل وحدة، بنفس أساس unitPrice. إذا كان الخصم ينزل بسعر الوحدة عن الحد الأدنى لسعر المنتج يُخفَّض تلقائياً إلى هذا الحد. | | `lines[].description` | string, nullable | | يُطبع تحت اسم المنتج. | | `payment` | object, nullable | | المبلغ المدفوع عند الإصدار. وإذا لم يُرسل تُسجَّل الفاتورة كلها على حساب العميل، وهذا لا يكون إلا لعميل من نوع شركة. | | `payment.paymentMethodId` | string (uuid) | نعم | مطلوب. | | `payment.amount` | number (double), nullable | | منزلتان عشريتان على الأكثر. إذا لم يُرسل تُدفع الفاتورة كاملة. وإذا كان أقل من الإجمالي يبقى الباقي على حساب العميل إذا كان شركة، أما العميل الفرد فيُرفض الطلب. | | `payment.date` | string (date), nullable | | إذا لم يُرسل يُستخدم تاريخ الفاتورة، ولا يكون قبله. | | `supplyDate` | string (date), nullable | | تاريخ توريد المنتجات أو الخدمات إذا كان مختلفاً عن تاريخ الإصدار. | | `dueDate` | string (date), nullable | | | | `notes` | string, nullable | | تُطبع على الفاتورة. | | `costCentreId` | string (uuid), nullable | | | | `warehouseId` | string (uuid), nullable | | المستودع الذي تخرج منه البضاعة. إذا لم يُرسل يُستخدم مستودع المبيعات الرئيسي. | | `referenceId` | string, nullable | | المعرّف الخاص بهذه الفاتورة في نظامك، حتى 100 حرف، ولا يتكرر في أي فاتورة أو إشعار آخر لدى التاجر. | | `metadata` | map of string, nullable | | حتى 20 مفتاحاً، طول كل مفتاح حتى 40 حرفاً، وطول القيمة حتى 500 حرف. | #### الردود **201** تم الإنشاء (`application/json`) الـ Header `Idempotent-Replayed`: قيمته true إذا كان هذا الرد محفوظاً من طلب سابق بالمفتاح نفسه. | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | قيمته دائماً salesInvoice، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `number` | string, nullable | نعم | رقم الفاتورة كما يُطبع، مثل «INV-1042». | | `issueDate` | string (date) | نعم | | | `supplyDate` | string (date), nullable | نعم | | | `dueDate` | string (date), nullable | نعم | | | `branchId` | string (uuid) | نعم | | | `customerId` | string (uuid), nullable | نعم | | | `customer` | object, nullable | | يرجع مع expand=customer. | | `customer.id` | string (uuid) | نعم | | | `customer.object` | string | نعم | قيمته دائماً customer، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `customer.type` | string: `person`, `company` | نعم | | | `customer.referenceId` | string, nullable | نعم | | | `customer.firstName` | string, nullable | نعم | | | `customer.lastName` | string, nullable | نعم | | | `customer.phone` | string, nullable | نعم | | | `customer.email` | string, nullable | نعم | | | `customer.isActive` | boolean | نعم | | | `customer.branchId` | string (uuid), nullable | نعم | | | `customer.company` | object, nullable | نعم | | | `customer.company.name` | string, nullable | نعم | | | `customer.company.vatNumber` | string, nullable | نعم | | | `customer.company.idType` | string, nullable | نعم | | | `customer.company.idValue` | string, nullable | نعم | | | `customer.company.creditLimit` | number (double) | نعم | | | `customer.company.creditPeriodDays` | integer (int32), nullable | نعم | | | `customer.address` | object | نعم | | | `customer.address.street` | string, nullable | نعم | | | `customer.address.buildingNumber` | string, nullable | نعم | | | `customer.address.postalCode` | string, nullable | نعم | | | `customer.address.district` | string, nullable | نعم | | | `customer.address.city` | string, nullable | نعم | | | `customer.address.country` | string, nullable | نعم | | | `customer.metadata` | map of string | نعم | | | `customer.standardInvoiceMissing` | array of string, nullable | نعم | الحقول الناقصة لإصدار فاتورة ضريبية، وقيمته null للعميل الفرد. | | `customer.createdAt` | string (date-time) | نعم | | | `currency` | string | نعم | | | `subtotal` | number (double) | نعم | مجموع البنود قبل الخصم والضريبة. | | `discountTotal` | number (double) | نعم | | | `vatTotal` | number (double) | نعم | | | `total` | number (double) | نعم | | | `amountPaid` | number (double) | نعم | | | `amountDue` | number (double) | نعم | المبلغ المتبقي على العميل في هذه الفاتورة. | | `lines` | array of object, nullable | | ترجع دائماً مع الفاتورة الواحدة، ومع القائمة فقط عند إرسال expand=lines. | | `lines[].itemId` | string (uuid) | نعم | | | `lines[].name` | string, nullable | نعم | اسم المنتج وقت البيع. | | `lines[].description` | string, nullable | نعم | | | `lines[].quantity` | number (double) | نعم | | | `lines[].unitPrice` | number (double) | نعم | قبل الضريبة. | | `lines[].unitDiscount` | number (double) | نعم | الخصم المطبّق على كل وحدة قبل الضريبة، بعد مراعاة الحد الأدنى لسعر المنتج. | | `lines[].vatRate` | number (double) | نعم | نسبة مئوية، مثل 15. | | `lines[].vatAmount` | number (double) | نعم | | | `lines[].lineTotal` | number (double) | نعم | إجمالي البند بعد الخصم وشامل الضريبة. | | `notes` | string, nullable | نعم | | | `costCentreId` | string (uuid), nullable | نعم | | | `referenceId` | string, nullable | نعم | | | `metadata` | map of string | نعم | | | `createdVia` | string: `api`, `dashboard`, `pos`, `kiosk`, `storefront`, `unknown` | نعم | إحدى القيم api أو dashboard أو pos أو kiosk أو storefront، أو unknown للفواتير الأقدم من تسجيل القناة. | | `zatca` | object | نعم | وضع الفاتورة لدى هيئة الزكاة والضريبة والجمارك، وتكون حقول المستند null حتى يتم توقيعه. | | `zatca.kind` | string: `standard`, `simplified` | نعم | standard (تعتمدها الهيئة قبل وصولها للمشتري) أو simplified (تُبلّغ بها الهيئة بعد الإصدار). | | `zatca.status` | string: `pending`, `cleared`, `reported`, `rejected` | نعم | إحدى القيم pending أو cleared أو reported أو rejected. | | `zatca.uuid` | string (uuid), nullable | نعم | | | `zatca.counter` | integer (int32), nullable | نعم | عدّاد الفواتير لدى الهيئة (ICV). | | `zatca.hash` | string, nullable | نعم | | | `zatca.qr` | string, nullable | نعم | بصيغة Base64. للفاتورة الضريبية هو رمز QR الذي أرجعته الهيئة عند الاعتماد. | | `zatca.submittedAt` | string (date-time), nullable | نعم | | | `zatca.clearedAt` | string (date-time), nullable | نعم | | | `zatca.reportedAt` | string (date-time), nullable | نعم | | | `zatca.errors` | array of object | نعم | رسائل الهيئة إذا رفضت الفاتورة. | | `zatca.errors[].code` | string | | | | `zatca.errors[].category` | string | | | | `zatca.errors[].message` | string | | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **422** Refused by a business rule or a reused Idempotency-Key: error.code says which. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### قائمة فواتير المبيعات `GET https://api.kosoor.com.sa/v1/sales-invoices` يحتاج صلاحية sales_invoices:read. ترجع كل فواتير المبيعات الصادرة في فروع المفتاح، أياً كانت القناة التي أنشأتها. الأحدث أولاً، مقسّمة إلى صفحات عن طريق limit و startingAfter. ويحتاج expand=customer صلاحية customers:read أيضاً. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Query parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `referenceId` | string | | | | `customerId` | string (uuid) | | | | `branchId` | string (uuid) | | | | `issuedAfter` | string (date-time) | | الفواتير الصادرة في هذا الوقت أو بعده، بصيغة ISO 8601. | | `issuedBefore` | string (date-time) | | الفواتير الصادرة قبل هذا الوقت، بصيغة ISO 8601. | | `zatcaStatus` | string: `pending`, `cleared`, `reported`, `rejected` | | إحدى القيم pending أو cleared أو reported أو rejected. | | `expand` | array of string | | lines و customer. كرّر الـ Parameter إذا أردت الاثنين. | | `limit` | integer (int32) | | من 1 إلى 100، والقيمة الافتراضية 20. | | `startingAfter` | string (uuid) | | الـ id لآخر سجل في الصفحة السابقة. | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `data` | array of object | نعم | | | `data[].id` | string (uuid) | نعم | | | `data[].object` | string | نعم | قيمته دائماً salesInvoice، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `data[].number` | string, nullable | نعم | رقم الفاتورة كما يُطبع، مثل «INV-1042». | | `data[].issueDate` | string (date) | نعم | | | `data[].supplyDate` | string (date), nullable | نعم | | | `data[].dueDate` | string (date), nullable | نعم | | | `data[].branchId` | string (uuid) | نعم | | | `data[].customerId` | string (uuid), nullable | نعم | | | `data[].customer` | object, nullable | | يرجع مع expand=customer. | | `data[].customer.id` | string (uuid) | نعم | | | `data[].customer.object` | string | نعم | قيمته دائماً customer، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `data[].customer.type` | string: `person`, `company` | نعم | | | `data[].customer.referenceId` | string, nullable | نعم | | | `data[].customer.firstName` | string, nullable | نعم | | | `data[].customer.lastName` | string, nullable | نعم | | | `data[].customer.phone` | string, nullable | نعم | | | `data[].customer.email` | string, nullable | نعم | | | `data[].customer.isActive` | boolean | نعم | | | `data[].customer.branchId` | string (uuid), nullable | نعم | | | `data[].customer.company` | object, nullable | نعم | | | `data[].customer.company.name` | string, nullable | نعم | | | `data[].customer.company.vatNumber` | string, nullable | نعم | | | `data[].customer.company.idType` | string, nullable | نعم | | | `data[].customer.company.idValue` | string, nullable | نعم | | | `data[].customer.company.creditLimit` | number (double) | نعم | | | `data[].customer.company.creditPeriodDays` | integer (int32), nullable | نعم | | | `data[].customer.address` | object | نعم | | | `data[].customer.address.street` | string, nullable | نعم | | | `data[].customer.address.buildingNumber` | string, nullable | نعم | | | `data[].customer.address.postalCode` | string, nullable | نعم | | | `data[].customer.address.district` | string, nullable | نعم | | | `data[].customer.address.city` | string, nullable | نعم | | | `data[].customer.address.country` | string, nullable | نعم | | | `data[].customer.metadata` | map of string | نعم | | | `data[].customer.standardInvoiceMissing` | array of string, nullable | نعم | الحقول الناقصة لإصدار فاتورة ضريبية، وقيمته null للعميل الفرد. | | `data[].customer.createdAt` | string (date-time) | نعم | | | `data[].currency` | string | نعم | | | `data[].subtotal` | number (double) | نعم | مجموع البنود قبل الخصم والضريبة. | | `data[].discountTotal` | number (double) | نعم | | | `data[].vatTotal` | number (double) | نعم | | | `data[].total` | number (double) | نعم | | | `data[].amountPaid` | number (double) | نعم | | | `data[].amountDue` | number (double) | نعم | المبلغ المتبقي على العميل في هذه الفاتورة. | | `data[].lines` | array of object, nullable | | ترجع دائماً مع الفاتورة الواحدة، ومع القائمة فقط عند إرسال expand=lines. | | `data[].lines[].itemId` | string (uuid) | نعم | | | `data[].lines[].name` | string, nullable | نعم | اسم المنتج وقت البيع. | | `data[].lines[].description` | string, nullable | نعم | | | `data[].lines[].quantity` | number (double) | نعم | | | `data[].lines[].unitPrice` | number (double) | نعم | قبل الضريبة. | | `data[].lines[].unitDiscount` | number (double) | نعم | الخصم المطبّق على كل وحدة قبل الضريبة، بعد مراعاة الحد الأدنى لسعر المنتج. | | `data[].lines[].vatRate` | number (double) | نعم | نسبة مئوية، مثل 15. | | `data[].lines[].vatAmount` | number (double) | نعم | | | `data[].lines[].lineTotal` | number (double) | نعم | إجمالي البند بعد الخصم وشامل الضريبة. | | `data[].notes` | string, nullable | نعم | | | `data[].costCentreId` | string (uuid), nullable | نعم | | | `data[].referenceId` | string, nullable | نعم | | | `data[].metadata` | map of string | نعم | | | `data[].createdVia` | string: `api`, `dashboard`, `pos`, `kiosk`, `storefront`, `unknown` | نعم | إحدى القيم api أو dashboard أو pos أو kiosk أو storefront، أو unknown للفواتير الأقدم من تسجيل القناة. | | `data[].zatca` | object | نعم | وضع الفاتورة لدى هيئة الزكاة والضريبة والجمارك، وتكون حقول المستند null حتى يتم توقيعه. | | `data[].zatca.kind` | string: `standard`, `simplified` | نعم | standard (تعتمدها الهيئة قبل وصولها للمشتري) أو simplified (تُبلّغ بها الهيئة بعد الإصدار). | | `data[].zatca.status` | string: `pending`, `cleared`, `reported`, `rejected` | نعم | إحدى القيم pending أو cleared أو reported أو rejected. | | `data[].zatca.uuid` | string (uuid), nullable | نعم | | | `data[].zatca.counter` | integer (int32), nullable | نعم | عدّاد الفواتير لدى الهيئة (ICV). | | `data[].zatca.hash` | string, nullable | نعم | | | `data[].zatca.qr` | string, nullable | نعم | بصيغة Base64. للفاتورة الضريبية هو رمز QR الذي أرجعته الهيئة عند الاعتماد. | | `data[].zatca.submittedAt` | string (date-time), nullable | نعم | | | `data[].zatca.clearedAt` | string (date-time), nullable | نعم | | | `data[].zatca.reportedAt` | string (date-time), nullable | نعم | | | `data[].zatca.errors` | array of object | نعم | رسائل الهيئة إذا رفضت الفاتورة. | | `data[].zatca.errors[].code` | string | | | | `data[].zatca.errors[].category` | string | | | | `data[].zatca.errors[].message` | string | | | | `hasMore` | boolean | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### بيانات فاتورة مبيعات `GET https://api.kosoor.com.sa/v1/sales-invoices/{id}` يحتاج صلاحية sales_invoices:read. ترجع البنود دائماً، وتضيف expand=customer بيانات العميل، ويحتاج صلاحية customers:read أيضاً. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### Query parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `expand` | array of string | | customer. البنود ترجع دائماً. | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | قيمته دائماً salesInvoice، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `number` | string, nullable | نعم | رقم الفاتورة كما يُطبع، مثل «INV-1042». | | `issueDate` | string (date) | نعم | | | `supplyDate` | string (date), nullable | نعم | | | `dueDate` | string (date), nullable | نعم | | | `branchId` | string (uuid) | نعم | | | `customerId` | string (uuid), nullable | نعم | | | `customer` | object, nullable | | يرجع مع expand=customer. | | `customer.id` | string (uuid) | نعم | | | `customer.object` | string | نعم | قيمته دائماً customer، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `customer.type` | string: `person`, `company` | نعم | | | `customer.referenceId` | string, nullable | نعم | | | `customer.firstName` | string, nullable | نعم | | | `customer.lastName` | string, nullable | نعم | | | `customer.phone` | string, nullable | نعم | | | `customer.email` | string, nullable | نعم | | | `customer.isActive` | boolean | نعم | | | `customer.branchId` | string (uuid), nullable | نعم | | | `customer.company` | object, nullable | نعم | | | `customer.company.name` | string, nullable | نعم | | | `customer.company.vatNumber` | string, nullable | نعم | | | `customer.company.idType` | string, nullable | نعم | | | `customer.company.idValue` | string, nullable | نعم | | | `customer.company.creditLimit` | number (double) | نعم | | | `customer.company.creditPeriodDays` | integer (int32), nullable | نعم | | | `customer.address` | object | نعم | | | `customer.address.street` | string, nullable | نعم | | | `customer.address.buildingNumber` | string, nullable | نعم | | | `customer.address.postalCode` | string, nullable | نعم | | | `customer.address.district` | string, nullable | نعم | | | `customer.address.city` | string, nullable | نعم | | | `customer.address.country` | string, nullable | نعم | | | `customer.metadata` | map of string | نعم | | | `customer.standardInvoiceMissing` | array of string, nullable | نعم | الحقول الناقصة لإصدار فاتورة ضريبية، وقيمته null للعميل الفرد. | | `customer.createdAt` | string (date-time) | نعم | | | `currency` | string | نعم | | | `subtotal` | number (double) | نعم | مجموع البنود قبل الخصم والضريبة. | | `discountTotal` | number (double) | نعم | | | `vatTotal` | number (double) | نعم | | | `total` | number (double) | نعم | | | `amountPaid` | number (double) | نعم | | | `amountDue` | number (double) | نعم | المبلغ المتبقي على العميل في هذه الفاتورة. | | `lines` | array of object, nullable | | ترجع دائماً مع الفاتورة الواحدة، ومع القائمة فقط عند إرسال expand=lines. | | `lines[].itemId` | string (uuid) | نعم | | | `lines[].name` | string, nullable | نعم | اسم المنتج وقت البيع. | | `lines[].description` | string, nullable | نعم | | | `lines[].quantity` | number (double) | نعم | | | `lines[].unitPrice` | number (double) | نعم | قبل الضريبة. | | `lines[].unitDiscount` | number (double) | نعم | الخصم المطبّق على كل وحدة قبل الضريبة، بعد مراعاة الحد الأدنى لسعر المنتج. | | `lines[].vatRate` | number (double) | نعم | نسبة مئوية، مثل 15. | | `lines[].vatAmount` | number (double) | نعم | | | `lines[].lineTotal` | number (double) | نعم | إجمالي البند بعد الخصم وشامل الضريبة. | | `notes` | string, nullable | نعم | | | `costCentreId` | string (uuid), nullable | نعم | | | `referenceId` | string, nullable | نعم | | | `metadata` | map of string | نعم | | | `createdVia` | string: `api`, `dashboard`, `pos`, `kiosk`, `storefront`, `unknown` | نعم | إحدى القيم api أو dashboard أو pos أو kiosk أو storefront، أو unknown للفواتير الأقدم من تسجيل القناة. | | `zatca` | object | نعم | وضع الفاتورة لدى هيئة الزكاة والضريبة والجمارك، وتكون حقول المستند null حتى يتم توقيعه. | | `zatca.kind` | string: `standard`, `simplified` | نعم | standard (تعتمدها الهيئة قبل وصولها للمشتري) أو simplified (تُبلّغ بها الهيئة بعد الإصدار). | | `zatca.status` | string: `pending`, `cleared`, `reported`, `rejected` | نعم | إحدى القيم pending أو cleared أو reported أو rejected. | | `zatca.uuid` | string (uuid), nullable | نعم | | | `zatca.counter` | integer (int32), nullable | نعم | عدّاد الفواتير لدى الهيئة (ICV). | | `zatca.hash` | string, nullable | نعم | | | `zatca.qr` | string, nullable | نعم | بصيغة Base64. للفاتورة الضريبية هو رمز QR الذي أرجعته الهيئة عند الاعتماد. | | `zatca.submittedAt` | string (date-time), nullable | نعم | | | `zatca.clearedAt` | string (date-time), nullable | نعم | | | `zatca.reportedAt` | string (date-time), nullable | نعم | | | `zatca.errors` | array of object | نعم | رسائل الهيئة إذا رفضت الفاتورة. | | `zatca.errors[].code` | string | | | | `zatca.errors[].category` | string | | | | `zatca.errors[].message` | string | | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### تحميل الفاتورة بصيغة PDF `GET https://api.kosoor.com.sa/v1/sales-invoices/{id}/pdf` يحتاج صلاحية sales_invoices:read. ملف PDF للفاتورة بمقاس A4، ضريبية كانت أو مبسطة. الفاتورة الضريبية (standard) ترجع 409 invoice_not_cleared حتى تعتمدها الهيئة. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/pdf`) **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### تحميل ملف XML المرسل للهيئة `GET https://api.kosoor.com.sa/v1/sales-invoices/{id}/xml` يحتاج صلاحية sales_invoices:read. ملف XML الذي اعتمدته الهيئة (standard) أو الملف الموقّع المرسل لها (simplified). يرجع 409 invoice_not_cleared حتى تقبل الهيئة الفاتورة، ويبقى كذلك إذا رفضتها. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/xml`) **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### تسجيل دفعة `POST https://api.kosoor.com.sa/v1/payments` يحتاج صلاحية payments:write. يسجّل الدفعة ويصدرها في طلب واحد. الدفعة الصادرة لا تُلغى، وإرجاع المال للعميل يكون بإشعار دائن. والتخصيص يسدد فواتير العميل، وما يتبقى يبقى رصيداً دائناً له. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Idempotency-Key` | string | نعم | من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أرسل المفتاح نفسه عند إعادة المحاولة. يُحفظ لمدة 30 يوماً، وإذا كان الطلب الأول لم ينتهِ بعد يرجع 409 مع Retry-After. Length 1–255. | | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### الـ Body `Content-Type: application/json` | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `customerId` | string (uuid) | نعم | مطلوب، فرداً كان العميل أو شركة، فلا يوجد سند قبض لعميل نقدي غير مسجّل. | | `branchId` | string (uuid) | نعم | مطلوب. الفرع الذي استلم المبلغ، ويجب أن تخدمه خزينة طريقة الدفع. | | `paymentMethodId` | string (uuid) | نعم | مطلوب. | | `amount` | number (double) | نعم | مطلوب. أكبر من 0، ومنزلتان عشريتان على الأكثر. | | `date` | string (date), nullable | | إذا لم يُرسل يُستخدم تاريخ اليوم بتوقيت السعودية. ويُقبل التاريخ السابق ما لم تكن فترته المحاسبية مقفلة، أو كان قبل تاريخ فاتورة مخصص لها. | | `allocations` | array of object, nullable | | حتى 100 فاتورة تسددها هذه الدفعة، ولا تتكرر الفاتورة. وما يتبقى يبقى رصيداً دائناً للعميل. | | `allocations[].invoiceId` | string (uuid) | نعم | مطلوب. فاتورة مبيعات صادرة لهذا العميل، في فرع يملك المفتاح صلاحية عليه، وليست بعد تاريخ الدفعة. | | `allocations[].amount` | number (double) | نعم | مطلوب. أكبر من 0، ومنزلتان عشريتان على الأكثر، ولا يزيد على المتبقي على الفاتورة. | | `notes` | string, nullable | | حتى 500 حرف. | | `referenceId` | string, nullable | | المعرّف الخاص بهذه الدفعة في نظامك، حتى 100 حرف، ولا يتكرر في أي دفعة أخرى لدى التاجر. | | `metadata` | map of string, nullable | | حتى 20 مفتاحاً، طول كل مفتاح حتى 40 حرفاً، وطول القيمة حتى 500 حرف. | #### الردود **201** تم الإنشاء (`application/json`) الـ Header `Idempotent-Replayed`: قيمته true إذا كان هذا الرد محفوظاً من طلب سابق بالمفتاح نفسه. | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | قيمته دائماً payment، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `number` | string | نعم | رقم سند القبض كما يُطبع. | | `date` | string (date) | نعم | | | `customerId` | string (uuid) | نعم | | | `branchId` | string (uuid), nullable | نعم | قيمته null في سند القبض المسجّل بدون فرع، والمفتاح المحدود ببعض الفروع لا يراه. | | `paymentMethodId` | string (uuid), nullable | نعم | قيمته null في سند القبض المسجّل بدون طريقة دفع. | | `amount` | number (double) | نعم | | | `discount` | number (double) | نعم | جزء من الدين تنازل عنه التاجر إضافة إلى المبلغ، وهو خصم سداد يُسجَّل من لوحة التحكم، وقيمته 0 للدفعة المسجّلة عن طريق الـ API. | | `allocations` | array of object | نعم | | | `allocations[].invoiceId` | string (uuid) | نعم | | | `allocations[].amount` | number (double) | نعم | | | `unallocatedAmount` | number (double) | نعم | قيمة amount مع discount مطروحاً منها التخصيص، وهو رصيد دائن للعميل. | | `notes` | string, nullable | نعم | | | `referenceId` | string, nullable | نعم | | | `metadata` | map of string | نعم | | | `createdVia` | string: `api`, `dashboard`, `pos`, `kiosk`, `storefront`, `unknown` | نعم | إحدى القيم api أو dashboard أو pos أو kiosk أو storefront، أو unknown لسند القبض الأقدم من تسجيل القناة. | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **422** Refused by a business rule or a reused Idempotency-Key: error.code says which. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### قائمة المدفوعات `GET https://api.kosoor.com.sa/v1/payments` يحتاج صلاحية payments:read. ترجع كل دفعة صادرة في فروع المفتاح، ومنها الدفعة المسجّلة مع البيع لعميل من نوع شركة. الأحدث أولاً، مقسّمة إلى صفحات عن طريق limit و startingAfter. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Query parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `referenceId` | string | | | | `customerId` | string (uuid) | | | | `branchId` | string (uuid) | | | | `invoiceId` | string (uuid) | | الدفعات التي خُصص منها مبلغ لهذه الفاتورة. | | `dateFrom` | string (date) | | الدفعات المؤرخة في هذا اليوم أو بعده. | | `dateTo` | string (date) | | الدفعات المؤرخة في هذا اليوم أو قبله. | | `limit` | integer (int32) | | من 1 إلى 100، والقيمة الافتراضية 20. | | `startingAfter` | string (uuid) | | الـ id لآخر سجل في الصفحة السابقة. | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `data` | array of object | نعم | | | `data[].id` | string (uuid) | نعم | | | `data[].object` | string | نعم | قيمته دائماً payment، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `data[].number` | string | نعم | رقم سند القبض كما يُطبع. | | `data[].date` | string (date) | نعم | | | `data[].customerId` | string (uuid) | نعم | | | `data[].branchId` | string (uuid), nullable | نعم | قيمته null في سند القبض المسجّل بدون فرع، والمفتاح المحدود ببعض الفروع لا يراه. | | `data[].paymentMethodId` | string (uuid), nullable | نعم | قيمته null في سند القبض المسجّل بدون طريقة دفع. | | `data[].amount` | number (double) | نعم | | | `data[].discount` | number (double) | نعم | جزء من الدين تنازل عنه التاجر إضافة إلى المبلغ، وهو خصم سداد يُسجَّل من لوحة التحكم، وقيمته 0 للدفعة المسجّلة عن طريق الـ API. | | `data[].allocations` | array of object | نعم | | | `data[].allocations[].invoiceId` | string (uuid) | نعم | | | `data[].allocations[].amount` | number (double) | نعم | | | `data[].unallocatedAmount` | number (double) | نعم | قيمة amount مع discount مطروحاً منها التخصيص، وهو رصيد دائن للعميل. | | `data[].notes` | string, nullable | نعم | | | `data[].referenceId` | string, nullable | نعم | | | `data[].metadata` | map of string | نعم | | | `data[].createdVia` | string: `api`, `dashboard`, `pos`, `kiosk`, `storefront`, `unknown` | نعم | إحدى القيم api أو dashboard أو pos أو kiosk أو storefront، أو unknown لسند القبض الأقدم من تسجيل القناة. | | `hasMore` | boolean | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### بيانات دفعة `GET https://api.kosoor.com.sa/v1/payments/{id}` يحتاج صلاحية payments:read. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | قيمته دائماً payment، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `number` | string | نعم | رقم سند القبض كما يُطبع. | | `date` | string (date) | نعم | | | `customerId` | string (uuid) | نعم | | | `branchId` | string (uuid), nullable | نعم | قيمته null في سند القبض المسجّل بدون فرع، والمفتاح المحدود ببعض الفروع لا يراه. | | `paymentMethodId` | string (uuid), nullable | نعم | قيمته null في سند القبض المسجّل بدون طريقة دفع. | | `amount` | number (double) | نعم | | | `discount` | number (double) | نعم | جزء من الدين تنازل عنه التاجر إضافة إلى المبلغ، وهو خصم سداد يُسجَّل من لوحة التحكم، وقيمته 0 للدفعة المسجّلة عن طريق الـ API. | | `allocations` | array of object | نعم | | | `allocations[].invoiceId` | string (uuid) | نعم | | | `allocations[].amount` | number (double) | نعم | | | `unallocatedAmount` | number (double) | نعم | قيمة amount مع discount مطروحاً منها التخصيص، وهو رصيد دائن للعميل. | | `notes` | string, nullable | نعم | | | `referenceId` | string, nullable | نعم | | | `metadata` | map of string | نعم | | | `createdVia` | string: `api`, `dashboard`, `pos`, `kiosk`, `storefront`, `unknown` | نعم | إحدى القيم api أو dashboard أو pos أو kiosk أو storefront، أو unknown لسند القبض الأقدم من تسجيل القناة. | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### إصدار إشعار دائن `POST https://api.kosoor.com.sa/v1/credit-notes` يحتاج صلاحية credit_notes:write. يُصدر إشعاراً دائناً على بنود من فاتورة مبيعات صادرة في طلب واحد، وترجع البضاعة إلى المخزون. مع refund يُرجع الإجمالي عن طريق طريقة الدفع المحددة، وبدونه يُخصم من المستحق على العميل، وهذا يحتاج عميلاً من نوع شركة، أو عميلاً فرداً لا يزيد إشعاره على المستحق على الفاتورة. والإشعار على فاتورة ضريبية (standard) تعتمده الهيئة قبل الرد، فإذا رفضته لا يُحفظ أي شيء، وإذا تأخر ردها يرجع الإشعار بقيمة pending في zatca.status. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Idempotency-Key` | string | نعم | من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أرسل المفتاح نفسه عند إعادة المحاولة. يُحفظ لمدة 30 يوماً، وإذا كان الطلب الأول لم ينتهِ بعد يرجع 409 مع Retry-After. Length 1–255. | | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### الـ Body `Content-Type: application/json` | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `invoiceId` | string (uuid) | نعم | مطلوب. فاتورة المبيعات الصادرة، ومنها يأخذ الإشعار الفرع والعميل والأسعار. | | `reason` | string: `cancellation`, `refund`, `informationChange` | نعم | مطلوب. إحدى القيم cancellation أو refund أو informationChange، وتُرسل إلى الهيئة سبباً للإشعار. | | `lines` | array of object | نعم | مطلوب. من 1 إلى 500 منتج من الفاتورة، ولا يتكرر المنتج، وترجع البضاعة إلى المخزون. | | `lines[].itemId` | string (uuid) | نعم | مطلوب. منتج مُباع في الفاتورة. | | `lines[].quantity` | integer | نعم | مطلوب. عدد صحيح، 1 أو أكثر، ولا يزيد على المتبقي من المنتج. | | `refund` | object, nullable | | يُرجع إجمالي الإشعار عن طريق طريقة دفع. وإذا كانت قيمته null يُخصم الإجمالي من المستحق على العميل، وهذا لا يمكن في فاتورة ليس لها عميل، ولا لعميل فرد إذا زاد الإشعار على المستحق على الفاتورة. | | `refund.paymentMethodId` | string (uuid) | نعم | مطلوب. طريقة الدفع التي يُرجع بها المبلغ، من خزينتها في فرع الفاتورة. | | `notes` | string, nullable | | حتى 500 حرف، ويُطبع على الإشعار. | | `referenceId` | string, nullable | | المعرّف الخاص بهذا الإشعار في نظامك، حتى 100 حرف، ولا يتكرر في أي فاتورة أو إشعار آخر لدى التاجر. | | `metadata` | map of string, nullable | | حتى 20 مفتاحاً، طول كل مفتاح حتى 40 حرفاً، وطول القيمة حتى 500 حرف. | #### الردود **201** تم الإنشاء (`application/json`) الـ Header `Idempotent-Replayed`: قيمته true إذا كان هذا الرد محفوظاً من طلب سابق بالمفتاح نفسه. | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | قيمته دائماً creditNote، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `number` | string, nullable | نعم | رقم الإشعار كما يُطبع. | | `invoiceId` | string (uuid) | نعم | فاتورة المبيعات التي صدر عليها الإشعار. | | `customerId` | string (uuid), nullable | نعم | | | `branchId` | string (uuid) | نعم | | | `issueDate` | string (date) | نعم | | | `reason` | string: `cancellation`, `refund`, `informationChange` | نعم | إحدى القيم cancellation أو refund أو informationChange. | | `currency` | string | نعم | | | `lines` | array of object | نعم | | | `lines[].itemId` | string (uuid) | نعم | | | `lines[].name` | string, nullable | نعم | اسم المنتج وقت البيع. | | `lines[].description` | string, nullable | نعم | | | `lines[].quantity` | number (double) | نعم | | | `lines[].unitPrice` | number (double) | نعم | قبل الضريبة. | | `lines[].unitDiscount` | number (double) | نعم | الخصم المطبّق على كل وحدة قبل الضريبة، بعد مراعاة الحد الأدنى لسعر المنتج. | | `lines[].vatRate` | number (double) | نعم | نسبة مئوية، مثل 15. | | `lines[].vatAmount` | number (double) | نعم | | | `lines[].lineTotal` | number (double) | نعم | إجمالي البند بعد الخصم وشامل الضريبة. | | `subtotal` | number (double) | نعم | | | `discountTotal` | number (double) | نعم | | | `vatTotal` | number (double) | نعم | | | `total` | number (double) | نعم | | | `refund` | object, nullable | نعم | المبلغ المرجع، وقيمته null إذا خُصم الإشعار من المستحق على العميل. | | `refund.paymentMethodId` | string (uuid), nullable | نعم | قيمته null إذا لم يحدد الاسترجاع طريقة دفع، وهو الغالب في الاسترجاع من نقطة البيع، فقد أُرجع المبلغ من صندوق الفرع نفسه أو إلى البطاقة التي دُفعت بها الفاتورة. | | `refund.amount` | number (double) | نعم | | | `notes` | string, nullable | نعم | | | `referenceId` | string, nullable | نعم | | | `metadata` | map of string | نعم | | | `createdVia` | string: `api`, `dashboard`, `pos`, `kiosk`, `storefront`, `unknown` | نعم | إحدى القيم api أو dashboard أو pos أو kiosk أو storefront، أو unknown للإشعار الأقدم من تسجيل القناة. | | `zatca` | object | نعم | وضع الإشعار لدى هيئة الزكاة والضريبة والجمارك، وتكون حقول المستند null حتى يتم توقيعه. | | `zatca.kind` | string: `standard`, `simplified` | نعم | standard (تعتمدها الهيئة قبل وصولها للمشتري) أو simplified (تُبلّغ بها الهيئة بعد الإصدار). | | `zatca.status` | string: `pending`, `cleared`, `reported`, `rejected` | نعم | إحدى القيم pending أو cleared أو reported أو rejected. | | `zatca.uuid` | string (uuid), nullable | نعم | | | `zatca.counter` | integer (int32), nullable | نعم | عدّاد الفواتير لدى الهيئة (ICV). | | `zatca.hash` | string, nullable | نعم | | | `zatca.qr` | string, nullable | نعم | بصيغة Base64. للفاتورة الضريبية هو رمز QR الذي أرجعته الهيئة عند الاعتماد. | | `zatca.submittedAt` | string (date-time), nullable | نعم | | | `zatca.clearedAt` | string (date-time), nullable | نعم | | | `zatca.reportedAt` | string (date-time), nullable | نعم | | | `zatca.errors` | array of object | نعم | رسائل الهيئة إذا رفضت الفاتورة. | | `zatca.errors[].code` | string | | | | `zatca.errors[].category` | string | | | | `zatca.errors[].message` | string | | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **422** Refused by a business rule or a reused Idempotency-Key: error.code says which. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### قائمة الإشعارات الدائنة `GET https://api.kosoor.com.sa/v1/credit-notes` يحتاج صلاحية credit_notes:read. ترجع كل إشعار دائن صادر في فروع المفتاح، أياً كانت القناة التي أصدرته، ومنها الاسترجاع من نقطة البيع أو من لوحة التحكم. ولا تظهر فيها مرتجعات المبيعات والإشعارات الدائنة المنشأة من شاشات المحاسبة في لوحة التحكم، مع أنها تُحسب من المتبقي على الفاتورة. الأحدث أولاً، مقسّمة إلى صفحات عن طريق limit و startingAfter. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Query parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `invoiceId` | string (uuid) | | الإشعارات الصادرة على فاتورة المبيعات هذه. | | `referenceId` | string | | | | `customerId` | string (uuid) | | | | `branchId` | string (uuid) | | | | `issuedAfter` | string (date-time) | | الإشعارات الصادرة في هذا الوقت أو بعده، بصيغة ISO 8601. | | `issuedBefore` | string (date-time) | | الإشعارات الصادرة قبل هذا الوقت، بصيغة ISO 8601. | | `zatcaStatus` | string: `pending`, `cleared`, `reported`, `rejected` | | إحدى القيم pending أو cleared أو reported أو rejected. | | `limit` | integer (int32) | | من 1 إلى 100، والقيمة الافتراضية 20. | | `startingAfter` | string (uuid) | | الـ id لآخر سجل في الصفحة السابقة. | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `data` | array of object | نعم | | | `data[].id` | string (uuid) | نعم | | | `data[].object` | string | نعم | قيمته دائماً creditNote، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `data[].number` | string, nullable | نعم | رقم الإشعار كما يُطبع. | | `data[].invoiceId` | string (uuid) | نعم | فاتورة المبيعات التي صدر عليها الإشعار. | | `data[].customerId` | string (uuid), nullable | نعم | | | `data[].branchId` | string (uuid) | نعم | | | `data[].issueDate` | string (date) | نعم | | | `data[].reason` | string: `cancellation`, `refund`, `informationChange` | نعم | إحدى القيم cancellation أو refund أو informationChange. | | `data[].currency` | string | نعم | | | `data[].lines` | array of object | نعم | | | `data[].lines[].itemId` | string (uuid) | نعم | | | `data[].lines[].name` | string, nullable | نعم | اسم المنتج وقت البيع. | | `data[].lines[].description` | string, nullable | نعم | | | `data[].lines[].quantity` | number (double) | نعم | | | `data[].lines[].unitPrice` | number (double) | نعم | قبل الضريبة. | | `data[].lines[].unitDiscount` | number (double) | نعم | الخصم المطبّق على كل وحدة قبل الضريبة، بعد مراعاة الحد الأدنى لسعر المنتج. | | `data[].lines[].vatRate` | number (double) | نعم | نسبة مئوية، مثل 15. | | `data[].lines[].vatAmount` | number (double) | نعم | | | `data[].lines[].lineTotal` | number (double) | نعم | إجمالي البند بعد الخصم وشامل الضريبة. | | `data[].subtotal` | number (double) | نعم | | | `data[].discountTotal` | number (double) | نعم | | | `data[].vatTotal` | number (double) | نعم | | | `data[].total` | number (double) | نعم | | | `data[].refund` | object, nullable | نعم | المبلغ المرجع، وقيمته null إذا خُصم الإشعار من المستحق على العميل. | | `data[].refund.paymentMethodId` | string (uuid), nullable | نعم | قيمته null إذا لم يحدد الاسترجاع طريقة دفع، وهو الغالب في الاسترجاع من نقطة البيع، فقد أُرجع المبلغ من صندوق الفرع نفسه أو إلى البطاقة التي دُفعت بها الفاتورة. | | `data[].refund.amount` | number (double) | نعم | | | `data[].notes` | string, nullable | نعم | | | `data[].referenceId` | string, nullable | نعم | | | `data[].metadata` | map of string | نعم | | | `data[].createdVia` | string: `api`, `dashboard`, `pos`, `kiosk`, `storefront`, `unknown` | نعم | إحدى القيم api أو dashboard أو pos أو kiosk أو storefront، أو unknown للإشعار الأقدم من تسجيل القناة. | | `data[].zatca` | object | نعم | وضع الإشعار لدى هيئة الزكاة والضريبة والجمارك، وتكون حقول المستند null حتى يتم توقيعه. | | `data[].zatca.kind` | string: `standard`, `simplified` | نعم | standard (تعتمدها الهيئة قبل وصولها للمشتري) أو simplified (تُبلّغ بها الهيئة بعد الإصدار). | | `data[].zatca.status` | string: `pending`, `cleared`, `reported`, `rejected` | نعم | إحدى القيم pending أو cleared أو reported أو rejected. | | `data[].zatca.uuid` | string (uuid), nullable | نعم | | | `data[].zatca.counter` | integer (int32), nullable | نعم | عدّاد الفواتير لدى الهيئة (ICV). | | `data[].zatca.hash` | string, nullable | نعم | | | `data[].zatca.qr` | string, nullable | نعم | بصيغة Base64. للفاتورة الضريبية هو رمز QR الذي أرجعته الهيئة عند الاعتماد. | | `data[].zatca.submittedAt` | string (date-time), nullable | نعم | | | `data[].zatca.clearedAt` | string (date-time), nullable | نعم | | | `data[].zatca.reportedAt` | string (date-time), nullable | نعم | | | `data[].zatca.errors` | array of object | نعم | رسائل الهيئة إذا رفضت الفاتورة. | | `data[].zatca.errors[].code` | string | | | | `data[].zatca.errors[].category` | string | | | | `data[].zatca.errors[].message` | string | | | | `hasMore` | boolean | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### بيانات إشعار دائن `GET https://api.kosoor.com.sa/v1/credit-notes/{id}` يحتاج صلاحية credit_notes:read. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | قيمته دائماً creditNote، وتحدد نوع الكائن للكود الذي يستقبل أكثر من نوع. | | `number` | string, nullable | نعم | رقم الإشعار كما يُطبع. | | `invoiceId` | string (uuid) | نعم | فاتورة المبيعات التي صدر عليها الإشعار. | | `customerId` | string (uuid), nullable | نعم | | | `branchId` | string (uuid) | نعم | | | `issueDate` | string (date) | نعم | | | `reason` | string: `cancellation`, `refund`, `informationChange` | نعم | إحدى القيم cancellation أو refund أو informationChange. | | `currency` | string | نعم | | | `lines` | array of object | نعم | | | `lines[].itemId` | string (uuid) | نعم | | | `lines[].name` | string, nullable | نعم | اسم المنتج وقت البيع. | | `lines[].description` | string, nullable | نعم | | | `lines[].quantity` | number (double) | نعم | | | `lines[].unitPrice` | number (double) | نعم | قبل الضريبة. | | `lines[].unitDiscount` | number (double) | نعم | الخصم المطبّق على كل وحدة قبل الضريبة، بعد مراعاة الحد الأدنى لسعر المنتج. | | `lines[].vatRate` | number (double) | نعم | نسبة مئوية، مثل 15. | | `lines[].vatAmount` | number (double) | نعم | | | `lines[].lineTotal` | number (double) | نعم | إجمالي البند بعد الخصم وشامل الضريبة. | | `subtotal` | number (double) | نعم | | | `discountTotal` | number (double) | نعم | | | `vatTotal` | number (double) | نعم | | | `total` | number (double) | نعم | | | `refund` | object, nullable | نعم | المبلغ المرجع، وقيمته null إذا خُصم الإشعار من المستحق على العميل. | | `refund.paymentMethodId` | string (uuid), nullable | نعم | قيمته null إذا لم يحدد الاسترجاع طريقة دفع، وهو الغالب في الاسترجاع من نقطة البيع، فقد أُرجع المبلغ من صندوق الفرع نفسه أو إلى البطاقة التي دُفعت بها الفاتورة. | | `refund.amount` | number (double) | نعم | | | `notes` | string, nullable | نعم | | | `referenceId` | string, nullable | نعم | | | `metadata` | map of string | نعم | | | `createdVia` | string: `api`, `dashboard`, `pos`, `kiosk`, `storefront`, `unknown` | نعم | إحدى القيم api أو dashboard أو pos أو kiosk أو storefront، أو unknown للإشعار الأقدم من تسجيل القناة. | | `zatca` | object | نعم | وضع الإشعار لدى هيئة الزكاة والضريبة والجمارك، وتكون حقول المستند null حتى يتم توقيعه. | | `zatca.kind` | string: `standard`, `simplified` | نعم | standard (تعتمدها الهيئة قبل وصولها للمشتري) أو simplified (تُبلّغ بها الهيئة بعد الإصدار). | | `zatca.status` | string: `pending`, `cleared`, `reported`, `rejected` | نعم | إحدى القيم pending أو cleared أو reported أو rejected. | | `zatca.uuid` | string (uuid), nullable | نعم | | | `zatca.counter` | integer (int32), nullable | نعم | عدّاد الفواتير لدى الهيئة (ICV). | | `zatca.hash` | string, nullable | نعم | | | `zatca.qr` | string, nullable | نعم | بصيغة Base64. للفاتورة الضريبية هو رمز QR الذي أرجعته الهيئة عند الاعتماد. | | `zatca.submittedAt` | string (date-time), nullable | نعم | | | `zatca.clearedAt` | string (date-time), nullable | نعم | | | `zatca.reportedAt` | string (date-time), nullable | نعم | | | `zatca.errors` | array of object | نعم | رسائل الهيئة إذا رفضت الفاتورة. | | `zatca.errors[].code` | string | | | | `zatca.errors[].category` | string | | | | `zatca.errors[].message` | string | | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### تحميل الإشعار بصيغة PDF `GET https://api.kosoor.com.sa/v1/credit-notes/{id}/pdf` يحتاج صلاحية credit_notes:read. ملف PDF للإشعار بمقاس A4. والإشعار على فاتورة ضريبية (standard) يرجع 409 invoice_not_cleared حتى تعتمده الهيئة. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/pdf`) **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### تحميل ملف XML المرسل للهيئة `GET https://api.kosoor.com.sa/v1/credit-notes/{id}/xml` يحتاج صلاحية credit_notes:read. ملف XML الذي اعتمدته الهيئة (standard) أو الملف الموقّع المرسل لها (simplified). يرجع 409 invoice_not_cleared حتى تقبل الهيئة الإشعار، ويبقى كذلك إذا رفضته. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/xml`) **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### بيانات فرع `GET https://api.kosoor.com.sa/v1/branches/{id}` يحتاج صلاحية branches:read. الفرع الذي لا يملك المفتاح صلاحية عليه يرجع غير موجود. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | Always `branch`. | | `number` | integer (int32), nullable | نعم | The merchant's own branch number, as its receipts print it. | | `name` | string | نعم | | | `isActive` | boolean | نعم | | | `createdAt` | string (date-time) | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### قائمة الفروع `GET https://api.kosoor.com.sa/v1/branches` يحتاج صلاحية branches:read. ترجع الفروع التي يستطيع المفتاح استخدامها، الأحدث أولاً، مقسّمة إلى صفحات عن طريق limit و startingAfter. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Query parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `limit` | integer (int32) | | من 1 إلى 100، والقيمة الافتراضية 20. | | `startingAfter` | string (uuid) | | الـ id لآخر سجل في الصفحة السابقة. | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `data` | array of object | نعم | | | `data[].id` | string (uuid) | نعم | | | `data[].object` | string | نعم | Always `branch`. | | `data[].number` | integer (int32), nullable | نعم | The merchant's own branch number, as its receipts print it. | | `data[].name` | string | نعم | | | `data[].isActive` | boolean | نعم | | | `data[].createdAt` | string (date-time) | نعم | | | `hasMore` | boolean | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### بيانات صنف `GET https://api.kosoor.com.sa/v1/items/{id}` يحتاج صلاحية items:read. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | Always `item`. | | `code` | string | نعم | The merchant's item code. | | `barcode` | string, nullable | نعم | | | `name` | string | نعم | | | `kind` | string: `service`, `product`, `subscription` | نعم | | | `sellable` | boolean | نعم | Whether a sales invoice line may name it; a subscription never can. | | `price` | number (double) | نعم | The list price, before or including VAT as Kosoor.MerchantApi.Items.ItemReply.PriceIncludesVat says. | | `priceIncludesVat` | boolean | نعم | | | `vatRate` | number (double) | نعم | A percentage: 15 means 15%. | | `unitPriceRequired` | boolean | نعم | Priced per car size, so every invoice line naming it sends `unitPrice`. | | `minimumPrice` | number (double) | نعم | The lowest unit price a discount can bring it to, including VAT; 0 for none. | | `isActive` | boolean | نعم | | | `createdAt` | string (date-time) | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### قائمة الأصناف `GET https://api.kosoor.com.sa/v1/items` يحتاج صلاحية items:read. ترجع الأحدث أولاً، مقسّمة إلى صفحات عن طريق limit و startingAfter. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Query parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `kind` | string: `service`, `product`, `subscription` | | | | `code` | string | | | | `barcode` | string | | | | `limit` | integer (int32) | | من 1 إلى 100، والقيمة الافتراضية 20. | | `startingAfter` | string (uuid) | | الـ id لآخر سجل في الصفحة السابقة. | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `data` | array of object | نعم | | | `data[].id` | string (uuid) | نعم | | | `data[].object` | string | نعم | Always `item`. | | `data[].code` | string | نعم | The merchant's item code. | | `data[].barcode` | string, nullable | نعم | | | `data[].name` | string | نعم | | | `data[].kind` | string: `service`, `product`, `subscription` | نعم | | | `data[].sellable` | boolean | نعم | Whether a sales invoice line may name it; a subscription never can. | | `data[].price` | number (double) | نعم | The list price, before or including VAT as Kosoor.MerchantApi.Items.ItemReply.PriceIncludesVat says. | | `data[].priceIncludesVat` | boolean | نعم | | | `data[].vatRate` | number (double) | نعم | A percentage: 15 means 15%. | | `data[].unitPriceRequired` | boolean | نعم | Priced per car size, so every invoice line naming it sends `unitPrice`. | | `data[].minimumPrice` | number (double) | نعم | The lowest unit price a discount can bring it to, including VAT; 0 for none. | | `data[].isActive` | boolean | نعم | | | `data[].createdAt` | string (date-time) | نعم | | | `hasMore` | boolean | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### بيانات طريقة دفع `GET https://api.kosoor.com.sa/v1/payment-methods/{id}` يحتاج صلاحية payment_methods:read. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | Always `paymentMethod`. | | `code` | string | نعم | | | `name` | string | نعم | The merchant's name for it, usually Arabic. | | `nameEn` | string, nullable | نعم | | | `kind` | string: `cash`, `card`, `bank_transfer`, `cheque`, `other` | نعم | | | `isActive` | boolean | نعم | | | `createdAt` | string (date-time) | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### قائمة طرق الدفع `GET https://api.kosoor.com.sa/v1/payment-methods` يحتاج صلاحية payment_methods:read. ترجع الأحدث أولاً، مقسّمة إلى صفحات عن طريق limit و startingAfter. وقبول الطريقة للدفع في فرع معين يعتمد على الخزينة المحددة لذلك الفرع، انظر payment_method_not_in_branch. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Query parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `limit` | integer (int32) | | من 1 إلى 100، والقيمة الافتراضية 20. | | `startingAfter` | string (uuid) | | الـ id لآخر سجل في الصفحة السابقة. | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `data` | array of object | نعم | | | `data[].id` | string (uuid) | نعم | | | `data[].object` | string | نعم | Always `paymentMethod`. | | `data[].code` | string | نعم | | | `data[].name` | string | نعم | The merchant's name for it, usually Arabic. | | `data[].nameEn` | string, nullable | نعم | | | `data[].kind` | string: `cash`, `card`, `bank_transfer`, `cheque`, `other` | نعم | | | `data[].isActive` | boolean | نعم | | | `data[].createdAt` | string (date-time) | نعم | | | `hasMore` | boolean | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### إنشاء Webhook Endpoint `POST https://api.kosoor.com.sa/v1/webhook-endpoints` يحتاج صلاحية webhooks:write. يحمل الرد سر التوقيع، ولا يظهر إلا هذه المرة. والـ Endpoints الخاصة بمفتاح الاختبار لا تصلها إلا أحداث بيئة الاختبار. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Idempotency-Key` | string | نعم | من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أرسل المفتاح نفسه عند إعادة المحاولة. يُحفظ لمدة 30 يوماً، وإذا كان الطلب الأول لم ينتهِ بعد يرجع 409 مع Retry-After. Length 1–255. | | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### الـ Body `Content-Type: application/json` | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `url` | string | نعم | An `https` URL of up to 2048 characters whose host resolves to a public address. | | `events` | array of string: `salesInvoice.zatcaCleared`, `salesInvoice.zatcaReported`, `salesInvoice.zatcaRejected`, `creditNote.zatcaCleared`, `creditNote.zatcaReported`, `creditNote.zatcaRejected` | نعم | One or more event types, each at most once. | #### الردود **201** تم الإنشاء (`application/json`) الـ Header `Idempotent-Replayed`: قيمته true إذا كان هذا الرد محفوظاً من طلب سابق بالمفتاح نفسه. | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | | | `url` | string | نعم | | | `events` | array of string | نعم | | | `status` | string: `enabled`, `disabled` | نعم | | | `disabledReason` | string: `merchant`, `gone`, `failing`, nullable | نعم | Why a disabled endpoint stopped: `merchant` switched it off, the receiver answered `gone` (410), or it was `failing` for five days. | | `secret` | string, nullable | | Only in the reply to create and rotate-secret: store it, it is never shown again. | | `createdAt` | string (date-time) | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **422** Refused by a business rule or a reused Idempotency-Key: error.code says which. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### قائمة الـ Webhook Endpoints `GET https://api.kosoor.com.sa/v1/webhook-endpoints` يحتاج صلاحية webhooks:read. ترجع الـ Endpoints الخاصة بالمفتاح، الأحدث أولاً، مقسّمة إلى صفحات عن طريق limit و startingAfter. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Query parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `limit` | integer (int32) | | من 1 إلى 100، والقيمة الافتراضية 20. | | `startingAfter` | string (uuid) | | الـ id لآخر سجل في الصفحة السابقة. | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `data` | array of object | نعم | | | `data[].id` | string (uuid) | نعم | | | `data[].object` | string | نعم | | | `data[].url` | string | نعم | | | `data[].events` | array of string | نعم | | | `data[].status` | string: `enabled`, `disabled` | نعم | | | `data[].disabledReason` | string: `merchant`, `gone`, `failing`, nullable | نعم | Why a disabled endpoint stopped: `merchant` switched it off, the receiver answered `gone` (410), or it was `failing` for five days. | | `data[].secret` | string, nullable | | Only in the reply to create and rotate-secret: store it, it is never shown again. | | `data[].createdAt` | string (date-time) | نعم | | | `hasMore` | boolean | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### بيانات Webhook Endpoint `GET https://api.kosoor.com.sa/v1/webhook-endpoints/{id}` يحتاج صلاحية webhooks:read. لا يجد المفتاح إلا الـ Endpoints الخاصة به. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/json`) | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | | | `url` | string | نعم | | | `events` | array of string | نعم | | | `status` | string: `enabled`, `disabled` | نعم | | | `disabledReason` | string: `merchant`, `gone`, `failing`, nullable | نعم | Why a disabled endpoint stopped: `merchant` switched it off, the receiver answered `gone` (410), or it was `failing` for five days. | | `secret` | string, nullable | | Only in the reply to create and rotate-secret: store it, it is never shown again. | | `createdAt` | string (date-time) | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### حذف Webhook Endpoint `DELETE https://api.kosoor.com.sa/v1/webhook-endpoints/{id}` يحتاج صلاحية webhooks:write. الأحداث التي ما زالت تنتظر الإرسال إليه لا تُرسل. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Idempotency-Key` | string | نعم | من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أرسل المفتاح نفسه عند إعادة المحاولة. يُحفظ لمدة 30 يوماً، وإذا كان الطلب الأول لم ينتهِ بعد يرجع 409 مع Retry-After. Length 1–255. | | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **204** No Content الـ Header `Idempotent-Replayed`: قيمته true إذا كان هذا الرد محفوظاً من طلب سابق بالمفتاح نفسه. **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **422** Refused by a business rule or a reused Idempotency-Key: error.code says which. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### تغيير سر Webhook Endpoint `POST https://api.kosoor.com.sa/v1/webhook-endpoints/{id}/rotate-secret` يحتاج صلاحية webhooks:write. يرجع السر الجديد، ولا يظهر إلا هذه المرة. ولمدة 24 ساعة يحمل كل إرسال توقيعاً بكل سر، فيستطيع المستقبِل التبديل دون أن يفوته حدث. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Idempotency-Key` | string | نعم | من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أرسل المفتاح نفسه عند إعادة المحاولة. يُحفظ لمدة 30 يوماً، وإذا كان الطلب الأول لم ينتهِ بعد يرجع 409 مع Retry-After. Length 1–255. | | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **200** تم بنجاح (`application/json`) الـ Header `Idempotent-Replayed`: قيمته true إذا كان هذا الرد محفوظاً من طلب سابق بالمفتاح نفسه. | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | | | `url` | string | نعم | | | `events` | array of string | نعم | | | `status` | string: `enabled`, `disabled` | نعم | | | `disabledReason` | string: `merchant`, `gone`, `failing`, nullable | نعم | Why a disabled endpoint stopped: `merchant` switched it off, the receiver answered `gone` (410), or it was `failing` for five days. | | `secret` | string, nullable | | Only in the reply to create and rotate-secret: store it, it is never shown again. | | `createdAt` | string (date-time) | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **422** Refused by a business rule or a reused Idempotency-Key: error.code says which. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). --- ### إرسال حدث تجريبي `POST https://api.kosoor.com.sa/v1/webhook-endpoints/{id}/test` يحتاج صلاحية webhooks:write. يرسل حدث webhookEndpoint.test إلى الـ Endpoint فوراً، موقّعاً وتُعاد محاولته مثل أي حدث آخر. #### الـ Headers | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `Idempotency-Key` | string | نعم | من 1 إلى 255 حرفاً من حروف ASCII القابلة للطباعة، مع التمييز بين الحروف الكبيرة والصغيرة. أرسل المفتاح نفسه عند إعادة المحاولة. يُحفظ لمدة 30 يوماً، وإذا كان الطلب الأول لم ينتهِ بعد يرجع 409 مع Retry-After. Length 1–255. | | `Accept-Language` | string: `en-US`, `ar-SA` | | لغة رسائل الأخطاء في الرد. أرسل ar للعربية. Default `en-US`. | #### Path parameters | الاسم | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string | نعم | | #### الردود **202** Accepted (`application/json`) الـ Header `Idempotent-Replayed`: قيمته true إذا كان هذا الرد محفوظاً من طلب سابق بالمفتاح نفسه. | الحقل | النوع | مطلوب | الوصف | |---|---|---|---| | `id` | string (uuid) | نعم | | | `object` | string | نعم | | | `type` | string | نعم | | **400** The request is invalid: error.code names the problem and error.param the field. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **401** No usable API key: missing_api_key, invalid_api_key, revoked_api_key, expired_api_key. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **403** The key may not do this: tenant_suspended, missing_permission, branch_not_allowed. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **404** غير موجود (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **409** تعارض (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **422** Refused by a business rule or a reused Idempotency-Key: error.code says which. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). **503** A test key's sandbox is not ready: sandbox_unavailable, sandbox_failed. sandbox_unavailable comes with Retry-After. (`application/json`) شكل الخطأ الموحّد، راجع [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md). أي طلب قد يرجع خطأً بالشكل الموحّد، وكل الأكواد موجودة في [أكواد الأخطاء](https://docs.kosoor.com.sa/ar/errors.md).