Skip to main content

Picking an order

Everything Click and Collect does happens because someone in a store walked the shop floor and put real goods in a real crate. This page follows one order through that, from the moment a picker claims it to the moment the goods leave.

Most integrations never call any of this — the Hii Retail Inventory app does it, and you subscribe to events to learn the outcome. Read this if you are driving picking from your own system, or if you need to understand what produced an event you received.

The sequence

All six steps are on the Click and Collect API under /api/v1/business-units/{businessUnitId}/orders.

StepCallWhat it means
1POST /{orderId}/start-pickinga picker claims the order
2POST /{orderId}/lines/{lineId}/pickrecord what was found for one line — repeated
3POST /{orderId}/lines/{lineId}/substitutionsoffer a replacement for something missing
4POST /{orderId}/pause-pickingstop with a reason, so colleagues know why
5POST /{orderId}/containersrecord the bags or boxes and where they are
6PUT /{orderId}/picked, then PUT /{orderId}/deliveredthe store is done; the goods have left

Steps 3 and 4 are optional. Steps 1, 2, 5 and 6 are the spine.

Claiming the order

One picker owns an order at a time. If someone else has already claimed it you get a conflict, not a silent takeover — two people picking the same order is the thing this prevents.

To take it over deliberately — the first picker went home, the phone was lost — send doForce:

{ "doForce": true }

Claiming an order you already hold is harmless and changes nothing.

Recording a pick

POST /api/v1/business-units/{businessUnitId}/orders/{orderId}/lines/{lineId}/pick
{ "pickedQty": 2, "isDone": true }

pickedQty is what the picker actually found. isDone means "I have finished with this line" — not "I found everything".

That distinction is the whole design. A picker who finds 1 of 2 and knows no more are coming sends pickedQty: 1, isDone: true, and the line stops being work. It does not become an error, and it does not become a special status — see order statuses.

To report an item as unavailable, send pickedQty: 0 with isDone: true. Zero without isDone is rejected — a partial pick of nothing is not a meaningful statement.

🛑 How much you may pick

You can never pick more than was ordered — with one exception, and it is about physics rather than policy.

Unit of measureMay you exceed the ordered quantity?
counted things — EA, PCE, PR, SET, packaging codes, anything unmappedno, not by any amount
weighed and measured things — see belowyes, by up to 10%

You cannot cut a banana to fit, so 1.05 kg against a 1 kg order is accepted. Three tins against an order of two is not.

The measured set is fixed, and it is the unit of measure on the order line that decides — not the kind of product:

KGM · GRM · TNE · LBR · LB · OZ · ONZ · LTR · MLT · HLT · PT · QT · MTR · CMT · MMT · MTK · MTQ · CMK · CMQ · KTM

Anything else gets no tolerance at all. A quantity above what is allowed is rejected with 400 and a message naming the ordered quantity, what you sent, the unit and the tolerance that applied.

Try it: two tins of beans

The customer ordered 2 × Baked Beans 400g, unit of measure EA. The picker reaches the shelf. What they send, and what comes out:

On the shelfThe callResulting line statusPicked quantity
both tins there{ "pickedQty": 2, "isDone": true }PICKED2
only one tin{ "pickedQty": 1, "isDone": true }PICKED1
only one, more expected later{ "pickedQty": 1 }PENDING1
shelf bare{ "pickedQty": 0, "isDone": true }UNAVAILABLE0
shelf bare, but a replacement offereda substitution call insteadSUBSTITUTED0
one tin found and a replacement for the otherpick, then substitutePARTIALLY_SUBSTITUTED1
three tins scanned{ "pickedQty": 3 }rejected, 400unchanged

🛑 Rows two and three differ only in isDone, and that is the point. Both picked 1 of 2. One is finished, one is still work. Nothing else in the payload distinguishes them.

🛑 Rows one and two produce the same status. A short pick that the picker finished is PICKED, exactly like a complete one — so the status alone never tells you whether an order was fulfilled in full. Compare the picked quantity against the ordered quantity. This is the single most common integration mistake on this API.

What the responses mean

A successful picking call answers 202 Accepted, not 200 OK. The difference is real: the request has been accepted and validated, and the change is being applied. It is not visible yet.

So do not read back immediately to confirm. A read issued straight after a successful call can still return the previous state. That is the gap between acceptance and application, not a failure. If you need to observe the result, subscribe to events rather than polling.

Rejections are synchronous and specific:

ResponseMeaning
400the request itself is wrong — too much picked, or pickedQty: 0 without isDone
409the order is claimed by another picker, or it is in a state where this makes no sense
404no such order or line in this business unit

Walking the store in a sensible order

Lines come back grouped by the store's own zones — whatever this shop calls them — so a picker walks one loop instead of criss-crossing the floor. Each store arranges its own zones, so the same order picked in two shops can be walked in two different orders. Nothing about the order changes; only the sequence the picker sees.

Bagging up

Before an order can be finished, the picker records what the shopping went into:

POST /api/v1/business-units/{businessUnitId}/orders/{orderId}/containers

Three things go in: the container type, how many, and which storage area each belongs to — DRY_GOODS, REFRIGERATOR or FREEZER. The storage area is what lets whoever hands the order over find all of it, including the part in a freezer.

One container type per order. The call takes a single type with a count; a mixed pack of two bags and a box cannot be expressed.

Finishing

PUT /{orderId}/picked closes the store's work, and the order becomes PICKED. Every line must be resolved first — picked, unavailable, substituted, cancelled or deleted. A line still sitting in PENDING blocks it.

PUT /{orderId}/delivered records that the goods physically left, whether the customer collected them or a driver took them.

  • Order statuses — every status these calls produce, and how a line reaches its final one
  • Substitutions — offering a replacement
  • Events — how to learn the outcome without polling