Transfers move stock from an origin location to a destination location. All endpoints for this resource require the inventory:transfer permission.
Read the write semantics section before integrating
The exact moment stock moves, and the guarantees (or lack thereof) about a transfer's state, are the most important detail of this resource. They are described below with the real production behavior — this is not the ideal design, it is what the system does today.
POST /inventory/transfersdoes not move stock. It only creates the record with status: 'pending'. Stock moves exclusively when the transfer is received (see below).
This is the only thing that moves stock in a transfer's lifecycle. For each item received:
The origin stock is decremented by reassignAmount, with the result floored at a minimum of 0 — if the amount exceeds the stock available at origin, the subtraction saturates at 0 instead of rejecting the operation. No visible error is produced for this over-send.
The destination stock is incremented by the same reassignAmount.
Internally, the system attempts to consume/create FIFO lots to maintain cost traceability; if that part fails, the receive does not fail — the lot error is logged as a warning and the request still responds 200.
Receiving does NOT emit the 'Inventory Updated' event
Unlike a direct stock write (PUT, bulk-update, adjustments), receiving a transfer moves stock through a different path that does not trigger the Inventory Updated event nor the low-stock check. If your integration listens for that event to react to stock changes, it will not see the movements caused by received transfers.
No state-transition guard — a transfer can be received twice
There is no check that the transfer is in the correct state before approving, receiving, or cancelling it. This means, in current production behavior:
You can call receive on a transfer that is already received — the stock movement gets reapplied, duplicating the effect.
You can cancel a transfer that was already received.
The receive operation is not idempotent: resending the same request reapplies the stock delta every time, because there is no deduplication key.
Your integration is responsible for not blindly retrying receive and for tracking the state locally before calling it again.
Cancelling does not revert any stock movement already applied by a previous receive — it only changes the status field. If you already received the transfer and then cancel it, the moved stock stays moved.