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. An ERP or OMS learns the outcome from Customer Order's events, and a system that works with the picking itself, such as a TMS, subscribes to Click and Collect's events. 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.

One picker owns an order at a time. Picker A starts order C7K2M9P. When picker B tries to start it too, Click and Collect answers 409 Conflict instead of letting two people pick the same order. If picker A has gone home, picker B sends doForce true and takes the order over.

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.

One tin of two found, two different answers. A pick of quantity 1 with isDone true makes the line PICKED: the picker is finished with it and nobody comes back. The same pick without isDone leaves the line PENDING: it is still work, and the picker expects to find more.

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.

You can never pick more than was ordered, except for weighed and measured goods, which may go over by up to 10 percent. Bananas ordered as 1 kg accept anything up to 1.10 kg, so 1.05 kg is accepted and 1.15 kg is rejected with 400. Baked beans ordered as 2 tins accept 1 or 2, and 3 tins are rejected: counted goods get no tolerance.

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.

A short pick has the same status as a full one. Two lines for Baked Beans 400g, both ordered 2: on one the picker found 2, on the other 1 and marked the line done. Both lines are PICKED. Only the quantities show that the second customer gets one tin less, so compare the picked quantity with the ordered quantity.

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.

A picking call answers 202 Accepted: the request is valid and the change is being applied, but it is not visible yet. A read sent straight after PUT picked can still show IN_PROGRESS. The order-status-changed event with newStatus PICKED is the signal that the change has been applied, so subscribe rather than poll.

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 in the order of the store's own layout, its zones and categories, so a picker walks one loop instead of criss-crossing the floor. Each store arranges its own layout, 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. A category the layout does not place comes last. See business unit layout.

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.

Bagging up records one container type and where the bags are kept. The picker packs Anna's order into four paper bags: two of dry goods, one for the fridge and one for the freezer. The app's Labels and packaging sheet sends containerType PAPER_BAG with totalNumberOfContainers 4, and a count per storage area: DRY_GOODS 2, REFRIGERATOR 1, FREEZER 1. Whoever hands the order over then knows to fetch the freezer bag too.

Reading containers from your own system​

The containers of a customer order are read with GET /api/v1/business-units/{businessUnitId}/customer-orders/{customerOrderId}/containers, grouped by fulfillment. A tenant that keeps this in its own system can have that read answered there instead: store a configuration under the service key click-and-collect-location in the logistics proxy configuration.

  • Only this read is forwarded, to <your base URL>/business-units/{businessUnitId}/customer-orders/{customerOrderId}/containers, so include /api/v1 or whatever prefix your system needs in the base URL. Recording containers while picking is unchanged.
  • Without a configuration, without an address, or with one that cannot be read, Click and Collect answers from its own containers.
  • An error from your system is passed on: a 4xx as it is, a 5xx or an unreachable system as 502 Bad Gateway, and no answer within the time limit as 504 Gateway Timeout.

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