Commit Movement
POST/api/v2/inventory/movements/composer
Atomic commit of a composed movement: creates inventory adjustments, persists lot/serial detail, and triggers allocation release or demotion — all inside one DB transaction. When an outbound demotion consumes hard-allocated stock, each confirmed shipping label is voided in the same transaction (the fulfillment's inventory is reversed back to on-hand, its FulfillmentOrder budget reopened, and the shipment marked canceled).
Named route: v2.inventory.movements.composer
Request fields:
direction(required) — one of:receive,ship,adjust_up,adjust_downwarehouse_id(required) — must exist inwarehousesmovement_date(required) — ISO dateagainst_type(optional) —purchase_order,transfer, ornoneagainst_id(optional) — ID of the source doc whenagainst_typeis setreason(optional, max 1000 chars)lines[](required, min 1):product_id(required)quantity(required, non-zero — positive for inbound, negative for outbound)unit_cost(optional, >= 0)serials[](optional, each max 100 chars)lot(optional):lot_number,expires_at,manufactured_atoverride_reason(optional, max 500 chars)
void_confirmations[](required when preview-demotion returnedrequires_void_confirmation=true):fulfillment_id(required)attempt_void(required bool)reason(optional, max 500 chars)
Response shape (200):
{
"data": {
"adjustment_ids": [101, 102],
"voided_fulfillment_ids": [9001],
"skipped_fulfillments": []
},
"message": "Receipt committed — 1 line(s)."
}
voided_fulfillment_ids lists the fulfillments whose labels were voided. skipped_fulfillments lists any confirmed fulfillment that could not be voided (already voided, or part of a multi-shipment merge group) as { fulfillment_id, kind, reason } — these are skipped, not fatal.
Error (422): Returned when hard-allocated fulfillments are missing from void_confirmations or any other \InvalidArgumentException is thrown by the manager.
{ "error": "Hard-allocated fulfillment #9001 requires an explicit void confirmation before committing." }
release_backorders (boolean, inbound only, default true): when true, the received stock is applied to any waiting back-orders (promoting them to allocated); when false, the stock is received as free inventory and the back-orders keep waiting.
release_sales_order_ids (array of ints, inbound only, optional): granular release. Omit or null to release ALL waiting back-orders; pass a list of sales order ids to release only those orders' back-orders. Requires release_backorders = true.
Request
Responses
- 200
- 401
- 403
- 422
- 429
Successful response
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden — the token lacks a required scope, the endpoint is not available to API tokens, or the user behind the token lacks the permission. A human must adjust the token scopes or user permissions; do not retry.
Validation failed — the body is a field → messages map (Laravel shape) or the platform envelope with a stable machine-readable code. Fix the payload and resubmit.
Rate limited — platform limit is 1,000 requests/min; individual tokens may carry lower limits. Honor the Retry-After header before retrying. See the Rate Limits guide.