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

صفحات الهبوط

CRUD لصفحات الهبوط — صفحات تحويل لمنتج واحد بأقسام (سلايدرات، نماذج طلب، زوار وهميون، عدّاد تنازلي، عروض خاصة).

بقلم: Support

صفحة الهبوط صفحة تحويل مركّزة لمنتج واحد. مستقلة عن كتالوج الواجهة — يمكنك امتلاك صفحة هبوط بدون منتج حي (لإطلاقات قادمة)، أو واحدة مرتبطة بمنتج لإعلانات مدفوعة.

يمكن بناء الأقسام (السلايدرات، نماذج الطلب، الزوار الوهميون، العدّاد التنازلي…) عبر الواجهة البرمجية بنقاط الأقسام المشروحة أدناه، أو من لوحة التحكم.

حدود الخطة

الخطة

صفحات الهبوط (كل الحالات — المسودات محسوبة)

Free

0 (شراء لمرة واحدة: 1000 دج/مدى الحياة لكل منها)

Pro

3

Unlimited / Enterprise

غير محدود

يُطبَّق الحد على كل طرق الإنشاء، بما فيها الواجهة البرمجية، مع احتساب كل صفحة هبوط بما فيها المسودات. على متجر بلغ حدّه، يُرجع POST /v1/landing-pages (وPOST /v1/landing-pages/generate) الخطأ 403 limit_reached. ولا يستطيع متجر على الخطة المجانية إنشاء صفحة إلا ما دام لديه شراء صفحة هبوط لم يُستعمل بعد؛ ويُعلِّم POST /v1/landing-pages تلك الصفحة بـ is_purchased: true، وهذا ما يجعلها مرئية على واجهة المتجر.

GET /v1/landing-pages

اسرد صفحات الهبوط. ترقيم بالمؤشّر. تُخدَم حديثًا في كل نداء، مثل GET /v1/landing-pages/{id}.

المصادقة: مفتاح منصة بصلاحية landing_pages:read، وتحتاجها GET /v1/landing-pages/{id} أيضًا. المفتاح الذي لا يحملها يتلقى 403 forbidden.

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

المعامل

النوع

ملاحظات

limit

int 1–200

الافتراضي 50

cursor

string

غير شفاف

status

active | draft

تصفية

القيمة غير المعروفة في status تُتجاهَل فتُرجَع كل الصفحات بدل 400.

الاستجابة 200

{
  "data": {
    "items": [
      {
        "id":            42,
        "title":         "Black T-Shirt — 30% off",
        "slug":          "black-tshirt-30-off",
        "public_url":    "https://your-store.example.com/landing/black-tshirt-30-off",
        "status":        "active",
        "language":      "ar",
        "product_id":    26,
        "views":         1543,
        "is_purchased":  false,
        "created_at":    "2026-03-01 10:00:00",
        "updated_at":    "2026-03-15 14:22:11"
      }
    ],
    "next_cursor": null,
    "has_more": false
  }
}

GET /v1/landing-pages/{id}

تفاصيل مع section_count.

{
  "data": {
    "id":               42,
    "title":            "Black T-Shirt — 30% off",
    "slug":             "black-tshirt-30-off",
    "public_url":       "https://your-store.example.com/landing/black-tshirt-30-off",
    "status":           "active",
    "language":         "ar",
    "product_id":       26,
    "views":            1543,
    "is_purchased":     false,
    "meta_title":       "Black T-Shirt — Cotton 200gsm — 30% off | DZBuild",
    "meta_description": "Limited-time offer on our cotton black t-shirt.",
    "section_count":    7,
    "created_at":       "2026-03-01 10:00:00",
    "updated_at":       "2026-03-15 14:22:11"
  }
}

مرجع الحقول

الحقل

ملاحظات

status

active أو draft فقط بصرامة — لا توجد حالة archived لصفحات الهبوط.

language

ar أو fr أو en.

public_url

العنوان الحي للصفحة: عنوان المتجر (نطاقه المخصّص متى صار فعّالًا، وإلا النطاق الفرعي)، ثم /landing/ ثم الـ slug. يكون null ما دام المتجر بلا عنوان. استعمله كما هو بدل بناء الرابط بنفسك.

product_id

المنتج المرتبط، أو null. القسم الذي يستقبل الطلبات (order_form وorder_button وproduct_offers) يحتاج إليه أو إلى settings.product_id الخاص به. بعد ضبطه، يمكن لـ PATCH تغييره إلى منتج آخر لكن لا يمكن إرجاعه إلى null.

views

للقراءة فقط. تُحتسب في كل مرة تُشاهَد فيها الصفحة العامة؛ ولا تستطيع الواجهة البرمجية كتابتها ولا توجد طريقة لتصفيرها.

is_purchased

true بمجرد شراء الصفحة نهائيًا (1000 دج/مدى الحياة). على الخطة المجانية هذا ما يجعل الصفحة مرئية على واجهة المتجر.

section_count

في نقطة التفاصيل فقط — عدّ حيّ لأقسام الصفحة يُحسب مع كل طلب.

meta_title / meta_description

وسوم SEO. انظر الملاحظة تحت الإنشاء.

POST /v1/landing-pages — إنشاء

المصادقة: مفتاح منصة بصلاحيتَي landing_pages:write وlanding_pages:read. تقرأ الاستجابة الصفحة بعد حفظها، فمع landing_pages:write وحدها تُحفَظ الصفحة ويُرجع النداء 403 forbidden؛ والأمر نفسه مع PATCH و/publish. يتطلب Idempotency-Key.

الجسم

الحقل

النوع

إلزامي

ملاحظات

title

string، من 1 إلى 255 بايت

✅

الحد يحسب البايتات لا الحروف: الحرف العربي يأخذ 2 بايت، فالعنوان العربي يتوقف عند نحو 127 حرفًا

slug

string

يُشتق تلقائيًا من title إن أُغفل. أما الـ slug الذي ترسله هنا فيُخزَّن بدون تطبيع — أرسل قيمة نظيفة

status

active | draft

الافتراضي draft. أي قيمة أخرى تُحوَّل بصمت إلى draft

language

ar | fr | en

الافتراضي ar. أي قيمة أخرى تُحوَّل بصمت إلى ar

product_id

int

يجب أن ينتمي إلى متجرك؛ الصفحة تربط بهذا المنتج

meta_title

string ≤ 255

عنوان SEO. إن أغفلته عبر الواجهة البرمجية يُخزَّن ويُرجَع كـ null (بخلاف نموذج لوحة التحكم الذي ينسخ title إليه). ومع ذلك تعرض الصفحة العامة title كبديل، فعنوان الصفحة الظاهر صحيح في الحالتين

meta_description

string

وصف SEO

تُجعل الـ slugs فريدة داخل متجرك بإلحاق -2 و-3 وهكذا. وإن كان أساس الـ slug فارغًا فيُستعمل landing- متبوعًا بـ 6 أحرف hex.

الأخطاء

الكود

السبب

bad_request "Body must be valid JSON"

Content-Type خاطئ أو JSON تالف

bad_request "title is required (1-255 chars)"

العنوان مفقود أو طويل جدًا

bad_request "product_id N does not belong to this store"

معرّف من متجر آخر

limit_reached (403)

بلغ المتجر حد صفحات الهبوط في خطته (المسودات محسوبة). على الخطة المجانية: لم يبقَ شراء صفحة هبوط غير مستعمل

الطلب

curl -X POST 'https://api.dzbuild.app/v1/landing-pages' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "title":      "Black T-Shirt — 30% off",
    "language":   "ar",
    "product_id": 26,
    "status":     "draft"
  }'

تُرجع 200 (وليس 201) وبنفس شكل GET /v1/landing-pages/{id}. صفحة الهبوط الجديدة بدون أقسام: أضفها بنقاط الأقسام المشروحة أدناه. فحص النشر لا يجري عند الإنشاء، لذا أنشئ الصفحة كـ draft وانشرها بعد إضافة أقسامها؛ فالصفحة المُنشأة بـ status: active تظهر للزبائن فارغة.

PATCH /v1/landing-pages/{id}

تحديث جزئي.

يتحقّق PATCH بصرامة أكبر من الإنشاء: status غير صالح يُرجع 400 bad_request ("status must be active or draft")، وlanguage غير صالح يُرجع 400 ("language must be ar, fr, or en") بدل التحويل الصامت. ويجب أن يبقى title بين 1 و255 بايت. أما الـ slug المُرسَل في PATCH فيُطبَّع، بخلاف الإنشاء. ضبط status على active يُشغّل فحص النشر (انظر /publish أدناه). وبعد أن يصير للصفحة منتج، يُرجع product_id: null الخطأ 422 product_required؛ أرسل معرّف منتج آخر لتغيير المنتج.

curl -X PATCH 'https://api.dzbuild.app/v1/landing-pages/42' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "title": "Black T-Shirt — Spring promo" }'

تغيير العنوان يُعيد توليد slug تلقائيًا فقط إن لم تُرسل slug صراحةً. وعندما يتغيّر الـ slug، بتغيير العنوان أو صراحةً، يبقى الرابط القديم يعمل ويُحوِّل إلى الرابط الجديد.

الأقسام

تعرض الصفحة أقسامها من الأعلى إلى الأسفل. قراءة الأقسام تحتاج إلى landing_pages:read، وكتابتها تحتاج إلى landing_pages:write، وكل كتابة تحتاج إلى Idempotency-Key.

النقطة

الجسم

الاستجابة

GET /v1/landing-page-section-types

200: section_types، كل عنصر فيها type مع default_settings الخاصة به

GET /v1/landing-pages/{id}/sections

200: landing_page_id وsections بترتيب العرض (حتى 500، بدون ترقيم)

POST /v1/landing-pages/{id}/sections

section_type (أو type)، وsettings اختياري

201: الـ section الجديد، مُضافًا في آخر الصفحة

POST /v1/landing-pages/{id}/sections/batch

sections: حتى 30 كائنًا فيها type (أو section_type) وsettings اختياري

201: الـ sections الجديدة. الكل أو لا شيء: عنصر واحد خاطئ يمنع حفظ الجميع

POST /v1/landing-pages/{id}/sections/reorder

sections: كل معرّفات أقسام الصفحة، كل واحد مرة واحدة، بالترتيب الجديد

200: الأقسام بترتيبها الجديد

PATCH /v1/landing-pages/{id}/sections/{section_id}

settings، وreplace اختياري

200: الـ section بعد التحديث

DELETE /v1/landing-pages/{id}/sections/{section_id}

200: { "deleted": true, "id": 901, "landing_page_id": 42 }

يحمل القسم id وsection_type وsort_order وsettings. والاستجابات التي تعيد قراءة الصفحة (القائمة وإعادة الترتيب وPATCH) تحمل أيضًا created_at وupdated_at. الأنواع الـ 14 هي image وorder_form وorder_button وfree_text وcontact_button وcountdown وfake_visitors وspecial_offer وprice_display وproduct_offers وcustom_form وimage_carousel وannouncement_bar وtestimonials. اقرأ GET /v1/landing-page-section-types قبل الكتابة، حتى تطابق مفاتيح إعداداتك ما تعرضه الصفحة.

  • الإعدادات التي ترسلها تُدمج فوق القيم الافتراضية للنوع، فنداء واحد يكفي لإنشاء قسم مضبوط بالكامل. ومع PATCH تُدمج فوق الإعدادات المخزّنة؛ أرسل "replace": true لتبدأ من جديد من القيم الافتراضية للنوع. الكائنات المتداخلة تُدمج مفتاحًا بمفتاح، أما القوائم مثل slides وitems وoffers وfields فتُستبدل كاملة. ولا يمكن تغيير نوع القسم.

  • قسم order_form أو order_button أو product_offers يحتاج إلى منتج: product_id الخاص بالصفحة أو settings.product_id الخاص به. بدونه يُرجع النداء 422 landing_page_has_no_product. وsettings.product_id من متجر آخر يُرجع 422 validation_error.

  • حقول نموذج الطلب show_name وshow_phone وshow_wilaya تُحفَظ دائمًا بقيمة true في أي قسم يحملها.

  • قائمة slides تقبل 20 عنصرًا على الأكثر، وقائمة items تقبل 30 على الأكثر (422 too_many_items). ولا يجوز أن تتجاوز الإعدادات المُرمَّزة 262144 بايت (422 settings_too_large). والنوع غير المعروف يُرجع 422 invalid_section_type.

  • عند الكتابة، الصفحة التي ليست من متجرك تُرجع 404 landing_page_not_found (القائمة والفحص يُرجعان 404 not_found)، والقسم غير الموجود على الصفحة يُرجع 404 section_not_found. وقائمة إعادة الترتيب التي تكرّر قسمًا أو تُغفله تُرجع 422 validation_error.

  • تظهر تغييرات الأقسام في GET /v1/changes. يمكن التراجع عن التعديل والحذف وإعادة الترتيب بـ POST /v1/changes/{id}/undo، والقسم المحذوف الذي يعود بالتراجع يأخذ رقمًا جديدًا. أما إضافة قسم فلا يمكن التراجع عنها: احذفه بدل ذلك.

curl -X POST 'https://api.dzbuild.app/v1/landing-pages/42/sections/batch' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "sections": [
      { "type": "announcement_bar" },
      { "type": "order_form" }
    ]
  }'

GET /v1/landing-pages/{id}/check

يُبلغ عمّا سيجده الزبون معطّلًا في الصفحة. المصادقة: landing_pages:read.

تحمل الاستجابة landing_page_id وstatus وproduct_id وsections (عدد الأقسام المفحوصة) وpublishable وblocked_by (أول رسالة مانعة، أو null) وproblems. لكل مشكلة code وseverity (blocking أو warning) وmessage؛ والمشاكل المرتبطة بقسم واحد تحمل أيضًا section_id.

الكود

الخطورة

المعنى

empty_page

blocking

الصفحة بلا أقسام، فتظهر فارغة

order_form_without_product

blocking

قسم يستقبل الطلبات، لكن لا الصفحة ولا القسم يحدّدان منتجًا، فتُسجَّل الطلبات بـ 0 دج

section_product_not_found

blocking

قسم يشير إلى منتج ليس في متجرك

no_order_form

warning

لا شيء في الصفحة يستقبل الطلبات

multiple_order_forms

warning

يظهر أكثر من order_form أو order_button واحد

variants_need_page_product

warning

قسم يحدّد منتجًا بمتغيرات والصفحة بلا منتج. أدوات اختيار المتغيرات لا تظهر إلا من منتج الصفحة نفسه، فاضبط product_id على الصفحة

يُرفض النشر ما دامت هناك مشكلة مانعة (انظر أدناه).

POST /v1/landing-pages/{id}/publish

اختصار: تحويل الحالة إلى active. يكافئ PATCH ... { status: "active" }، ويُرفض بالطريقة نفسها: ما دام GET /v1/landing-pages/{id}/check يُبلغ عن مشكلة مانعة، يُرجع النداء 422 page_not_publishable ويحمل الخطأ قائمة problems. أما التعديلات التي لا ترسل status فلا تمرّ بهذا الفحص، حتى على صفحة منشورة.

curl -X POST 'https://api.dzbuild.app/v1/landing-pages/42/publish' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Idempotency-Key: publish-42-$(date +%s)"

POST /v1/landing-pages/generate

يبني صفحة هبوط من أحد منتجاتك بالذكاء الاصطناعي. يُرجع 202 فورًا مع مهمة تتابعها؛ وتستغرق العملية نحو دقيقتين. المصادقة: صلاحية ai:generate. المفاتيح المُنشأة من لوحة التحكم أو عبر POST /v1/keys والتطبيقات الخارجية لا تحملها وتتلقى 403 forbidden؛ أما اتصالات Claude وChatGPT وDZBuild Copilot فتحملها. يتطلب Idempotency-Key.

الحقل

النوع

إلزامي

ملاحظات

title

string

✅

3 أحرف على الأقل، ويُقطع عند 255

product_id

int

✅

منتج نشِط من متجرك فيه صورة واحدة على الأقل

description

string

وصف موجز للصفحة، يُقطع عند 2000 حرف

language

ar | fr | en

الافتراضي ar

size

medium | tall

الافتراضي medium. تكلفة medium هي 20 نقطة ذكاء اصطناعي، وتكلفة tall هي 45

تحمل الاستجابة task_id وsize وcredits_charged وeta_seconds وpoll. تُخصم النقاط عند بدء العملية وتُعاد إذا فشلت أو تجاوزت المهلة. الأخطاء: 400 bad_request (العنوان مفقود أو أقصر من 3 أحرف)، 402 quota_exceeded (نقاط الذكاء الاصطناعي غير كافية)، 403 limit_reached (حد صفحات الهبوط في الخطة، مع limit وcurrent)، 409 already_processing (توليد واحد لكل متجر في الوقت نفسه)، 422 product_required أو product_not_found أو product_has_no_image، 429 rate_limited أو too_many_concurrent، 503 provider_unavailable (بدون خصم أي نقاط).

تابِع GET /v1/landing-pages/generate/{task_id} بصلاحية landing_pages:read. تُرجع task_id وstatus (processing أثناء بناء الصفحة، ثم completed أو failed) وlanding_page_id بمجرد وجود الصفحة وcurrent_step وerror. العملية التي تبقى قيد المعالجة بعد 10 دقائق تُعلَّم failed بالخطأ timeout عند المتابعة التالية، وتُعاد نقاطها.

DELETE /v1/landing-pages/{id}

حذف نهائي. وتُحذف أقسام الصفحة معها.

curl -X DELETE 'https://api.dzbuild.app/v1/landing-pages/42' \
  -H "Authorization: Bearer $DZ_KEY" \
  -H "Idempotency-Key: del-42"

الاستجابة: { "data": { "deleted": true, "id": 42 } }.

هل أجاب هذا عن سؤالك؟