Build
Online orders
Accept or reject paid orders, then pick and pack them. The full lifecycle.
When a customer pays for a delivery order in the Scanimart app, the store has to say yes or no. Your POS can be where that happens — next to the counter, where the staff already are.
The lifecycle
| Status | Means | What moves it on |
|---|---|---|
PENDING |
Paid, waiting for the store | You accept or reject (or the retailer app does) |
ACCEPTED |
The store will fulfil it | Picking starts, then it is packed |
PACKED |
Ready for the rider | A rider picks it up |
OUT_FOR_DELIVERY |
With the rider | The rider delivers |
DELIVERED |
The customer has it | — a sale.completed follows |
CANCELLED |
Rejected by the store, cancelled by the customer, or by Scanimart | — |
PAYMENT_FAILED |
Checkout timed out unpaid; never a real order | — |
Picking is an event, not a status: order.picking_started fires when anyone starts picking, and from then on the customer can no longer cancel.
Hearing about a new order
order.placed is sent to your webhook endpoint the moment payment is confirmed. data.order has everything you need to show it to the cashier:
{
"object": "order",
"id": "ORDER-1A2B3C4D5E",
"store_id": 1042,
"type": "delivery",
"status": "PENDING",
"payment": { "status": "SUCCESS", "paid": true, "method": "UPI" },
"total_amount": "297.00",
"delivery_fee": "30.00",
"placed_at": "2026-10-08T10:15:00+05:30",
"lines": [
{
"barcode": "8901725181222",
"name": "Aashirvaad Atta 5kg",
"quantity": 1,
"free_quantity": 0,
"unit_price": "229.00",
"mrp": "245.00",
"line_total": "229.00",
"source": "catalog",
"external_id": "SKU-ATTA-5"
}
]
}Prices are what the customer actually paid, tax-inclusive, after any offer. free_quantity counts the units of quantity given free by a buy-X-get-Y offer. external_id on a line is your SKU, if you sent it with inventory.
If your endpoint was down, GET /v1/stores/{store_id}/orders?status=PENDING lists what is waiting, and ?updated_since= catches up on everything that changed.
Accepting
curl -X POST https://api.scanimart.com/v1/stores/1042/orders/ORDER-1A2B3C4D5E/accept \
-H "Authorization: Bearer $SCANIMART_KEY" \
-H "Idempotency-Key: 8e0f6a1c-…" \
-H "Content-Type: application/json" \
-d '{"fulfilment_mode": "STORE_STAFF", "external_id": "BILL-20391"}'fulfilment_modechooses who delivers: the store's own riders (STORE_STAFF, the default) or a Scanimart courier (SCANIMART_COURIER). See Delivery and commission for what each costs the store — and askGET …/delivery-optionsbefore offering the choice.external_idis your bill or order number. Every later event and API response about this order carries it asexternal_id.
Accepting takes the stock the order was holding. Accept or reject within 15 minutes: until then the order's units are held for it, and afterwards the hold lapses and accepting can fail with insufficient_stock if the shelf has since run out.
The store's app can act too
The store's Scanimart retailer app shows the same order. Whoever acts first wins; the second gets 409 order_not_in_required_state. Treat that as "already handled": fetch the order, and the order.accepted (or order.cancelled) webhook tells you who did it — data.order.accepted_via is APP or POS.
Rejecting
curl -X POST https://api.scanimart.com/v1/stores/1042/orders/ORDER-1A2B3C4D5E/reject \
-H "Authorization: Bearer $SCANIMART_KEY" \
-H "Idempotency-Key: 1d2c…" \
-H "Content-Type: application/json" \
-d '{"reason": "Out of stock"}'The customer is refunded in full and the held stock is released. Keep reason short and customer-safe.
Picking and packing
Picking and packing are performed in the Scanimart Staff app. The POS integration observes these steps through webhooks; it cannot start picking or mark an order packed.
order.picking_startedmeans staff have started picking. The customer's cancellation window closes, and Scanimart books the courier for a courier order.order.packedmeans staff have finished packing and the bag is ready.
In test mode, use POST /v1/sandbox/orders/{order_id}/advance with {"to":"PACKED"} to simulate staff packing before testing delivery. This simulator is unavailable with live keys.
Out for delivery and delivered are recorded by whoever delivers — the store's rider in the staff app, or the courier — and arrive as order.out_for_delivery and order.delivered.
Cancellations
order.cancelled covers three cases; data.actor.kind says which:
actor.kind |
Who | When |
|---|---|---|
retailer or pos |
The store rejected it | Only while PENDING |
customer |
The customer cancelled | Before picking started |
system |
Scanimart cancelled it | e.g. a payment problem |
data.previous_status tells you how far it had got, and data.reason why. If you had already started a bill for it, void it.
Orders paid in the store
Customers can also scan and pay in the aisle with Scan & Go. Those are type: instore orders, with nothing for the POS to accept. Verified payments reach you as sales. GET …/orders?type=instore can also include unpaid checkouts: check payment.paid, and book only confirmed sales from sale.completed or the sales feed.
