Skip to main content

The API

All endpoints are addressed by business unit:

/business-units/{businessUnitId}/store-transfers…

The business unit in the URL must be one of the two stores on the transfer. A request for a transfer that involves neither is refused β€” this is how a store is prevented from reading or changing another store's transfers.

πŸ›‘ Writes are asynchronous​

Every write returns 202 Accepted. The response means the instruction was accepted, not that it has been applied. The change lands a moment later.

Two consequences, and both bite in practice:

  1. Reading straight back can return the old state. If you need to confirm a write, poll the transfer until it shows what you expect rather than reading once.
  2. A write that depends on an earlier write can arrive too early. Creating a transfer and then immediately adding a line to it can fail, because the line write checks that the transfer exists and the create may not have landed yet.

The second one has a proper fix β€” use the composite create below rather than sequencing two calls.

Reads (GET) are ordinary synchronous calls and answer from stored state.

Raising a transfer​

Create the transfer and its lines in one call. This is the recommended way to raise a transfer:

POST /business-units/{businessUnitId}/store-transfers:with-lines
{
"fromBusinessUnitId": "store-42",
"toBusinessUnitId": "store-17",
"comment": "weekend top-up",
"lines": [
{ "itemId": "7350053850019", "quantity": 6 },
{ "itemId": "7350053850026", "quantity": 2 }
]
}

fromBusinessUnitId, toBusinessUnitId and at least one line are required, and each line needs itemId and quantity. lineId and comment are optional β€” omit lineId and one is minted.

⚠ The line field is quantity, not quantityRequested. The transfer returns quantityRequested and quantityApproved as two separate fields, but you write to them through the single quantity field, and which one it lands in depends on which store you are calling as β€” see below.

The whole draft is written together, so there is no window in which the transfer exists without its lines and no second call that can arrive too early.

A plain POST /business-units/{businessUnitId}/store-transfers without lines also exists, and is still the right call if you genuinely have nothing to add yet β€” a transfer being built up interactively, for instance. If you already know the lines, send them together.

Every endpoint​

Two groups, and the distinction matters more than it looks:

  • Used by the supplied flow β€” reading, building the draft, and :send-request. This is what the Hii Retail Inventory app calls, and what you would call to raise transfers from your own system.
  • Integration-only β€” writing approved quantities, line infos, and :submit. Supported, but nothing in the supplied flow calls them: a transfer picked in Click and Collect completes on its own. Reach for these only if you are replacing the picking app with your own system.

Reading​

EndpointAnswers
GET /business-units/{businessUnitId}/store-transfersall transfers this store is involved in, incoming and outgoing
GET /business-units/{businessUnitId}/store-transfers/{storeTransferId}one transfer, with its lines

Each transfer in the list carries a line count (itemCount) and a total unit count (totalUnits), so a dashboard does not have to fetch every transfer to show its size. Both endpoints also return the transfer's orderCode β€” the short human-readable code, e.g. S4K9M2X.

Query parameters on the list:

ParameterValuesDefault
statusCREATED, REQUESTED, IN-PROGRESS, COMPLETED, DELETED β€” repeatableCREATED and REQUESTED only
directionFROM β€” what this store is sending Β· TO β€” what it is receivingboth
limit0–100β€”
offset0 or moreβ€”

πŸ›‘ Omitting status does not mean "all statuses". It defaults to CREATED and REQUESTED β€” the transfers still needing attention. A caller that lists without status and concludes a transfer has vanished is looking at a filtered view: the transfer completed. Always pass status explicitly when you want history, listing every value you care about.

⚠ Drafts and deleted transfers are only ever visible to the receiving store, whichever direction is asked for.

The list is ordered newest first, by creation time. With limit set, older transfers fall off the end rather than being reachable by another filter β€” page with offset to reach them.

Building the draft​

EndpointDoes
POST …/store-transfers:with-linescreates a transfer together with its lines
POST …/store-transferscreates an empty transfer
PUT …/store-transfers/{storeTransferId}changes the transfer's comment
PUT …/store-transfers/{storeTransferId}/linesadds a line, or changes one that is already there
DELETE …/store-transfers/{storeTransferId}/lines/{lineId}removes a line
DELETE …/store-transfers/{storeTransferId}abandons the draft

Removing a line, and abandoning the draft, can only be done by the receiving store. See Lifecycle for the full matrix.

The line endpoint means two different things​

PUT …/store-transfers/{storeTransferId}/lines takes one quantity field:

{ "itemId": "7350053850019", "quantity": 4, "comment": "only four on the shelf" }

πŸ›‘ Which stored field that quantity lands in depends on which business unit you call as.

Calling asquantity setsRules
the receiving store (toBusinessUnitId)quantityRequestedmust be at least 1
the sending store (fromBusinessUnitId)quantityApprovedmay be 0 β€” that is how a line is refused

The first row is the ordinary case: building the request.

The second row is integration-only. A store never writes approved quantities this way β€” when a transfer is picked in Click and Collect, the pick sets them. It is there for a customer who has replaced the picking app with their own system and is deciding what ships themselves; see Completing from your own system.

⚠ Calling as the wrong business unit does not fail. The request succeeds and the number lands in the other field β€” an intended approval turns up as a changed request, and nothing reports it.

Omit lineId to add a line; pass an existing one to change it.

Batch, serial and expiry detail β€” integration-only​

A line can carry one or more line infos β€” the specific units being sent, when that matters:

EndpointDoes
PUT …/store-transfers/{storeTransferId}/lines/{lineId}/lineInfoadds or changes a line info
DELETE …/store-transfers/{storeTransferId}/lines/{lineId}/line-info/{lineInfoId}removes one

Each line info carries a quantity plus whichever of these apply: batch number, serial number, best before date, expiry date. When they are present the detail travels on the completed event, so the receiving store knows exactly which batches arrived.

⚠ Nothing in the supplied flow writes these. A transfer raised and picked through the Hii Retail apps carries no line infos, and lineInfos arrives as an empty array on the completed event. They exist for an integrator who tracks goods at batch or serial level and is writing the transfer from their own system. Do not treat an empty lineInfos as missing data β€” treat quantityApproved on the line as the quantity that shipped.

Moving it along​

EndpointDoes
POST …/store-transfers/{storeTransferId}:send-requestsends the request to the other store
POST …/store-transfers/{storeTransferId}:submitcompletes the transfer

:send-request requires the transfer to be a draft with at least one line β€” an empty transfer cannot be sent.

:submit requires the caller to be the sending store, and is integration-only β€” a transfer picked in Click and Collect completes on its own. See Lifecycle for the full set of guards.

Permissions​

Each endpoint requires its own permission, so a role can be given the ability to build a transfer without the ability to send or complete one:

GroupPermissions
Readingstr.transfer.get, str.transfer-line.get
Buildingstr.transfer.create, str.transfer.update, str.transfer.delete, str.transfer-line.create, str.transfer-line.delete, str.line-info.delete
Progressingstr.transfer.send-request, str.transfer.submit

The composite create requires both str.transfer.create and str.transfer-line.create, since it does both jobs.

Tracing a request​

Pass a Correlation-Id header and it is carried through every downstream step β€” the write, the events, and the stock movements that follow. It is returned on the write responses. If you do not send one, one is generated.

Reference​

The OpenAPI specification is the definitive source for request and response shapes: Store Transfer API.