Orders API
Create orders with atomic stock deduction, view history, and track delivery status.
/api/v1/orders
🔒 Auth Required
Create an order. Server calculates subtotal, tax, discount, and delivery fee. Initiates payment automatically.
Request Body
{
"items": [
{ "variant_id": "uuid", "quantity": 1 } // required
],
"shipping_address_id": "uuid", // use saved address (OR provide shipping_address inline)
"shipping_address": { // OR inline (for guests)
"recipient_name": "Jane",
"phone": "0712345678",
"street": "Kimathi St",
"county": "nairobi",
"town": "CBD"
},
"county": "nairobi", // required — used for delivery zone lookup
"payment_method": "MPESA", // required — "MPESA" | "CARD"
"mpesa_phone": "0712345678", // required if MPESA
"coupon_code": "WELCOME10", // optional
"customer_note": "Leave at gate" // optional
}
Response
{
"success": true,
"data": {
"order": {
"id": "uuid",
"order_number": "ALO-2024-00001",
"status": "pending",
"payment_status": "pending",
"subtotal": 1500,
"discount_amount": 150,
"tax_amount": 216,
"delivery_fee": 200,
"total": 1766,
"tracking_status": [],
"estimated_delivery_at": null
},
"payment": {
"method": "MPESA",
"checkout_request_id": "ws_CO_...",
"message": "STK push sent to 0712345678"
}
}
}
Possible Errors
| Code | HTTP | When |
|---|---|---|
| STOCK_INSUFFICIENT | 422 | Variant out of stock |
| COUPON_INVALID | 422 | Invalid coupon |
| VALIDATION_ERROR | 422 | Missing required fields |
/api/v1/orders/my
🔒 Auth Required
Paginated list of the current user's orders, newest first.
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
page | number | No | Page |
limit | number | No | Per page |
Response
{ "success": true, "data": { "orders": [ { "id","order_number","status","payment_status","total","created_at" } ] }, "meta": { ... } }
/api/v1/orders/my/:id
🔒 Auth Required
Full order detail including line items, tracking timeline, and invoice link.
Response
{
"success": true,
"data": {
"order": {
"id": "uuid",
"order_number": "ALO-2024-00001",
"status": "shipped",
"tracking_status": [
{ "state": "Order Placed", "note": "Received", "timestamp": "2024-01-10T09:00:00Z" },
{ "state": "Shipped", "note": "Left warehouse", "timestamp": "2024-01-11T08:00:00Z" }
],
"order_items": [ { "product_snapshot": { "name","price" }, "quantity", "unit_price", "total_price" } ],
"estimated_delivery_at": "2024-01-15",
"delivery_fee_adjusted": false,
"invoice": { "id", "invoice_number", "pdf_url" }
}
}
}
/api/v1/orders/:id/cancel
🔒 Auth Required
Cancel an order. Only possible when status = "pending".
Response
{ "success": true, "message": "Order cancelled successfully." }
Possible Errors
| Code | HTTP | When |
|---|---|---|
| NOT_FOUND | 404 | Order not found or not yours |
| VALIDATION_ERROR | 400 | Order is not cancellable |
/api/v1/orders/:id/reorder
🔒 Auth Required
Fetch items from an old order for easy reordering.
Response
{
"success": true,
"message": "Reorder items prepared.",
"data": {
"items": [
{ "product_id": "...", "variant_id": "...", "name": "Classic Tee", "image": "url", "quantity": 2, "unit_price": "1500" }
],
"omitted_count": 0
}
}
Public Order Tracker
/api/v1/orders/track/request-otp
Public
Request a 6-digit OTP for secure order tracking.
Request Body
{ "order_number": "ORD-123", "email": "john@example.com" }
Response
{ "success": true, "data": { "otp_session_token": "eyJ...", "message": "OTP sent to email." } }
/api/v1/orders/track/verify-otp
Public
Verify the OTP to get a long-lived tracking token.
Request Body
{ "otp_code": "123456", "otp_session_token": "eyJ..." }
Response
{ "success": true, "data": { "trusted_link_token": "eyJ..." } }
/api/v1/orders/track
Public
Fetch public tracking timeline using a trusted link token.
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
token | string | Yes | trusted_link_token from verify step |
Response
{
"success": true,
"data": {
"order_number": "ORD-123",
"status": "shipped",
"tracking_number": "TRK-999",
"carrier": "G4S",
"estimated_delivery": "2026-06-25T00:00:00Z",
"items": [ { "product_name": "...", "quantity": 1, "product_image": "..." } ],
"timeline": [ { "status": "shipped", "note": "On the way" } ]
}
}
Admin Orders /api/v1/admin/orders
/api/v1/admin/orders
🔒 Auth Required
List all orders with filters.
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
status | string | No | pending | confirmed | shipped | delivered | cancelled |
payment_status | string | No | paid | pending | failed |
from | date | No | ISO date start |
to | date | No | ISO date end |
county | string | No | Filter by county |
search | string | No | Order number search |
assigned_to | uuid | No | Filter by assigned staff |
page | number | No | Page |
limit | number | No | Per page |
Response
{ "success": true, "data": { "orders": [ ... ] }, "meta": { ... } }
/api/v1/admin/orders/:id/status
🔒 Auth Required
Update order status. Optionally sends customer notification.
Request Body
{ "status": "shipped", "note": "Dispatched via road courier", "notify_customer": true }
Response
{ "success": true, "data": { "order": { "id","status","tracking_status" } } }
/api/v1/admin/orders/:id/tracking
🔒 Auth Required
Append a new entry to the tracking timeline.
Request Body
{ "state": "Out for Delivery", "note": "Driver in Mombasa CBD" }
Response
{ "success": true, "data": { "order": { "tracking_status": [ ... ] } } }
/api/v1/admin/orders/:id/delivery-fee
🔒 Auth Required
Adjust delivery fee. Sets delivery_fee_adjusted = true and notifies customer.
Request Body
{ "new_fee": 800, "note": "Remote area surcharge", "notify_customer": true }
Response
{ "success": true, "data": { "order": { "delivery_fee": 800, "delivery_fee_adjusted": true } } }
/api/v1/admin/orders/:id/assign
🔒 Auth Required
Assign an order to a fulfillment staff member.
Request Body
{ "assigned_to": "uuid" }
Response
{ "success": true, "data": { "order": { "assigned_to": "uuid" } } }
/api/v1/admin/orders/:id/internal-note
🔒 Auth Required
Add an internal note to an order (not visible to customer).
Request Body
{ "note": "Customer called to confirm address" }
Response
{ "success": true, "data": { "order": { "internal_note": "..." } } }