Orders
Orders are the heart of the platform. Every order:
- Belongs to exactly one store.
- Has a 7-state lifecycle.
- Is tied to a customer (deduped by phone within the store).
- Has 1+ items, each optionally with variants and add-ons.
- Has a payment status (
pending,paid,refunded) independent of the fulfillment status.
All API order calls are scoped to your store.
Lifecycle
pending → confirmed → processing → shipped → delivered
↘ returned
↘ cancelled
| State | Meaning | Stock |
|---|---|---|
pending | Created via storefront or API. Awaiting merchant confirmation. | Not committed |
confirmed | Merchant confirmed (called customer, reviewed cart). | Committed (decremented) |
processing | Being packed / preparing for courier pickup. | Committed |
shipped | Handed to courier. | Committed |
delivered | Customer signed for it. | Committed |
cancelled | Order cancelled — stock restored if it had been committed. | Restored |
returned | Customer returned the item — stock restored. | Restored |
cancelled and returned are terminal — you cannot transition out of them.
GET /v1/orders
List orders. Cursor-paginated.
Auth: platform key with orders:read.
Query parameters
| Param | Type | Notes |
|---|---|---|
limit | int 1–200 | Default 50 |
cursor | string | Opaque |
status | one of the 7 states | Filter |
since | ISO 8601 string | created_at >= since |
customer_phone | string | Exact match |
Request
curl 'https://api.dzbuild.app/v1/orders?status=pending&limit=20' \
-H "Authorization: Bearer $DZ_KEY"
Response 200
{
"data": {
"items": [
{
"id": 6894,
"order_number": "ORD-13-20260317-AD3C",
"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
}
}
GET /v1/orders/{id}
Full detail with line items, variants, and customer block.
Auth: platform key with orders:read.
Response 200
{
"data": {
"id": 6894,
"order_number": "ORD-13-20260317-AD3C",
"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": [
{ "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"
}
}
PATCH /v1/orders/{id} — change status
Auth: platform key with orders:write. Requires Idempotency-Key.
Body must be {"status": "<one of the 7>"}. We validate the transition is allowed; otherwise we return 400 with the legal next states.
Request
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"}'
Returns 200 and the updated order detail. The status transition is wrapped in a transaction with SELECT ... FOR UPDATE so concurrent attempts collide cleanly with optimistic concurrency.
Stock side-effects
When the transition crosses the committed-stock boundary:
- Non-committed → committed (e.g.
pending → confirmed) — stock is decremented, sales count is incremented, products cache is invalidated. - Committed → cancelled / returned — stock is restored, products cache invalidated.
- Other transitions don't touch stock.
Stock work uses the legacy OrderController::handleStockChange exactly so behaviour matches the dashboard 1:1.
Errors
| Code | Cause |
|---|---|
bad_request "status must be one of: pending, confirmed, …" | Invalid string |
bad_request "Transition X → Y not allowed. From 'X' you can only go to: …" | Not allowed by the state machine |
bad_request "order status changed concurrently; retry" | Another request changed status while you were transitioning. Safe to retry with same idempotency key. |
not_found | Order id is unknown or belongs to another store |
POST /v1/orders/{id}/cancel
Convenience endpoint. Equivalent to PATCH /v1/orders/{id} with {"status":"cancelled"} but with a slightly more permissive rule: any non-terminal state can transition to cancelled.
Auth: platform key with orders:write. Requires 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)"
POST /v1/orders (reserved)
Order creation via API is on the v1.1 roadmap with first-class support: full order body (items, variants, addons, customer) signed for replay protection, returns a fully populated order. Until then, orders are created by customers on the storefront via the legacy /api/orders endpoint with anti-fraud and rate limits.
For now, treat the API as read + status-transition for orders. New orders flow in via storefront; you observe and act on them through the API.
Common patterns
"Sync new orders to my CRM every minute"
Use the since filter:
curl 'https://api.dzbuild.app/v1/orders?since=2026-04-30T20:00:00Z&limit=200' \
-H "Authorization: Bearer $DZ_KEY"
Even better — register a webhook for order.created and skip polling entirely. See Webhooks.
"Confirm all pending orders for one customer"
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