Lot tracking through the PickFlow pick-and-pack API

When a product has Lot Tracking enabled in Luminous, the PickFlow integration now captures, validates, and surfaces the lot at every stage of the pick-and-pack workflow.


Step 1 — Read the lot assigned to each picklist line

When you fetch a picklist, each item in picklist_items now includes:

FieldDescription
lot_idLuminous lot identifier (integer)
lot_numberHuman-readable lot number printed on the label
production_dateManufacture date (YYYY-MM-DD)
expiry_dateExpiry date (YYYY-MM-DD)

Use lot_number to help the picker match the physical label on the shelf.


Step 2 — Complete picking with a lot

POST /external/api/v1/pickflow/picklists/{id}/complete

Each item in the items array must now include:

{
  "picklist_item_id": 456,
  "rfq_id": 789,
  "location_id": 12,
  "picked_quantity": 3,
  "lot_id": 101
}
  • lot_id is required for lot-tracked products and optional for non-lot-tracked products.
  • The system validates that the lot belongs to the product on that line and that sufficient on-hand quantity exists in the specified bin.

Validation errors come back as HTTP 422 with field-keyed messages:

{
  "message": "Cannot complete picklist 99: one or more picked lines are invalid.",
  "errors": {
    "items.0.lot_id": ["Lot 101 only has 2 on hand in location 12; 3 was requested."]
  }
}

Four conditions are refused with 422 (not 500):

  1. Product or bin does not match the picklist line.
  2. Lot-tracked product submitted with no lot_id.
  3. lot_id belongs to a different product.
  4. The lot does not have enough on-hand in the claimed bin.

Retry safety: If the network drops after the server commits, re-submitting the same complete-picking call returns success with already_completed: true and does not move stock a second time.


Step 3 — Read staged lots from the WIP bin (before packing)

After pick-complete, use the new endpoint to see which lots are physically in the tote:

GET /external/api/v1/pickflow/picklists/{id}/wip-lots

Example response:

{
  "data": [
    {
      "picklist_id": 99,
      "location_id": 55,
      "rfq_id": 789,
      "sku": "REN0007",
      "lot_tracking": true,
      "lot_id": 101,
      "lot_number": "LUM-2026-09",
      "manufacturer_code": "MFG-A",
      "production_date": "2026-01-15",
      "expiry_date": "2027-01-15",
      "qty_onhand": 3.0
    }
  ]
}

Rows with zero or negative on-hand are excluded so only pickable stock is listed. Non-lot-tracked stock in the same bin is also included (with lot_id: null).


Step 4 — Write a lot when buying a shipping label

Lot allocation for a package isn't a separate "create package" call — it happens on the label-buy endpoint: POST /pickflow/fulfillment-orders/{fulfillmentOrderId}/label. Each entry in the items array accepts:

FieldDescription
lot_idThe exact lot row (integer FK). Validated against the line's product.
{
"items": [{
"order_item_id": 456,
"product_id": 789,
"quantity": 3,
"lot_id": 101
}]
}

If lot_id belongs to a different product than the line item, the request is refused with 422.

Step 5 — Write a lot when buying a shipping label

Both the shipment-item and package-item responses now includes lot fields:

FieldNotes
Lot IdInteger or null
Lot numberFalls back to the lot record if only lot Id was stored
expiry dateYYYY-MM-DD format

This allows downstream traceability product than the line item, the request is refused with 422.

Tips

  • Always pass lot_id (not just lot_number) when completing picking; lot_id is unambiguous and avoids lookup failures.
  • Poll the WIP-lots endpoint after pick-complete to pre-populate the lot field on the pack screen, rather than asking the operator to re-enter it.
  • A 422 on complete-picking means the payload needs correction; a 500 indicates a system error — handle these separately in your error flow.