Receive Shipment
POST/api/inbound-shipments/receive
Receive goods against a purchase order shipment. Creates a receipt record and triggers inventory movements.
purchase-orders:writeGrant this scope to your token under Settings → Developer → Personal Access Tokens.
Authentication: Requires Bearer token.
Async behavior:
- By default, this endpoint dispatches the receive to a background job chain and returns 202 Accepted with
data.tracked_job_log_id. The frontend should open the tracked job log endpoints (notifyJobStarted) and watch lastCompletedJob to refresh. - Pass
?async=0to force the legacy synchronous response (200 with the receipt resource). - The async path is only taken when the payload is ad-hoc eligible: requires
purchase_order_id, and rejects any payload that includesunexpected_itemsor negativereceipt_lines[].quantity. An explicitinbound_shipment_idorforce_new_shipmentnow takes the async path as well — the receipt is landed on the chosen shipment. Ineligible payloads silently fall back to the sync path. - 409 Conflict is returned when a background job is already in flight (PENDING or PROCESSING) for the same
purchase_order_id— the caller should tell the user to track progress via the tracked job log endpoints instead of retrying.
Fields:
- purchase_order_id (conditional): Required if purchase_order_shipment_id is not provided. Required for the async path.
- purchase_order_shipment_id (optional): Specific shipment ID to receive. If omitted, receives for the entire PO
- received_at (required): Date/time of receipt
- warehouse_id (optional): Override destination warehouse
- shipment_date (optional): Used if a new shipment needs to be created automatically
- shipping_method_id (optional): Shipping method for auto-created shipment
- tracking (optional): Tracking number for auto-created shipment, max 255 characters
- inbound_shipment_id (optional, exists:inbound_shipments,id): Route the whole receive against this specific inbound shipment. Restricts delegation to that shipment and over-receives it past its planned capacity if the received quantity exceeds what was expected. Routes the whole receive against this shipment; handled on the async path.
- force_new_shipment (optional, boolean): If true, skip auto-routing/delegation to existing planned shipments and create a brand-new ad-hoc inbound shipment for this receipt. Mutually exclusive in intent with inbound_shipment_id
- receipt_lines (optional): Array of specific line quantities to receive. If omitted, receives all unreceived lines
- receipt_lines[].purchase_order_shipment_line_id (conditional): Shipment line ID. Required without purchase_order_line_reference or purchase_order_line_id
- receipt_lines[].purchase_order_line_id (conditional): PO line ID. Alternative to shipment_line_id
- receipt_lines[].purchase_order_line_reference (conditional): PO line reference code. Alternative to IDs
- receipt_lines[].quantity (required): Quantity received. Negative quantities force sync fallback (the reversal/adjustment path).
- unexpected_items (optional): Items received that were not on the original PO. Presence forces sync fallback.
- unexpected_items[].product_id (conditional): Product ID. Required without product_sku
- unexpected_items[].product_sku (conditional): Product SKU. Required without product_id
- unexpected_items[].quantity (required): Quantity of unexpected item
- unexpected_items[].action (optional): Action for unexpected item (e.g., add_to_inventory)
- unexpected_items[].amount (optional): Unit cost for unexpected item
- unexpected_items[].description (optional): Description for unexpected item
lots (synchronous responses only). A 200 response reports the lots this receipt created, each with its fifo_layer_id and batch_number. Use it to attach a Certificate of Analysis or other batch paperwork to the lot after the receipt commits, via POST /api/v2/fifo-layers/{fifoLayer}/documents. The default asynchronous path answers 202 with a tracked job and no lots, so request ?async=0 when you intend to file documents against what you receive. A line received without a batch number still produces a lot, but its batch is generated server-side (AUTO-…).
rechain_affected(optional bool, default false) — when the entry is dated earlier than existing consumption in an affected product/warehouse pool, opt in to re-anchor those later transactions to the oldest available stock (true FIFO) after the save commits. Runs as a background job; pools in a locked accounting period or with unsupported shapes are skipped and reported.
Serial numbers: for a serial-tracked product, a line may carry serials (one per unit received). An empty list is accepted. A non-empty list must match the line quantity (in Auto generation mode it may be shorter, and the rest are generated), must not repeat a serial, and must not name a serial that already exists. Otherwise the request returns 422 with a message naming the serial.
Requires permission: purchase_orders.receive
Request
Responses
- 200
- 202
- 400
- 401
- 403
- 409
- 422
- 429
OK
Response Headers
Accepted
Response Headers
Bad Request
Response Headers
Unauthenticated — the bearer token is missing, revoked, expired, or malformed. Never retry automatically; fix the credential. See the Errors guide.
Forbidden
Response Headers
Conflict
Response Headers
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.