Produits
Le produit est l'unité vendable de base d'une boutique. Tous les appels produit sont scopés à la boutique de la clé appelante — vous ne pouvez jamais toucher accidentellement les données d'un autre marchand.
GET /v1/products
Liste les produits. Pagination par curseur. Mis en cache en périphérie pendant 30 s.
Auth : clé plateforme avec products:read.
Paramètres de requête
| Param | Type | Défaut | Notes |
|---|---|---|---|
limit | int (1–200) | 50 | Taille de page |
cursor | string | — | Du next_cursor d'une réponse précédente |
status | active | draft | archived | — | Filtre par statut |
search | string | — | Match sur name (LIKE) et sku exact |
Requête
curl 'https://api.dzbuild.app/v1/products?limit=10&status=active' \
-H "Authorization: Bearer $DZ_KEY"
Réponse 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": "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" }
}
primary_image est un nom de fichier. Construisez l'URL avec https://cdn.dzbuild.app/<store_id>/products/<filename> (ou votre domaine CDN personnalisé). Le chemin complet est dans images[].url sur GET /v1/products/{id}.
GET /v1/products/{id}
Détail complet du produit incluant images et variantes.
Auth : clé plateforme avec products:read.
Requête
curl https://api.dzbuild.app/v1/products/26 \
-H "Authorization: Bearer $DZ_KEY"
Réponse 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": "13_1768313552_b33d660c_1562f6687591.webp",
"is_primary": true, "sort_order": 0 }
],
"variants": [
{
"id": 11,
"name": "Duration",
"type": "text",
"options": [
{ "id": 14, "value": "30 days", "color_code": null, "image_id": null, "stock": null },
{ "id": 15, "value": "90 days", "color_code": null, "image_id": null, "stock": null },
{ "id": 16, "value": "180 days", "color_code": null, "image_id": null, "stock": null },
{ "id": 17, "value": "365 days", "color_code": null, "image_id": null, "stock": null }
]
}
],
"created_at": "2026-01-13 15:06:06",
"updated_at": "2026-01-13 15:12:32"
}
}
POST /v1/products — créer
Auth : clé plateforme avec products:write. Nécessite Idempotency-Key.
Corps
| Champ | Type | Requis | Notes |
|---|---|---|---|
name | string (1–255) | ✅ | |
price | number ≥ 0 | ✅ | DZD |
compare_price | number ≥ 0 | null | Prix barré | |
cost_price | number ≥ 0 | null | Interne — jamais montré au client | |
description | string | Long format, retours à la ligne acceptés | |
short_description | string ≤ 500 | Phrase courte | |
sku | string ≤ 100 | SKU interne | |
barcode | string ≤ 100 | UPC/EAN | |
weight | number | kg, pour la livraison | |
shipping_height / width / length | number | cm | |
do_insurance | bool | Forcer l'assurance livraison sur ce produit | |
track_stock | bool | Défaut false | |
stock_quantity | int ≥ 0 | Si track_stock | |
low_stock_alert | int ≥ 0 | Défaut 5. Déclenche le webhook product.stock_low. | |
variant_stock_enabled | bool | SKUs par variante | |
combination_stock_enabled | bool | SKUs par (couleur × taille). Implique variant_stock_enabled. | |
category_id | int | Doit exister dans votre boutique | |
status | active | draft | archived | Défaut draft | |
featured | bool | Défaut false |
Quand variant_stock_enabled ou combination_stock_enabled vaut true, track_stock est désactivé automatiquement (les variantes gèrent leur propre stock).
Limite par plan
Free : 5 produits actifs. Pro : 300. Unlimited / Enterprise : illimité. Le check ne compte que les lignes status = 'active'. Les brouillons ne comptent pas. À la limite :
{ "error": { "code": "bad_request",
"message": "Plan 'free' allows at most 5 active products. Upgrade to add more." } }
Requête
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"
}'
Réponse 201
Renvoie la même forme que GET /v1/products/{id}. id, slug et created_at sont maintenant remplis. Le slug est généré automatiquement depuis name et rendu unique dans votre boutique. Vous pouvez le forcer en envoyant votre propre slug (sujet à normalisation [a-z0-9-]).
Erreurs
| Code | Cause |
|---|---|
bad_request "Body must be valid JSON" | Content-Type incorrect ou JSON malformé |
bad_request "name is required (1-255 chars)" | Nom manquant ou trop long |
bad_request "price must be a non-negative number" | Prix invalide |
bad_request "category_id N does not belong to this store" | ID de catégorie cross-store |
bad_request "Plan 'free' allows at most …" | Limite de plan |
PATCH /v1/products/{id} — mettre à jour
Auth : clé plateforme avec products:write. Nécessite Idempotency-Key.
Mise à jour partielle — n'envoyez que les champs à changer. Les champs non spécifiés sont préservés.
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" }'
Renvoie 200 et le produit complet mis à jour. Si le produit n'existe pas (ou appartient à une autre boutique), vous obtenez 404 not_found.
Renommer via PATCH { name: ... } régénère automatiquement le slug uniquement si vous n'avez pas envoyé slug explicitement. Envoyez slug pour préserver une URL spécifique après un renommage.
DELETE /v1/products/{id}
Auth : clé plateforme avec products:write. Nécessite 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"
Réponse :
{ "data": { "deleted": true, "id": 26 } }
C'est une suppression dure — la ligne produit est supprimée et les FK en cascade nettoient images, variantes, offres et combinaisons. Les fichiers images dans R2 sont laissés à un sweep de nettoyage asynchrone (on ne bloque pas l'appel API sur la suppression R2).
POST /v1/products/{id}/images (à venir en v1.1)
Retournera une URL PUT pré-signée R2 pour que votre client uploade les fichiers directement vers Cloudflare R2 — pas d'aller-retour vers l'origine algérienne, pas de blocage FPM par le traitement d'image.
D'ici là, les uploads d'images passent par le tableau de bord : Produits → Modifier → Images.