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.
| Step | Call | What it means |
|---|---|---|
| 1 | POST /{orderId}/start-picking | a picker claims the order |
| 2 | POST /{orderId}/lines/{lineId}/pick | record what was found for one line — repeated |
| 3 | POST /{orderId}/lines/{lineId}/substitutions | offer a replacement for something missing |
| 4 | POST /{orderId}/pause-picking | stop with a reason, so colleagues know why |
| 5 | POST /{orderId}/containers | record the bags or boxes and where they are |
| 6 | PUT /{orderId}/picked, then PUT /{orderId}/delivered | the 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 measure | May you exceed the ordered quantity? |
|---|---|
counted things — EA, PCE, PR, SET, packaging codes, anything unmapped | no, not by any amount |
| weighed and measured things — see below | yes, 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 shelf | The call | Resulting line status | Picked quantity |
|---|---|---|---|
| both tins there | { "pickedQty": 2, "isDone": true } | PICKED | 2 |
| only one tin | { "pickedQty": 1, "isDone": true } | PICKED | 1 |
| only one, more expected later | { "pickedQty": 1 } | PENDING | 1 |
| shelf bare | { "pickedQty": 0, "isDone": true } | UNAVAILABLE | 0 |
| shelf bare, but a replacement offered | a substitution call instead | SUBSTITUTED | 0 |
| one tin found and a replacement for the other | pick, then substitute | PARTIALLY_SUBSTITUTED | 1 |
| three tins scanned | { "pickedQty": 3 } | rejected, 400 | unchanged |
🛑 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:
| Response | Meaning |
|---|---|
400 | the request itself is wrong — too much picked, or pickedQty: 0 without isDone |
409 | the order is claimed by another picker, or it is in a state where this makes no sense |
404 | no 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.
Related
- 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