Les commandes sont le cœur de la plateforme. Chaque commande :
Appartient à exactement une boutique (scope par votre clé API — vous ne pouvez jamais toucher accidentellement aux données d'un autre marchand).
A un cycle de vie en 7 états.
Est liée à un client (dédupliqué par téléphone dans la boutique).
A 1 ou plus articles, chacun pouvant porter des variantes. Les add-ons produit (les extras payants à champs personnalisés) ne sont pas supportés par l'API v1 — voir Add-ons.
A un statut de paiement (
pending,paid,refunded) indépendant du statut de fulfillment.
Cycle de vie {#lifecycle}
chemin principal pending → confirmed → processing → shipped → delivered sauts légaux pending → processing confirmed → shipped annulation pending | confirmed | processing → cancelled (terminal) retour shipped | delivered → returned (terminal)
Voici la carte exacte des transitions appliquée par l'API, état par état :
Depuis | États suivants autorisés |
|
|
|
|
|
|
|
|
|
|
| — (terminal) |
| — (terminal) |
L'annulation n'est légale que depuis pending, confirmed et processing — une commande shipped ou delivered ne peut plus être annulée.
Un PATCH vers le statut que la commande porte déjà est un 200 sans effet.
État | Signification | Stock |
| Créée via vitrine ou API. En attente de confirmation marchand. | Non engagé |
| Marchand a confirmé (appel, revue panier). | Engagé (décrémenté) |
| En préparation pour le transporteur. | Engagé |
| Remis au transporteur. | Engagé |
| Client a signé. | Engagé |
| Annulée — stock restitué s'il avait été engagé. | Restitué |
| Retournée par le client — stock restitué. | Restitué |
cancelled et returned sont terminaux — pas de transition de sortie.
POST /v1/orders — créer une commande
Créer une commande pour la boutique appelante. Utilisé par les thèmes personnalisés, les apps mobiles / natives, les revendeurs, et toute vitrine headless qui soumet ses commandes depuis son propre serveur au lieu d'utiliser le checkout intégré de la vitrine.
Auth : clé plateforme avec orders:write. Nécessite Idempotency-Key.
La commande est créée en pending. Le stock n'est PAS engagé à la création — c'est la première entrée dans un état engagé (confirmed, processing, shipped ou delivered) qui décrémente le stock, comme dans le flux du tableau de bord. Intentionnel : ça laisse à votre équipe le temps de filtrer les fakes / doublons / non-réponses avant de toucher à l'inventaire.
Corps
{
"customer": {
"name": "Sarra Benali",
"phone": "0555000111",
"email": "[email protected]",
"wilaya_id": 16,
"commune": "Bab Ezzouar",
"address": "12 Rue X, Apt 3"
},
"delivery": {
"type": "home",
"desk_id": null,
"desk_name": null
},
"items": [
{
"product_id": 26,
"quantity": 2,
"variants": [
{ "group_name": "Color", "option_name": "Red", "color_code": "#ff0000", "price_adjustment": 0 },
{ "group_name": "Size", "option_name": "L", "color_code": null, "price_adjustment": 200 }
]
}
],
"shipping_cost": 600,
"discount": 0,
"payment_fee": 0,
"payment_method": "cod",
"notes": "Please call before delivery"
}
Référence des champs
customer (objet, requis)
Champ | Type | Requis | Notes |
| string (1–255) | ✅ | Nom complet |
| string | ✅ |
|
| string | null | Si présent, sera enregistré sur la fiche client | |
| int 1–58 | ✅ | Code wilaya algérienne |
| string (1–100) | ✅ | Texte libre, ex. "Bab Ezzouar" |
| string | Rue + appt ; peut être vide pour stop-desk |
Les clients sont dédupliqués par boutique + téléphone. Si un client avec ce téléphone existe déjà dans votre boutique, sa fiche est mise à jour (nom, wilaya, commune, adresse, email) et réutilisée. Sinon, une nouvelle fiche est créée.
delivery (objet, optionnel)
Champ | Type | Défaut | Notes |
|
|
|
|
| int | null | null | Requis si |
| string | null | null | Libellé humain optionnel |
items (tableau, requis, 1–50 lignes)
Champ | Type | Requis | Notes |
| int | ✅ | Doit appartenir à votre boutique (les IDs cross-store sont rejetés |
| int 1–9999 | ✅ | |
| tableau d'objets variante | Voir ci-dessous |
Important — tarification autoritative côté serveur. Vous ne précisez pas le prix de la ligne. C'est toujours le prix catalogue courant du produit qui est appliqué. Si vous envoyez un champ price, il est ignoré.
Le price_adjustment de chaque variante est lui aussi autoritatif côté serveur : pour chaque paire (group_name, option_name) qui correspond à une vraie option du produit, DZBuild substitue le price_adjustment du catalogue. Votre valeur ne survit que pour les paires absentes du catalogue — une tolérance volontaire pour les intégrations obsolètes — donc une « remise » négative que vous inventez est silencieusement écartée pour toute option réelle. Traitez price_adjustment en entrée comme purement informatif : renvoyez la valeur lue sur GET /v1/products/{id} pour que votre total côté client corresponde à celui du serveur.
items[].variants (tableau, optionnel)
Chaque objet variante décrit un choix d'option pour un groupe de variantes du produit :
Champ | Type | Notes |
| string | ex. "Color", "Size", "Material" |
| string | ex. "Red", "L", "Cotton" |
| string | null | Couleur hex (variantes de type couleur uniquement) |
| number | Ajouté au prix de base. Écrasé par la valeur du catalogue dès que la paire groupe/option existe sur le produit — voir la note tarification ci-dessus |
Envoyez un objet variante par groupe choisi sur la ligne. Donc "T-shirt rouge taille L" devient 2 entrées (une pour Color/Red, une pour Size/L). DZBuild les rend sur la page commande du dashboard exactement comme si le client avait cliqué sur la vitrine.
Pour les produits utilisant des variantes par pièce (ex. offre "achetez 3 t-shirts, choisissez une couleur par pièce"), utilisez quantity = 1 par ligne et créez une ligne par pièce — c'est le mapping le plus propre.
Champs monétaires de niveau supérieur
Champ | Type | Défaut | Notes |
| number ≥ 0 | 0 | Vous le calculez côté client à partir de la wilaya + type de livraison |
| number ≥ 0 | 0 | Montant code promo, remise manuelle, etc. |
| number ≥ 0 | 0 | Frais de processeur de paiement en ligne |
|
| auto | Défaut |
| string ≤ 1000 | null | Notes client, visibles sur la page commande du dashboard |
Le total est calculé côté serveur : subtotal + shipping_cost - discount + payment_fee (clamp à 0). Le subtotal lui-même = sum(items[].quantity × (price + Σ variants.price_adjustment)).
shipping_cost, discount et payment_fee sont repris exactement tels qu'envoyés — la seule vérification est que chacun soit ≥ 0. Ils ne sont pas validés contre les tarifs de livraison ni les codes promo de votre boutique : POST /v1/orders ne doit donc être appelé que depuis un serveur de confiance, jamais depuis du code navigateur ou applicatif qu'un client peut altérer.
Requête
curl -X POST 'https://api.dzbuild.app/v1/orders' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"customer": {
"name": "Sarra Benali",
"phone": "0555000111",
"wilaya_id": 16,
"commune": "Bab Ezzouar",
"address": "12 Rue X"
},
"items": [
{ "product_id": 26, "quantity": 1,
"variants": [
{ "group_name": "Duration", "option_name": "30 days", "price_adjustment": 0 }
]
}
],
"shipping_cost": 600,
"payment_method": "cod"
}'
Réponse 200
La création d'une commande renvoie HTTP 200, pas 201 — ne testez pas le code de statut ; vérifiez data.id / data.order_number. Le corps a la même forme que GET /v1/orders/{id} — entièrement peuplé avec totaux calculés, bloc client normalisé, et lignes que vous venez de créer (avec leurs variantes).
order_number a le format ORD-{store_id}-{YYYYMMDD}-{8 caractères hex majuscules}. Les commandes issues d'une landing page utilisent LP-{8 caractères hex majuscules}. Les commandes créées avant le 2026-06-02 portent un suffixe hérité de 4 caractères hex : un parseur doit accepter les deux longueurs.
Erreurs
Code | Cause |
| Content-Type incorrect ou JSON malformé |
|
|
| Nom manquant ou trop long |
| Téléphone non conforme regex |
| Wilaya invalide |
| Commune manquante ou trop longue |
| Panier vide |
| Trop de lignes (splitter en plusieurs commandes) |
|
|
| ID cross-boutique |
| Quantité invalide |
| Type de livraison invalide |
| Méthode de paiement invalide |
| Champ monétaire négatif |
| Plafond du plan Free — voir ci-dessous |
Plafond mensuel de commandes. Le plan Free est plafonné à 30 commandes par mois calendaire ; Pro, Unlimited et Enterprise sont sans plafond. Un nom de plan non reconnu retombe également sur le plafond Free de 30/mois. Le compteur est mensuel calendaire et couvre toutes les sources de commandes (vitrine + landing page + tableau de bord + API).
Idempotence
Chaque POST doit porter un en-tête Idempotency-Key. Si vous rejouez la même requête (même clé, même boutique) sous 24 h, on renvoie la même réponse — la commande est créée exactement une fois. Voir Idempotence.
# rejouable indéfiniment avec la même clé
KEY="$(uuidgen)"
for i in 1 2 3; do
curl -X POST 'https://api.dzbuild.app/v1/orders' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
-d @order.json
done
# une seule commande est créée
Webhook déclenché
Créer une commande déclenche order.created vers tous les abonnements /v1/webhooks à cet événement. Voir Événements webhook.
⚠️ Attention — /v1/webhooks ne voit que l'activité API
Les abonnements /v1/webhooks se déclenchent uniquement pour les changements effectués via l'API v1. Les commandes passées sur la vitrine ou une landing page, et les changements de statut faits dans le tableau de bord, ne déclenchent pas order.created / order.confirmed / order.shipped.
Pour des événements couvrant toute l'activité commandes, utilisez l'add-on Webhooks (/dashboard/addons → « Webhooks — Connect n8n, Make & Zapier », nécessite le plan Unlimited), qui capte toutes les commandes quelle que soit leur origine — ou continuez à interroger GET /v1/orders?since=….
Ce que le chemin de commande API saute
Une commande créée via l'API est un insert allégé. Comparée à une commande vitrine, landing page ou tableau de bord, elle ne :
Envoie aucune notification de nouvelle commande au marchand (email, Telegram, push). Si le marchand doit être alerté, abonnez-vous à
order.createdet diffusez vous-même.Produit aucun événement Pixel ou CAPI Meta / TikTok.
N'incrémente pas les compteurs
total_orders/total_spentdu client.Ne consulte pas la blacklist client — la commande API d'un client blacklisté passe. Lisez
is_banneddepuis Clients et refusez côté client si nécessaire.N'applique ni les contrôles anti-abus automatiques du checkout de la vitrine, ni les règles de l'add-on quantité min/max.
Notez aussi qu'un client créé par l'API stocke la chaîne customer.name entière dans first_name et laisse last_name vide ; pour un client existant retrouvé par téléphone, seul first_name est écrasé et tout last_name existant reste intact.
GET /v1/orders
Liste les commandes. Pagination par curseur.
Auth : n'importe quelle clé plateforme active de la boutique (orders:read est accordé par défaut et n'est pas vérifié séparément en v1 ; seul orders:write est contrôlé, sur les endpoints d'écriture).
Paramètres de requête
Param | Type | Notes |
| int 1–200 | Défaut 50 |
| string | Opaque |
| un des 7 états | Filtre |
| ISO 8601 |
|
| string | Match exact |
Un status invalide ou un since non analysable est silencieusement ignoré — vous recevez la liste non filtrée, pas un 400. Il n'y a pas de filtre payment_status en v1 ; filtrez côté client.
Requête
curl 'https://api.dzbuild.app/v1/orders?status=pending&limit=20' \ -H "Authorization: Bearer $DZ_KEY"
Réponse 200
{
"data": {
"items": [
{
"id": 6894,
"order_number": "ORD-13-20260317-AD3C91F7",
"status": "pending",
"payment_status": "pending",
"payment_method": "cod",
"total": 1000,
"customer_name": "John Doe",
"customer_phone": "0555000000",
"wilaya_id": 16,
"commune": "Bab Ezzouar",
"delivery_type": "home",
"created_at": "2026-03-17 15:18:13"
}
],
"next_cursor": null,
"has_more": false
}
}
La vue liste est volontairement compacte (pas d'items, pas de variantes). Appelez GET /v1/orders/{id} pour le détail complet.
GET /v1/orders/{id}
Détail complet avec lignes, variantes et bloc client.
Auth : n'importe quelle clé plateforme active de la boutique (orders:read n'est pas vérifié séparément en v1).
Réponse 200
{
"data": {
"id": 6894,
"order_number": "ORD-13-20260317-AD3C91F7",
"status": "pending",
"payment_status": "pending",
"payment_method": "cod",
"customer": {
"id": 5578,
"name": "John Doe",
"phone": "0555000000",
"email": null,
"wilaya_id": 16,
"commune": "Bab Ezzouar",
"address": "12 Rue X"
},
"delivery": { "type": "home", "desk_id": null, "desk_name": null },
"amounts": {
"subtotal": 1000,
"shipping_cost": 0,
"discount": 0,
"payment_fee": 0,
"total": 1000
},
"items": [
{
"id": 8421,
"product_id": 26,
"price": 1000,
"quantity": 1,
"variants": [
{ "order_item_id": 8421, "group_name": "Duration", "option_name": "30 days",
"color_code": null, "price_adjustment": "0.00" }
]
}
],
"created_at": "2026-03-17 15:18:13",
"updated_at": "2026-03-17 15:18:13"
}
}
Variantes dans la réponse
Le tableau variants de chaque article est la source de vérité de ce que le client a choisi. Chaque entrée a order_item_id, group_name, option_name, color_code (variantes couleur), et price_adjustment (le supplément par pièce, renvoyé comme string JSON et non comme nombre). Pour les offres multi-pièces, vous verrez plusieurs entrées de variantes sur le même article avec des regroupements effectifs différents — voir notes par pièce ci-dessous.
Ce que la réponse détail ne renvoie pas
notesest en écriture seule en v1 — il est stocké sur la commande et visible dans le tableau de bord, mais jamais renvoyé parGET /v1/orders/{id}.Les
product_name,skude l'article et letotalde la ligne sont capturés à la création mais ne sont pas renvoyés non plus. RejoignezGET /v1/products/{id}si vous avez besoin des noms.
Les add-ons ne sont pas exposés {#add-ons-are-not-exposed}
Les add-ons produit (les extras payants à champs personnalisés qu'un marchand configure sur un produit) n'ont aucune surface en v1 : vous ne pouvez pas les envoyer sur POST /v1/orders, et GET /v1/orders/{id} ne renvoie aucune entrée d'add-on. Une commande passée sur la vitrine avec des add-ons se relit via l'API avec le montant des add-ons déjà intégré dans items[].price (et donc dans amounts.subtotal) — mais sans ligne d'add-on, sans titre et sans attribution de fichier.
PATCH /v1/orders/{id} — changer le statut
Auth : clé plateforme avec orders:write. Nécessite Idempotency-Key.
Le corps doit être {"status": "<un des 7>"}. On valide la transition contre le tableau du Cycle de vie ; sinon on renvoie 400 avec les états suivants légaux. Un PATCH vers le statut courant de la commande est un 200 sans effet.
Requête
curl -X PATCH 'https://api.dzbuild.app/v1/orders/6894' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: confirm-6894-$(date +%s)" \
-d '{"status": "confirmed"}'
Renvoie 200 et le détail commande à jour. Les transitions concurrentes sur une même commande sont sérialisées : si une autre requête a changé le statut avant vous, la vôtre échoue proprement en bad_request (« order status changed concurrently; retry ») au lieu de s'appliquer deux fois.
Effets sur le stock
Les états à stock engagé sont confirmed, processing, shipped et delivered.
Non-engagé → engagé — le stock est décrémenté la première fois que la commande entre dans l'un de ces états, y compris via le raccourci
pending → processing. Le compteur de ventes du produit augmente également.Engagé → cancelled / returned — le stock est restitué.
Les autres transitions ne touchent pas au stock.
Le stock n'est pas validé : la décrémentation est bornée à zéro, donc une survente n'est jamais rejetée, et un problème de stock ne fait jamais échouer ni annuler le changement de statut. Le stock se comporte exactement comme pour les commandes gérées depuis le tableau de bord.
Erreurs
Code | Cause |
| Le corps n'a pas de |
| String invalide |
| Non autorisé par la machine à états (l'API émet un |
| Une autre requête a changé le statut pendant votre transition. Sûr de retry avec la même clé d'idempotence. |
| ID commande inconnu ou appartient à une autre boutique |
POST /v1/orders/{id}/cancel
Endpoint de commodité — exactement équivalent à PATCH /v1/orders/{id} avec {"status":"cancelled"}. Même table de transitions, aucune permission supplémentaire. L'annulation n'est légale que depuis pending, confirmed et processing ; depuis shipped ou delivered vous obtenez 400 bad_request (ces états ne peuvent aller que vers delivered / returned).
Auth : clé plateforme avec orders:write. Nécessite Idempotency-Key.
curl -X POST 'https://api.dzbuild.app/v1/orders/6894/cancel' \ -H "Authorization: Bearer $DZ_KEY" \ -H "Idempotency-Key: cancel-6894-$(date +%s)"
Variantes — référence complète
Les variantes font qu'un seul produit couvre plusieurs options (couleur, taille, matière, capacité, …). Sur la vitrine, les clients cliquent sur des cartes de variantes pour choisir ; via l'API, vous envoyez les options choisies dans la commande.
Modèle de variantes
Un produit a 0 ou + groupes de variantes. Un groupe a 0 ou + options. Chaque option peut avoir :
Une
value(nom affiché)Un
color_code(hex, variantes couleur uniquement)Un
price_adjustment(ajouté au prix de base)Un
image_id(image produit liée comme swatch)
Lisez GET /v1/products/{id} pour voir tous les groupes et options d'un produit :
{
"variants": [
{
"id": 11,
"name": "Color",
"type": "color",
"options": [
{ "id": 14, "value": "Red", "color_code": "#ff0000", "image_id": 28, "stock": 12 },
{ "id": 15, "value": "Blue", "color_code": "#0000ff", "image_id": 29, "stock": 5 }
]
},
{
"id": 12,
"name": "Size",
"type": "text",
"options": [
{ "id": 16, "value": "S", "stock": 10 },
{ "id": 17, "value": "M", "stock": 10 },
{ "id": 18, "value": "L", "stock": 5 }
]
}
]
}
Un produit avec 2 couleurs × 3 tailles a 6 combinaisons.
Comment envoyer les variantes sur POST /v1/orders
Convertissez la sélection client en une entrée variante par groupe choisi. Pour "T-shirt rouge taille L" :
"variants": [
{ "group_name": "Color", "option_name": "Red", "color_code": "#ff0000", "price_adjustment": 0 },
{ "group_name": "Size", "option_name": "L", "color_code": null, "price_adjustment": 0 }
]
Les noms envoyés sont stockés tels quels sur la commande — ils doivent matcher ce qu'a renvoyé GET /v1/products/{id}. price_adjustment est ajouté au prix de la ligne (donc final_price = product.price + Σ price_adjustment), mais pour toute paire présente dans le catalogue le serveur substitue sa propre valeur : envoyez donc le chiffre du catalogue.
Stock par variante
Si le marchand active variant_stock_enabled sur un produit, chaque option de variante porte son propre compteur de stock. Lisez-le depuis options[].stock. L'API ne vous bloque pas pour créer une commande avec quantity > stock — c'est l'arbitrage du marchand. Le stock est décrémenté à la première entrée dans un état engagé (confirmed, processing, shipped, delivered) et n'est jamais validé : la décrémentation est bornée à zéro, donc une survente est enregistrée plutôt que rejetée.
Stock par combinaison
Si combination_stock_enabled est on, le stock est suivi par combinaison (Red+L = 5, Red+M = 8, etc.). Les combinaisons ne sont pas exposées sur l'endpoint produits public (elles le seront en v1.1 comme tableau combinations[] sur GET /v1/products/{id}/combinations). Pour l'instant, le stock par combinaison est appliqué à la confirmation de la commande et reste visible dans le tableau de bord.
Variantes en cascade
L'add-on Cascading Variants permet au marchand de faire dépendre les options du groupe B de la sélection du groupe A (ex. "Marque → Modèle" — choisir "Apple" pour Marque ne montre que "iPhone 15" / "iPhone 14" pour Modèle). Cet add-on nécessite le plan Pro.
GET /v1/products/{id} n'expose aucune information de cascade — le mappage parent/enfant n'est appliqué que sur la vitrine et dans le tableau de bord. Votre client ne peut pas découvrir les règles de cascade via l'API en v1 : codez-les en dur, ou lisez-les depuis le tableau de bord. Envoyer une combinaison invalide crée quand même la commande (on ne bloque pas) — mais le marchand la rejettera au confirm.
Variantes image-texte
Certains marchands utilisent le type image_text (vignette à côté du libellé). Sur l'API, vous envoyez toujours group_name + option_name — l'image est purement une affaire de vitrine et n'est pas dans la payload de commande.
Offres multi-pièces
Si le marchand fait une offre "Achetez 3, mixez les couleurs", le client choisit une variante différente par pièce. Sur l'API :
"items": [
{ "product_id": 26, "quantity": 1,
"variants": [{ "group_name": "Color", "option_name": "Red" }] },
{ "product_id": 26, "quantity": 1,
"variants": [{ "group_name": "Color", "option_name": "Blue" }] },
{ "product_id": 26, "quantity": 1,
"variants": [{ "group_name": "Color", "option_name": "Green" }] }
]
Trois lignes séparées, chacune quantity = 1. Ainsi la page commande dashboard affiche la couleur de chaque pièce proprement.
Patterns courants
« Soumettre une commande depuis une vitrine React custom »
Vous construisez une vitrine React/Vue/Next.js qui parle à l'API au lieu d'utiliser les thèmes intégrés DZBuild. Flow :
Lisez
GET /v1/products+GET /v1/products/{id}pour rendre le catalogue.L'utilisateur ajoute au panier côté client.
Calculez
shipping_costdepuis lewilaya_id(rates en dur ou viaGET /v1/store— à venir v1.1).POST
/v1/ordersavec panier + bloc client + shipping cost.Affichez au client son
order_numberet une page « merci ».Interrogez
GET /v1/orders?since=…pour suivre l'avancement du fulfillment. Les événements/v1/webhooksorder.confirmed/order.shippedne se déclenchent que si le statut a été changé via l'API — un marchand qui confirme dans le tableau de bord ne produit aucun événement.
Voir le guide Thèmes & vitrines personnalisés pour un walkthrough complet.
« Synchroniser les nouvelles commandes vers mon CRM chaque minute »
Utilisez le filtre since :
curl 'https://api.dzbuild.app/v1/orders?since=2026-04-30T20:00:00Z&limit=200' \ -H "Authorization: Bearer $DZ_KEY"
Le polling est le bon outil ici. Un abonnement /v1/webhooks sur order.created ne couvre que les commandes créées par votre propre intégration via l'API — les commandes vitrine et landing page ne le déclenchent jamais, il ne peut donc pas remplacer le balayage since. Voir Webhooks.
« Confirmer toutes les commandes pending d'un seul client »
PHONE="0555000000"
curl -sS "https://api.dzbuild.app/v1/orders?status=pending&customer_phone=$PHONE" \
-H "Authorization: Bearer $DZ_KEY" \
| jq -r '.data.items[].id' \
| while read OID; do
curl -sS -X PATCH "https://api.dzbuild.app/v1/orders/$OID" \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: confirm-$OID" \
-d '{"status":"confirmed"}'
done