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

المنتجات

المنتج هو الوحدة الأساسية القابلة للبيع في المتجر. كل نداءات المنتجات مقيّدة بمتجر المفتاح المنادي — لا يمكنك أبدًا الوصول إلى بيانات تاجر آخر بالخطأ.

GET /v1/products

قائمة المنتجات. ترقيم بالمؤشّر. مخزَّنة مؤقتًا لمدة 30 ثانية — تحقّق من ترويسة الاستجابة X-Cache: HIT|MISS.

المصادقة: أي مفتاح منصة نشِط للمتجر. صلاحية products:read ممنوحة افتراضيًا وغير مطبَّقة بشكل منفصل في v1؛ الصلاحية الوحيدة المفحوصة هي products:write على POST/PATCH/DELETE.

معاملات الاستعلام

المعاملالنوعالافتراضيملاحظات
limitint (1–200)50حجم الصفحة
cursorstringمن next_cursor لاستجابة سابقة
statusactive | draft | archivedتصفية حسب الحالة
searchstringيطابق name (LIKE) وsku بالضبط

القيمة غير المعروفة في status تُتجاهَل بدل أن تُرفَض — فتحصل على القائمة غير المصفّاة، وهي تشمل المنتجات بحالة archived. صفِّ صراحةً إن أردت العناصر الحيّة فقط.

الطلب

curl 'https://api.dzbuild.app/v1/products?limit=10&status=active' \
-H "Authorization: Bearer $DZ_KEY"

الاستجابة 200

{
"data": {
"items": [
{
"id": 26,
"name": "PRO",
"slug": "pro",
"short_description": null,
"price": 1000,
"compare_price": null,
"sku": "",
"stock_quantity": 0,
"track_stock": false,
"status": "active",
"has_variants": true,
"featured": false,
"primary_image": "https://cdn.dzbuild.app/uploads/products/13/13_1768313552_b33d660c_1562f6687591.webp",
"created_at": "2026-01-13 15:06:06",
"updated_at": "2026-01-13 15:12:32"
}
],
"next_cursor": null,
"has_more": false
},
"meta": { "request_id": "...", "api_version": "v1" }
}
تغيّر في v1.1 — روابط الصور صارت كاملة

صار primary_image (وكذلك images[].url في GET /v1/products/{id}) رابط CDN كاملًا جاهزًا للاستعمال كما هو. قبل v1.1 كان الاثنان يُرجعان اسم ملف مجرّدًا على المستدعي أن يضيف إليه البادئة بنفسه. فإن كان تكاملك يبني البادئة يدويًا، احذف ذلك المنطق — فالقيمة تبدأ أصلًا بـ https://.

GET /v1/products/{id}

تفاصيل المنتج كاملة بما فيها الصور والمتغيرات.

المصادقة: أي مفتاح منصة نشِط للمتجر (products:read غير مطبَّقة بشكل منفصل في v1).

الطلب

curl https://api.dzbuild.app/v1/products/26 \
-H "Authorization: Bearer $DZ_KEY"

الاستجابة 200

{
"data": {
"id": 26,
"name": "PRO",
"slug": "pro",
"description": "- Single store\n- Up to 300 products\n- ...",
"short_description": null,
"category_id": null,
"pricing": {
"price": 1000,
"compare_price": null,
"cost_price": null
},
"inventory": {
"sku": "",
"barcode": null,
"track_stock": false,
"stock_quantity": 0,
"low_stock_alert": 5
},
"shipping": {
"weight": null, "height": null, "width": null, "length": null,
"do_insurance": false
},
"status": "active",
"featured": false,
"has_variants": true,
"images": [
{ "id": 28, "url": "https://cdn.dzbuild.app/uploads/products/13/13_1768313552_b33d660c_1562f6687591.webp",
"alt_text": "Front view", "is_primary": true, "sort_order": 0 }
],
"variants": [
{
"id": 11,
"name": "Duration",
"type": "text",
"required": true,
"sort_order": 0,
"options": [
{ "id": 14, "value": "30 days", "color_code": null, "price_adjustment": 0,
"stock": null, "sku": null, "image_id": null, "show_as_card": false,
"sort_order": 0, "is_active": true },
{ "id": 15, "value": "90 days", "color_code": null, "price_adjustment": 500,
"stock": null, "sku": null, "image_id": null, "show_as_card": false,
"sort_order": 1, "is_active": true }
]
}
],
"combinations": [],
"combination_count": 0,
"combinations_truncated": false,
"created_at": "2026-01-13 15:06:06",
"updated_at": "2026-01-13 15:12:32"
}
}
أُضيف في v1.1

images[].alt_text، وحقول الخيار الكاملة (price_adjustment وsku وshow_as_card وsort_order وis_active)، وrequired / sort_order على مستوى المجموعة، وكتلة combinations بأكملها — كلها جديدة. تسرد combinations 300 مدخلة كحد أقصى، بينما يبقى combination_count دائمًا الإجمالي الحقيقي، وcombinations_truncated يخبرك متى اقتُطعت القائمة.

POST /v1/products — إنشاء

المصادقة: مفتاح منصة بصلاحية products:write. يتطلب Idempotency-Key.

الجسم

الحقلالنوعإلزاميملاحظات
namestring (1–255)
pricenumber ≥ 0بالدينار الجزائري
compare_pricenumber ≥ 0 | nullالسعر المشطوب
cost_pricenumber ≥ 0 | nullللتاجر فقط — لا يُعرض للعميل
descriptionstringوصف طويل، يقبل الأسطر
short_descriptionstring ≤ 500عبارة قصيرة
skustring ≤ 100SKU داخلي
barcodestring ≤ 100UPC/EAN
weightnumberكغ، للشحن
shipping_height / width / lengthnumberسم
do_insuranceboolفرض تأمين الشحن لهذا المنتج
track_stockboolالافتراضي false
stock_quantityint ≥ 0إن كان track_stock
low_stock_alertint ≥ 0الافتراضي 5. يقود شارة "مخزون منخفض" في لوحة التحكم.
variant_stock_enabledboolتتبّع المخزون لكل خيار متغيّر (أحمر، L، …)
combination_stock_enabledboolتتبّع المخزون لكل تركيبة متغيرات (أحمر+L). يستلزم variant_stock_enabled.
category_idintيجب أن يكون موجودًا في متجرك
statusactive | draft | archivedالافتراضي draft
featuredboolالافتراضي false

عند تفعيل variant_stock_enabled أو combination_stock_enabled، يُعطَّل track_stock تلقائيًا (المتغيرات تتحكم بمخزونها).

نادرًا ما تحتاج إلى ضبط هذين الحقلين مباشرةً: فـ PUT /v1/products/{id}/variants يضبطهما لك انطلاقًا من الحمولة التي ترسلها (مخزون لكل خيار أو تركيبات).

حد الخطة

Free: 5 منتجات نشطة. Pro: 300. Unlimited / Enterprise: غير محدود. يحسب فقط المنتجات ذات الحالة active — والمسوّدات لا تُحسب — ويجري العدّ لحظيًا عند كل نداء. الفحص يجري عند الإنشاء فقط: تحويل مسوّدة موجودة إلى active عبر PATCH لا يُحجب أبدًا، فيمكن لمتجر على الخطة المجانية تجاوز 5 منتجات نشطة بهذه الطريقة. واسم خطة غير معروف يعود إلى الحد المجاني وهو 5. عند بلوغ الحد:

{ "error": { "code": "bad_request",
"message": "Plan 'free' allows at most 5 active products. Upgrade to add more." } }

الطلب

curl -X POST 'https://api.dzbuild.app/v1/products' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "T-shirt - Cotton 200gsm",
"price": 1500,
"compare_price": 1900,
"description": "100% cotton, made in Algeria.",
"sku": "TS-COT-200",
"stock_quantity": 50,
"track_stock": true,
"status": "draft"
}'

الاستجابة 200

الإنشاء الناجح يُرجع HTTP 200 (وليس 201) بنفس جسم GET /v1/products/{id}. لا تتفرّع على status === 201 — تحقّق من data.id بدلًا من ذلك. id وslug وcreated_at صارت معبّأة.

عند الإنشاء يُشتقّ slug دائمًا من name — وأي slug في الجسم يُتجاهل. لتعيين slug محدّد، أنشئ أولًا ثم استعمل PATCH /v1/products/{id} مع {"slug":"…"}. التطبيع يحوّل إلى حروف صغيرة ويستبدل كل تتابع من المحارف غير الحرفية وغير الرقمية بـ - (مدرك لليونيكود — الحروف العربية والمشكّلة تُحفَظ، فهو إذًا ليس [a-z0-9-])، مع الاقتطاع عند 200 حرف؛ والتعارضات تأخذ لواحق -2 و-3 وهكذا.

الأخطاء

الكودالسبب
bad_request "Body must be valid JSON"Content-Type خاطئ أو JSON تالف
bad_request "name is required (1-255 chars)"الاسم مفقود أو طويل جدًا
bad_request "price must be a non-negative number"سعر غير صالح
bad_request "category_id N does not belong to this store"فئة من متجر آخر
bad_request "Plan 'free' allows at most …"تجاوز حد الخطة

PATCH /v1/products/{id} — تعديل

المصادقة: مفتاح منصة بصلاحية products:write. يتطلب Idempotency-Key.

تحديث جزئي — أرسل فقط الحقول التي تريد تغييرها. الحقول غير المرسلة تُحفظ كما هي.

curl -X PATCH 'https://api.dzbuild.app/v1/products/26' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "price": 1200, "status": "active" }'

تُرجع 200 والمنتج الكامل المحدَّث. إذا لم يكن المنتج موجودًا (أو ينتمي لمتجر آخر) ستحصل على 404 not_found.

عند تغيير الاسم عبر PATCH { name: ... } يُعاد توليد الـ slug تلقائيًا فقط إن لم تُرسل slug صراحةً. أرسل slug للحفاظ على رابط محدد بعد إعادة التسمية.

DELETE /v1/products/{id}

المصادقة: مفتاح منصة بصلاحية products:write. يتطلب Idempotency-Key.

curl -X DELETE 'https://api.dzbuild.app/v1/products/26' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: del-26-2026-04-30"

الاستجابة:

{ "data": { "deleted": true, "id": 26 } }

هذا حذف نهائي — يُحذف المنتج ومعه صوره ومتغيراته وعروضه وإضافاته (add-ons) وتركيباته. أما ملفات الصور المخزَّنة فتُنظَّف لاحقًا بشكل منفصل، فلا ينتظرها نداء API.

الحذف يفصل السجلّ ويكسر صفحات الهبوط المرتبطة

تحتفظ الطلبات القديمة بسطورها، ويبقى اسم المنتج وsku والسعر المُلتقطة وقت الشراء، فتُقرأ الطلبات القديمة بشكل صحيح — لكن السطر لم يعد مرتبطًا بمنتج (product_id يصير null). أما أي صفحة هبوط تشير إلى المنتج فيُمسح product_id الخاص بها، وهذا يكسر نموذج الطلب في تلك الصفحة (صفحة هبوط بلا معرّف منتج سبب معروف لطلبات بأسعار خاطئة). فضّل PATCH { "status": "archived" } على الحذف.

POST /v1/products/{id}/images — إضافة صورة

أُضيف في v1.1. المصادقة: مفتاح منصة بصلاحية products:write. يتطلب Idempotency-Key.

أنت تُعطي رابط https عموميًا؛ وتتولّى DZBuild تنزيل الصورة من جهة الخادم وتحويلها وتحسينها ثم استضافتها على شبكة CDN الخاصة بالمتجر. لا يوجد رفع للملفات عبر الواجهة البرمجية — تكفي الإشارة إلى الصورة برابط ونحن نجلبها.

الجسم

الحقلالنوعإلزاميملاحظات
urlstring ≤ 2000رابط https:// عمومي لملف الصورة
alt_textstring ≤ 255نص لإمكانية الوصول وتحسين محركات البحث
is_primaryboolاجعل هذه الصورة هي الصورة الرئيسية للمنتج
curl -X POST 'https://api.dzbuild.app/v1/products/26/images' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "url": "https://example.com/tshirt-front.jpg", "alt_text": "T-shirt front" }'
{ "data": { "image": { "id": 88,
"url": "https://cdn.dzbuild.app/uploads/products/13/13_1786570549_77c4_4d0c.webp",
"alt_text": "T-shirt front", "is_primary": true, "sort_order": 0,
"file_size": 27652, "width": 1000, "height": 1000 },
"deduplicated": false } }

قواعد يجدر معرفتها:

  • أول صورة للمنتج تصير تلقائيًا الصورة الرئيسية.
  • إرسال رابط بايتاته مرفقة أصلًا بالمنتج لا يُنشئ نسخة مكرّرة — بل تستعيد الصورة الموجودة مع "deduplicated": true (وHTTP 200 بدل 201).
  • الصيغ المقبولة: JPEG وPNG وWebP وGIF وBMP وAVIF وHEIC/HEIF وTIFF. الحد الأقصى 20 ميغابايت و10000×10000 بكسل. تُعاد ترميز الصور (مع إزالة بيانات EXIF) وتُصغَّر لتناسب 2000×2000.
  • حد أقصى 20 صورة لكل منتج.

أي الروابط تُقبل

لأسباب أمنية، لا يقبل الجالب إلا العناوين العمومية ولا يتبع أي إعادة توجيه. يُرفَض الرابط (url_refused) إذا لم يكن https، أو حمل بيانات اعتماد (https://user:pass@…)، أو استعمل منفذًا غير 443، أو كان عنوان IP بدل اسم مضيف، أو أدّى إلى عنوان خاص أو داخلي أو عنوان بيانات وصفية سحابية. أما الرابط الذي يردّ بإعادة توجيه أو بصفحة تسجيل دخول أو بأي شيء ليس صورة فيفشل بالخطأ image_fetch_failed.

الأخطاء

الكودHTTPالسبب
url_refused422رابط مرفوض حسب القواعد أعلاه
image_fetch_failed422المضيف غير متاح، أو إعادة توجيه، أو استجابة غير 200، أو ليس صورة
unsupported_image422صيغة غير مدعومة أو أبعاد خارج المجال
image_too_large422أكبر من 20 ميغابايت
too_many_images422المنتج يحتوي أصلًا على 20 صورة
not_found404المنتج ليس في متجرك

PATCH /v1/products/{id}/images/{image_id}

أُضيف في v1.1. لتعديل alt_text أو sort_order (0–999)، أو لترقية الصورة إلى رئيسية عبر is_primary: true.

curl -X PATCH 'https://api.dzbuild.app/v1/products/26/images/88' \
-H "Authorization: Bearer $DZ_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "is_primary": true }'

يحتفظ كل منتج دائمًا بصورة رئيسية واحدة بالضبط، لذا يُرفض is_primary: false بالخطأ primary_required — رقِّ صورة أخرى بدلًا من ذلك.

DELETE /v1/products/{id}/images/{image_id}

أُضيف في v1.1. يحذف سجلّ الصورة وملفاتها المخزَّنة.

{ "data": { "deleted": true, "new_primary_image_id": 89,
"variant_references_cleared": 2, "remaining_images": 3 } }

إن كانت خيارات متغيرات تشير إلى هذه الصورة، فتُمسح تلك الروابط (وتبقى الخيارات نفسها موجودة) — وvariant_references_cleared يخبرك بعددها. وحذف الصورة الرئيسية يُرقّي الصورة التالية تلقائيًا.

PUT /v1/products/{id}/variants — استبدال المتغيرات

أُضيف في v1.1. المصادقة: مفتاح منصة بصلاحية products:write. يتطلب Idempotency-Key.

هذا يستبدل كل متغيرات المنتج

لا يوجد تحديث جزئي للمتغيرات. اقرأ الحالة الحالية عبر GET /v1/products/{id} وأعد إرسال كل ما تريد الاحتفاظ به — فكل ما تحذفه من الحمولة يُحذف فعليًا. أرسل {"groups": []} لإزالة جميع المتغيرات.

الجسم

الحقلالنوعإلزاميملاحظات
groupsarrayمجموعات المتغيرات بترتيب العرض. و[] تمسح كل المتغيرات.
groups[].namestring ≤ 100مثل Color أو Size. فريد داخل المنتج.
groups[].typetext | color | image_text | selectable | dropdownالافتراضي text. وselectable تعني مجموعة إضافات اختيارية متعددة الاختيار. وdropdown تعني خيارات نصية تظهر في قائمة منسدلة.
groups[].requiredboolالافتراضي true (ودائمًا false مع selectable)
groups[].options[].namestring ≤ 100فريد داخل المجموعة
groups[].options[].color_code#rrggbbلمجموعات color
groups[].options[].price_adjustmentnumberيُضاف إلى السعر الأساسي (أو يُطرح منه)
groups[].options[].stockint ≥ 0 | nullمخزون لكل خيار
groups[].options[].skustring ≤ 100SKU لكل خيار
groups[].options[].image_idintيجب أن يكون صورة موجودة لهذا المنتج
groups[].options[].show_as_cardboolعرض الخيار على شكل بطاقة صورة
combinationsarrayمخزون لكل تركيبة (يحتاج مجموعتين أو أكثر من غير نوع selectable)
combinations[].optionsobject{ "Color": "Red", "Size": "L" } — مدخلة واحدة لكل مجموعة من غير نوع selectable
combinations[].stockint ≥ 0
combinations[].skustring ≤ 100
combinations[].is_activeboolالافتراضي true

الحدود: 10 مجموعات، و100 خيار لكل مجموعة، و200 خيار إجمالًا، و1000 تركيبة.

curl -X PUT 'https://api.dzbuild.app/v1/products/26/variants' \
-H "Authorization: Bearer $DZ_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"groups": [
{ "name": "Color", "type": "color", "options": [
{ "name": "Red", "color_code": "#ff0000", "image_id": 88 },
{ "name": "Blue", "color_code": "#0000ff" } ] },
{ "name": "Size", "type": "text", "options": [
{ "name": "L" }, { "name": "XL", "price_adjustment": 100 } ] }
],
"combinations": [
{ "options": { "Color": "Red", "Size": "L" }, "stock": 5, "sku": "TS-R-L" },
{ "options": { "Color": "Blue", "Size": "XL" }, "stock": 2 }
]
}'

تُرجع كتلتَي variants وcombinations الجديدتين (بنفس شكل GET /v1/products/{id}).

وضع المخزون يُضبط لك تلقائيًا

  • عند إرسال تركيبات: مخزون لكل تركيبة (combination_stock_enabled)، مع تعطيل track_stock على مستوى المنتج.
  • بلا تركيبات لكن الخيارات تحمل stock: مخزون لكل خيار (variant_stock_enabled)، مع تعطيل track_stock.
  • لا هذا ولا ذاك: المتغيرات للعرض فقط، ويبقى المخزون على مستوى المنتج يعمل كالمعتاد.

الأخطاء

الكودHTTPالسبب
validation_error422أسماء أو أنواع أو ألوان أو أرقام غير صالحة، أو تجاوز أحد الحدود
invalid_image_id422image_id ليس صورة لهذا المنتج
combinations_not_applicable422أُرسلت تركيبات مع أقل من مجموعتين من غير نوع selectable
duplicate_combination422تركيبتان بنفس مجموعة الخيارات
not_found404المنتج ليس في متجرك

يجري التحقق من الصحة قبل حذف أي شيء — فالحمولة المرفوضة تترك متغيراتك الحالية دون أي مساس.