الانتقال إلى المحتوى الرئيسي

العملاء

كل فاتورة مبيعات ودفعة تُنشأ عن طريق الـ API تحدد عميلاً. والعميل إما فرد أو شركة، ونوعه يحدد نوع الفاتورة: الشركة تصدر لها فاتورة ضريبية تعتمدها هيئة الزكاة والضريبة والجمارك، والفرد تصدر له فاتورة مبسطة. التفاصيل في ابدأ من هنا.

الـ Endpointالصلاحية
POST /v1/customerscustomers:write
GET /v1/customerscustomers:read
GET /v1/customers/{id}customers:read
PATCH /v1/customers/{id}customers:write

إنشاء عميل فرد​

العميل الفرد يحتاج type و phone. ورقم الجوال بصيغة E.164: علامة + ثم رمز الدولة ثم الرقم، مثل +966501234567.

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:

{
"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.

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:

{
"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.vatNumber15 رقماً تبدأ بـ 3 وتنتهي بـ 3 (400 invalid_vat_number)، ولا يكون لعميل آخر (409 vat_number_taken)
company.idType و company.idValueيُرسلان معاً، إما كلاهما بقيمة أو كلاهما null، وقيمة idValue من 1 إلى 50 حرفاً ورقماً
address.buildingNumber4 أرقام إذا كانت الدولة SA، وحتى 20 حرفاً في غيرها
address.postalCode5 أرقام إذا كانت الدولة SA، وحتى 20 حرفاً في غيرها
company.creditLimit و company.creditPeriodDaysفي الرد فقط، ويحددهما التاجر من لوحة التحكم. حد الائتمان 0 يعني بلا حد.

أنواع الهوية​

إذا لم يكن للشركة رقم ضريبي، أو إلى جانبه، يحدد idType نوع الرقم الموجود في idValue. وهذه رموز الهيئة لهوية المشتري:

idTypeالرقم
CRNالسجل التجاري
MOMترخيص وزارة الشؤون البلدية والقروية
MLSترخيص وزارة الموارد البشرية
700الرقم الموحد 700
SAGترخيص الاستثمار (الهيئة العامة للاستثمار سابقاً، وزارة الاستثمار حالياً)
NATالهوية الوطنية
GCCهوية خليجية
IQAالإقامة
PASجواز السفر
OTHأي هوية أخرى

الحقول المشتركة لكل عميل​

الحقلالقاعدة
typeperson أو 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 في الخطأ معرّف العميل الذي يستخدمها. استخدمه بدل أن تنشئ عميلاً ثانياً:

{
"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
vatNumbercompany.vatNumber
idValuecompany.idValue
referenceIdمعرّفك الخاص

اكتب علامة + في رقم الجوال بالشكل %2B، لأن + في الـ Query String تُقرأ مسافة.

curl "https://api.kosoor.com.sa/v1/customers?phone=%2B966501234567" \
-H "Authorization: Bearer ksr_test_..."
{
"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 فارغة فلا يوجد عميل مطابق. وبدون فلاتر تعرض القائمة كل العملاء صفحة بعد صفحة، الأحدث أولاً. التفاصيل في تقسيم النتائج.

تعديل عميل​

يغيّر PATCH /v1/customers/{id} الحقول الموجودة في الـ Body فقط، وتبقى الحقول الأخرى على قيمها. وكذلك داخل company و address: لا يتغير إلا ما ترسله.

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 بقيمة null400 invalid_parameter، فأرسل الحقول التي تريد تغييرها بدلاً من ذلك
isActive بقيمة null400 invalid_parameter، فأرسل true أو false

وإذا خالف الطلب أي قاعدة فلا يتغير شيء.