Appearance
Orders
Create and manage delivery orders. An order ships a package from one of your branches to a customer's address.
List orders
GET /integration/merchant/order
Returns a paginated list of your orders. Useful for reconciliation and dashboards.
Query parameters (all optional):
| Parameter | Type | Notes |
|---|---|---|
page | number | Page number (default 1). |
limit | number | Items per page (default 20, max 100). |
status | string | Filter by status. Defaults to all statuses except cancelled. |
orderType | b2c | b2b | Filter by order type. |
dateFrom / dateTo | ISO date | Filter by creation date range. |
referenceNumber | string | Filter by your orderMerchantReferenceNumber. |
Response: 200 OK
json
{
"success": true,
"data": {
"list": [ /* order objects */ ],
"pagination": { "page": 1, "limit": 20, "total": 42 }
}
}Create an order
POST /integration/merchant/order
Request body:
json
{
"customerId": "<customer-id>",
"customerAddressId": "<address-id>",
"branchCode": "MAIN",
"specialHandlingTags": ["none"],
"paymentMethod": "cod",
"amountToCollect": 12.500,
"pricing": { "subtotal": 11.000, "deliveryFee": 1.500, "total": 12.500 },
"items": [
{ "name": "T-Shirt", "sku": "TS-01", "qty": 2, "price": 5.500 }
],
"merchantNotes": "Leave at reception",
"orderMerchantReferenceNumber": "WEB-10042"
}| Field | Required | Notes |
|---|---|---|
customerId | Yes | A customer that belongs to your merchant. |
customerAddressId | No | Which address to deliver to. Defaults to the customer's default address. |
branchId or branchCode | Yes | The pickup branch. Use either the branch's _id or its code. |
specialHandlingTags | Yes | Array; at least one tag required. Use ["none"] if nothing special. Allowed: fragile, liquid, heavy, oversized, temperature_sensitive, food, flowers, other, none. |
paymentMethod | No | cod or prepaid (default). COD must be enabled for your merchant account. |
amountToCollect | COD only | Required and non-negative for COD. Cannot exceed pricing.total. |
pricing | No | { subtotal, deliveryFee, total }. If deliveryFee is omitted, Wasal resolves it automatically from your pricing zones for the delivery area. |
items | No | Line items: { name, sku, qty, price }. |
merchantNotes | No | Internal note shown to the driver. |
agentNotes | No | Note for the delivery agent. |
orderMerchantReferenceNumber | No | Your own reference (e.g. ecommerce order number). Echoed back and used for reconciliation. |
packageSide | No | One of front, back, left, right, top, bottom, other. |
packagePhotoUrls | No | Array of photo URLs. Required if your merchant has requirePackagePhoto enabled. |
scheduledPickupAt, scheduledDeliveryAt | No | ISO timestamps. |
forceDraft | No | true saves the order as a draft regardless of the order window. |
orderType | No | b2c (default) or b2b. See the B2B note below. |
b2bSizeTier | B2B only | Required when orderType is b2b. One of box, car, van, chiller_van, truck. |
isExpress | No | true requests express delivery. Requires an express contract — see the Express note below. |
Response: 201 Created
json
{
"success": true,
"data": {
"order": { "_id": "665f...", "orderNumber": "MAIN-000123", "status": "pending" }
}
}Order window & drafts. If the delivery partner's order window is closed (or you pass
forceDraft: true), the order is created as adraftand is not dispatched until activated. Otherwise it enters the live queue immediately.
Wallet. Orders deduct the delivery fee from your Wasal wallet. If the balance is insufficient you receive
400 INSUFFICIENT_WALLET_BALANCEwith the required amount and current balance.
B2B orders. Pass
orderType: "b2b"with ab2bSizeTierto ship on the B2B rate card. B2B orders require the customer to be a business customer (400 B2B_CUSTOMER_NOT_BUSINESSotherwise) and B2B delivery to be enabled for your merchant by Wasal (400 B2B_NOT_ENABLEDotherwise). If you omitpricing.deliveryFee, the fee is resolved from the B2B rate card for the delivery area —400 B2B_PRICING_NOT_CONFIGUREDif no B2B rate exists for that destination. See Pricing for the B2B rate card.
Express delivery. Pass
isExpress: trueto request express delivery. Express requires an express contract with Wasal (400 EXPRESS_NOT_ENABLEDotherwise). Express orders bypass the order window (they are dispatched immediately, never saved as a draft for a closed window), get a 2–4 hour delivery SLA from your contracted express window, and add an express surcharge — returned aspricing.expressFeeand included in the wallet charge.
Order objects (list rows and single-order responses) include orderType, b2bSizeTier, orderRecipientBusinessName (the business customer's name, B2B only), isExpress, and pricing.expressFee.
Get an order
GET /integration/merchant/order/:orderId
Returns the full order document — status, customer snapshot, pricing, items, assigned agent (if any), and timestamps.
Update an order
PUT /integration/merchant/order/:orderId
Updates mutable fields on a pending or draft order. Only the fields you send are changed; all others remain unchanged.
Only
pendinganddraftorders can be edited — any other status returns403 ORDER_NOT_EDITABLE.
Editable fields:
| Field | Notes |
|---|---|
merchantNotes | Internal note for the driver. |
agentNotes | Note for the delivery agent. |
orderMerchantReferenceNumber | Your reference number. |
customerId | Select another customer linked to your merchant. |
customerAddressId | Select an address belonging to the effective customer; refreshes the order's delivery-address snapshot. |
items | Replaces the full items array. |
pricing | { subtotal, deliveryFee, total }. |
paymentMethod | cod or prepaid. |
amountToCollect | COD amount. |
scheduledPickupAt, scheduledDeliveryAt | ISO timestamps. |
Response: 200 OK — updated order object.
Activate a draft order
PUT /integration/merchant/order/:orderId/activate
Dispatches a draft order into the live queue. The order window must be open at the time of activation.
Response: 200 OK — order with status: "pending".
Returns
400 ORDER_WINDOW_CLOSEDif the delivery partner's order window is not currently open.
Cancel an order
PUT /integration/merchant/order/:orderId/cancel
Cancels a pending order. Sets the status to cancelled and records the cancellation timestamp.
Response: 200 OK — { "success": true, "message": "Order cancelled successfully" }. The response does not include the order object; fetch GET /order/:orderId if you need the updated record.
Only
pendingorders can be cancelled — once a driver has been assigned, the API returns403 ORDER_NOT_CANCELLABLE. Contact Wasal operations to cancel an order that is already in progress.
Advance a demo order status
PUT /integration/merchant/sandbox/order/:orderId/advance-status
Simulates an order lifecycle status for the DEMO and DEMO2 merchant accounts. This endpoint uses a live integration API key but rejects every merchant whose account code is not explicitly recognized as a demo merchant.
Send { "status": "assigned" } to attach the configured Demo Driver, or use another supported lifecycle status. The order must belong to the authenticated demo merchant.
Each successful simulated transition triggers the same integration webhook event as a driver-led transition: order.status_changed, or order.delivered when the simulated status is delivered.
This testing endpoint bypasses normal driver-led status transitions and must not be used for production merchant orders. Non-demo merchants receive
403 DEMO_MERCHANT_ONLY.
Order status history
GET /integration/merchant/order/:orderId/history
Returns the chronological statusHistory[] — each entry includes the status, timestamp, an optional note, and who made the change. Use this to render a timeline.
Agent live location
GET /integration/merchant/order/:orderId/agent-location
Returns the assigned agent's latest location while the order is in transit. Returns no location if no agent is assigned, the agent is offline, or the order is not in an active delivery status.
Shipping label
GET /integration/merchant/order/:orderId/label
Returns a printable PDF shipping label for an order owned by the authenticated merchant.
| Path parameter | Type | Notes |
|---|---|---|
orderId | string | The Wasal order _id returned by the create, list, or order-details API. This is not the human-readable orderNumber. |
bash
curl https://www.wasal.org/api/v1/integration/merchant/order/ORDER_ID/label \
-H "Authorization: Bearer pk_live_YOUR_KEY_HERE" \
--output shipping-label.pdfResponse: 200 OK with the PDF as the binary response body.
http
Content-Type: application/pdf
Content-Disposition: inline; filename="label-ORDER_NUMBER.pdf"Do not parse this response as JSON. Stream the body to a file, object storage, or your print service.
| HTTP | code | Meaning |
|---|---|---|
401 | Authentication error | API key is missing or invalid. See Authentication. |
404 | ORDER_NOT_FOUND | The order does not exist or belongs to another merchant. |
Order lifecycle
| Status | Meaning |
|---|---|
draft | Created but not yet dispatched (order window closed or forceDraft). |
pending | In the live queue, awaiting a driver. |
assigned | A driver has accepted the order. |
on_way_to_merchant | Driver is heading to your branch for pickup. |
picked_up | Driver has collected the package. |
in_transit | En route to the customer. |
on_hold | Temporarily paused by Wasal operations (e.g. an issue during delivery). Resumes or resolves to a terminal status. |
delivered | Successfully delivered (terminal). |
failed | Delivery failed (terminal). |
returned | Returned to merchant (terminal). |
cancelled | Cancelled (terminal). |
Drivers and operations move orders through these statuses. Observe changes via polling or webhooks. For closed testing, use the dedicated demo merchant, delivery partner, and driver accounts.
Errors
| HTTP | code | Meaning |
|---|---|---|
| 400 | CUSTOMER_REQUIRED | customerId missing. |
| 400 | SPECIAL_HANDLING_REQUIRED | specialHandlingTags empty. |
| 400 | INVALID_SPECIAL_HANDLING_TAG | A tag is not in the allowed list. |
| 400 | INVALID_PACKAGE_SIDE | packageSide not in the allowed list. |
| 400 | INVALID_AMOUNT_TO_COLLECT | COD amount missing or negative. |
| 400 | AMOUNT_EXCEEDS_TOTAL | COD amount greater than pricing.total. |
| 400 | COD_NOT_ENABLED | COD orders are not enabled for your merchant. |
| 400 | PACKAGE_PHOTO_REQUIRED | Merchant requires a package photo. |
| 400 | INVALID_B2B_SIZE_TIER | orderType is b2b but b2bSizeTier is missing or not in the allowed list. |
| 400 | B2B_CUSTOMER_NOT_BUSINESS | B2B order for a customer that is not a business customer. |
| 400 | B2B_NOT_ENABLED | B2B delivery is not enabled for your merchant. |
| 400 | EXPRESS_NOT_ENABLED | Express delivery is not enabled for your merchant. |
| 400 | B2B_PRICING_NOT_CONFIGURED | No B2B delivery rate is configured for the destination. |
| 400 | VALIDATION_FAILED | Other field errors — see errors. |
| 400 | INSUFFICIENT_WALLET_BALANCE | Wallet cannot cover the delivery fee (production only). |
| 400 | ORDER_NOT_DRAFT | Activate was called on an order that is not a draft. |
| 400 | ORDER_WINDOW_CLOSED | Activate was called outside the delivery partner's order window. |
| 403 | ORDER_NOT_CANCELLABLE | Cancel was called on an order that is not pending. |
| 403 | ORDER_NOT_EDITABLE | Update was called on an order that is neither pending nor draft. |
| 404 | ORDER_NOT_FOUND | Order not found under your merchant account. |
| 404 | BRANCH_NOT_FOUND | The branchId sent in an update does not exist under your merchant. |
| 404 | CUSTOMER_NOT_FOUND | Customer not found or not linked to your merchant. |
| 404 | CUSTOMER_NO_DELIVERY_ADDRESS | Customer has no address. |
See the full Error Reference.
