Browse the docs

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:

JSON
{
  "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

Shell
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_mode chooses 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 ask GET …/delivery-options before offering the choice.
  • external_id is your bill or order number. Every later event and API response about this order carries it as external_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

Shell
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_started means staff have started picking. The customer's cancellation window closes, and Scanimart books the courier for a courier order.
  • order.packed means 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.