In short
qc_status is AVAILABLE, or RELEASED with a qc_release_expires_at that is null or still ahead.lot_number is null is the generic lot: real stock, not a data error.archived=false for live stock.updated_since is strictly greater than, follows the parent row only and never reports deletions. Overlap each poll by a minute.A lot is a quantity of one product, in one location, that arrived or was made together. Everything about stock on this API is expressed in lots.
quantity_base is in the product's base unit, on every operation, in both directions. To show a lot in the product's inventory unit, divide by both factors on the lot: quantity_base / (uom_qty * uom_base_conversion). See Units and quantities.
qc_status has five values: AVAILABLE, RELEASED, ON_HOLD, QUARANTINE and REJECTED. Two of them are sellable: AVAILABLE and RELEASED.
ON_HOLD and moves to RELEASED, never back to AVAILABLE, so filtering on AVAILABLE alone misses every positively released finished good.| Status | Means |
|---|---|
AVAILABLE | In stock, and no release decision was needed. Sellable. |
RELEASED | Signed off by quality. Sellable until qc_release_expires_at, if set. |
ON_HOLD | Made or received, not yet released. Yours, not sellable. |
QUARANTINE | Held pending investigation. Yours, not sellable. |
REJECTED | Failed. Not sellable. |
A release can carry a retest date, published as qc_release_expires_at. Only RELEASED lots have one, and it is null when the release is open-ended. Once it has passed, NutraSoft will not ship the lot or use it in production until quality retests and releases it again. The sellable rule, in full: qc_status is AVAILABLE, or it is RELEASED and qc_release_expires_at is null or still in the future.
A lot whose lot_number is null is the generic lot: the unnumbered bucket created with a product to hold stock that was never lotted. It is a real row with a real quantity. Do not filter it out, and do not treat a null lot number as a data error.
A lot's location is location_id: a warehouse id or a location inside one. When you create a lot, the warehouse id you send is location_id. To list the stock of a whole warehouse, filter GET /product-lots with warehouse_id.
A lot that is used up, spoiled or written off is archived: archived becomes true and the row stays readable with its history. Without an archived filter, the list returns both, so pass archived=false for live stock and archived=true to audit what was retired. A lot can also be deleted outright, when someone removes it from a product in NutraSoft; a deleted lot simply stops appearing.
Seven resources accept updated_since: products, product lots, sales orders, invoices, purchase orders, customers and suppliers. Each publishes created_at and updated_at and can be sorted by them. Four rules are not guessable, so read them before you build a sync:
updated_at equals the value you pass is not returned again.updated_at. If you care about lines, re-fetch the documents you hold on a slower cycle.updated_at is slightly earlier than rows you have already seen. Overlap each poll by a minute and de-duplicate by id.updated_at, so an archived lot shows up in the feed. A deleted row, a lot included, simply stops appearing, and polling will never tell you about it.sort=updated_at:asc.updated_at you saw.updated_since set to that value minus a minute, sorted the same way, and upsert by id.Offset paging stops at an offset of 10,000. For a large first run, sort by updated_at and move updated_since forward to the last updated_at of each page, minus the same overlap, instead of paging deeper.
updated_since cannot report. Most integrations run both: webhooks to react, a periodic sweep to reconcile.