Landing pages
Une landing page est une page de conversion focalisée sur un seul produit. Elles sont indépendantes du catalogue boutique — vous pouvez avoir une landing page sans produit en ligne (pour des lancements à venir), ou une liée à un produit pour des pubs payantes.
Les sections (carrousels, faux visiteurs, comptes à rebours, etc.) sont gérées dans le tableau de bord en v1 ; l'API ne fait que CRUD sur l'enregistrement parent. Une mise à jour v1.1 exposera aussi le CRUD des sections.
Limites par plan
| Plan | Landing pages (tous statuts — les brouillons comptent) |
|---|---|
| Free | 0 (achat unique : 1000 DZD / à vie chacune) |
| Pro | 3 |
| Unlimited / Enterprise | illimité |
Le plafond n'est appliqué que par les flux création / duplication du tableau de bord, en comptant chaque landing page, brouillons inclus. L'API n'applique rien : POST /v1/landing-pages suivi de /publish contourne totalement le plafond, et sur un plan payant les pages en trop s'affichent bien en ligne sur la vitrine. Sur le plan Free, les pages restent invisibles sauf si la page a été achetée (is_purchased).
GET /v1/landing-pages
Liste les landing pages. Pagination par curseur. Mis en cache pendant 30 s — vérifiez l'en-tête de réponse X-Cache: HIT|MISS. GET /v1/landing-pages/{id} n'est pas mis en cache.
Auth : n'importe quelle clé plateforme active de la boutique (landing_pages:read n'est pas appliqué en v1 ; seul landing_pages:write est contrôlé, sur les endpoints d'écriture).
Paramètres de requête
| Param | Type | Notes |
|---|---|---|
limit | int 1–200 | Défaut 50 |
cursor | string | Opaque |
status | active | draft | Filtre |
Un status non reconnu est ignoré : toutes les pages sont renvoyées plutôt qu'un 400.
Réponse 200
{
"data": {
"items": [
{
"id": 42,
"title": "Black T-Shirt — 30% off",
"slug": "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}
Détail avec section_count.
{
"data": {
"id": 42,
"title": "Black T-Shirt — 30% off",
"slug": "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"
}
}
Référence des champs
| Champ | Notes |
|---|---|
status | Strictement active ou draft — il n'y a pas d'état archived pour les landing pages. |
language | ar, fr ou en. |
product_id | Le produit lié, ou null. Une page sans product id ne peut pas tarifer correctement son formulaire de commande. |
views | Lecture seule. Compté à chaque consultation de la page publique ; l'API ne peut pas l'écrire et il n'existe aucun moyen de le remettre à zéro. |
is_purchased | true une fois la page achetée définitivement (1000 DZD / à vie). Sur le plan Free, c'est ce qui rend la page visible sur la vitrine. |
section_count | Endpoint détail uniquement — un comptage en direct des sections de la page, calculé à chaque requête. |
meta_title / meta_description | Balises SEO. Voir la note sous création. |
POST /v1/landing-pages — créer
Auth : clé plateforme avec landing_pages:write. Nécessite Idempotency-Key.
Corps
| Champ | Type | Requis | Notes |
|---|---|---|---|
title | string 1–255 | ✅ | |
slug | string | Auto-déduit de title si omis. Un slug que vous fournissez ici est stocké sans normalisation — envoyez-en un propre | |
status | active | draft | Défaut draft. Toute autre valeur est silencieusement ramenée à draft | |
language | ar | fr | en | Défaut ar. Toute autre valeur est silencieusement ramenée à ar | |
product_id | int | Doit appartenir à votre boutique ; la page lie ce produit | |
meta_title | string ≤ 255 | Titre SEO. Omis via l'API, il est stocké et renvoyé comme null (contrairement au formulaire du tableau de bord, qui y recopie title). La page publique affiche quand même title en repli, le titre visible est donc correct dans les deux cas | |
meta_description | string | Description SEO |
Les slugs sont rendus uniques dans votre boutique par ajout de -2, -3, … Une base de slug vide retombe sur landing- suivi de 6 caractères hex.
Erreurs
| Code | Cause |
|---|---|
bad_request "Body must be valid JSON" | Content-Type incorrect ou JSON malformé |
bad_request "title is required (1-255 chars)" | Titre manquant ou trop long |
bad_request "product_id N does not belong to this store" | ID cross-boutique |
Requête
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"
}'
Renvoie 200 (et non 201) avec la même forme que GET /v1/landing-pages/{id}. La nouvelle landing page n'a aucune section — peuplez-les depuis le dashboard.
PATCH /v1/landing-pages/{id}
Mise à jour partielle.
PATCH valide plus strictement que la création : un status invalide renvoie 400 bad_request (« status must be active or draft ») et un language invalide renvoie 400 (« language must be ar, fr, or en ») au lieu d'être coercé. title doit toujours faire 1 à 255 caractères. Un slug envoyé sur PATCH est normalisé, contrairement à la création.
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" }'
Renommer régénère slug automatiquement uniquement si vous n'avez pas envoyé slug explicitement.
POST /v1/landing-pages/{id}/publish
Raccourci : passer le status à active. Équivaut à PATCH ... { status: "active" }.
curl -X POST 'https://api.dzbuild.app/v1/landing-pages/42/publish' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: publish-42-$(date +%s)"
DELETE /v1/landing-pages/{id}
Suppression dure. Les sections de la page sont supprimées avec elle.
curl -X DELETE 'https://api.dzbuild.app/v1/landing-pages/42' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Idempotency-Key: del-42"
Réponse : { "data": { "deleted": true, "id": 42 } }.