Stock in base units: apply signed adjustments with traceability, or update an absolute balance without a movement. Retired lots are archived.
idintegerarchivedbooleanArchived lots are retired but stay readable with their history.
created_atstringnullableexpiration_datestringnullableleasestringThe state of the lot as read. It changes whenever the lot number, location, quantity, expiration date or archived state does. Send it back as lease on PATCH /product-lots/{lot_id} or POST /product-lots/{lot_id}/adjustments to have the update refused with STALE_LEASE if the lot changed since. Opaque: send it as received.
location_codestringnullablelocation_idintegernullablelocation_namestringnullablelot_numberstringnullableNull on the product's unnumbered lot, which holds stock that was never lotted.
lot_originstringnullableHow the lot came into existence.
RECEPTIONINVOICEPRODUCTIONMANUAL_ADJUSTMENTPACKAGINGSHIPPINGINVENTORY_COUNTPRODUCT_FORMSHOPIFYproduct_codestringnullableproduct_idintegernullableproduct_namestringnullableqc_release_expires_atstringnullableWhen a RELEASED status stops counting. Null means it does not expire.
qc_statusstringnullableTwo statuses are sellable: AVAILABLE and RELEASED. A production output under positive release moves from ON_HOLD to RELEASED, never to AVAILABLE.
AVAILABLEON_HOLDQUARANTINERELEASEDREJECTEDqc_status_atstringnullableqc_status_notestringnullablequantity_basenumbernullableOn hand, in the product's base unit.
unit_of_measure_idintegernullableuom_base_conversionnumbernullableSecond conversion factor. See uom_qty.
uom_base_idintegernullableuom_qtynumbernullableFirst conversion factor. One inventory unit is uom_qty * uom_base_conversion base units.
updated_atstringnullable/product-lotsinventory:readStock, lot by lot, in each product's base unit. Two quality statuses are sellable: AVAILABLE and RELEASED.
product_idintegernullableproduct_codestringnullableExact match, ignoring case. Sent with product_id, both must match the same product.
lot_numberstringnullablePartial match.
location_idintegernullablewarehouse_idintegernullableLots in this warehouse, plus lots with no location.
archivedbooleannullableOmit for both.
searchstringnullableFree-text match on the lot number.
sortstringnullableComma-separated field:direction pairs, e.g. id:desc. Sortable: created_at, expiration_date, id, lot_number, product_name, quantity_base, updated_at. id is always added last, so paging is stable.
limitintegerRows to return. Maximum 200.
offsetintegerRows to skip. Maximum 10000: to read further, narrow the filters or page with updated_since.
updated_sincestringnullableOnly rows changed after this instant, as an ISO 8601 timestamp; without an offset it is read as UTC. Tracks the record itself, not its child rows, and deletions are not reported, so reconcile by re-listing from time to time.
data, with pagination.curl "https://public-api.nutrasoft.ca/v1/product-lots?limit=50" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY"{
"data": [
{
"id": 123,
"archived": false,
"created_at": "2026-09-10T14:30:00Z",
"expiration_date": "2026-09-10T14:30:00Z",
"lease": "string",
"location_code": "SAMPLE-01",
"location_id": 123,
"location_name": "Sample name",
"lot_number": "LOT-2026-0142",
"lot_origin": "RECEPTION",
"product_code": "SAMPLE-01",
"product_id": 123,
"product_name": "Sample name",
"qc_release_expires_at": "2026-09-10T14:30:00Z",
"qc_status": "AVAILABLE",
"qc_status_at": "2026-09-10T14:30:00Z",
"qc_status_note": "string",
"quantity_base": 12.5,
"unit_of_measure_id": 123,
"uom_base_conversion": 12.5,
"uom_base_id": 123,
"uom_qty": 12.5,
"updated_at": "2026-09-10T14:30:00Z"
}
],
"pagination": {
"limit": 50,
"offset": 0,
"total": 1,
"has_more": false
},
"order": "id:asc"
}/product-lots/{lot_id}inventory:readlot_idintegerrequiredThe lot's id.
data.curl "https://public-api.nutrasoft.ca/v1/product-lots/123" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY"{
"data": {
"id": 123,
"archived": false,
"created_at": "2026-09-10T14:30:00Z",
"expiration_date": "2026-09-10T14:30:00Z",
"lease": "string",
"location_code": "SAMPLE-01",
"location_id": 123,
"location_name": "Sample name",
"lot_number": "LOT-2026-0142",
"lot_origin": "RECEPTION",
"product_code": "SAMPLE-01",
"product_id": 123,
"product_name": "Sample name",
"qc_release_expires_at": "2026-09-10T14:30:00Z",
"qc_status": "AVAILABLE",
"qc_status_at": "2026-09-10T14:30:00Z",
"qc_status_note": "string",
"quantity_base": 12.5,
"unit_of_measure_id": 123,
"uom_base_conversion": 12.5,
"uom_base_id": 123,
"uom_qty": 12.5,
"updated_at": "2026-09-10T14:30:00Z"
}
}/product-lotsinventory:write· idempotent with Idempotency-KeyPuts stock on the books with the PRODUCT_FORM stock movement that explains it, in one transaction. quantity_base is stored as sent, in the product's base unit. A second lot with the same number at the same location is refused with 409.
Idempotency-KeystringYour own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.
product_idintegerrequiredThe product this lot is stock of. Must be one of yours.
expiration_datestringnullableISO 8601. Null for stock that does not expire.
location_idintegernullableWhere the stock sits: one of your warehouses or a location in one.
lot_numberstringnullableUnique per product and location. Omit it only for the product's unnumbered lot, of which there is at most one.
quantity_basenumberOn hand, in the product's base unit. Stored as sent.
data.curl -X POST "https://public-api.nutrasoft.ca/v1/product-lots" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c6a52-8d0e-4b7a-9c2f-5e8d1b4a7c90" \
-d '{
"product_id": 123
}'{
"data": {
"id": 123,
"archived": false,
"created_at": "2026-09-10T14:30:00Z",
"expiration_date": "2026-09-10T14:30:00Z",
"lease": "string",
"location_code": "SAMPLE-01",
"location_id": 123,
"location_name": "Sample name",
"lot_number": "LOT-2026-0142",
"lot_origin": "RECEPTION",
"product_code": "SAMPLE-01",
"product_id": 123,
"product_name": "Sample name",
"qc_release_expires_at": "2026-09-10T14:30:00Z",
"qc_status": "AVAILABLE",
"qc_status_at": "2026-09-10T14:30:00Z",
"qc_status_note": "string",
"quantity_base": 12.5,
"unit_of_measure_id": 123,
"uom_base_conversion": 12.5,
"uom_base_id": 123,
"uom_qty": 12.5,
"updated_at": "2026-09-10T14:30:00Z"
}
}/product-lots/{lot_id}inventory:write· idempotent with Idempotency-KeyOmitted fields are left unchanged. quantity_base sets the absolute quantity on hand: the lot is locked, and the difference from the current balance is recorded as an ADJUSTMENT stock movement with its accounting entries, exactly as POST /product-lots/{lot_id}/adjustments records it. For a balance of 100, 25 records -75. Sending the current balance records nothing. A quantity change on an archived lot returns 409, and one the ledger cannot post is refused with nothing written. The lot number and location are not writable. Send the lease from your last read to have the update refused with 409 STALE_LEASE if the lot changed since. Archiving has its own operations, and the quality status is not writable.
lot_idintegerrequiredThe lot's id.
Idempotency-KeystringYour own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.
expiration_datestringnullableleasestringnullableThe lease from your last read of this lot. When sent, the update is refused with 409 STALE_LEASE and nothing is written if the lot has changed since that read, so a quantity computed from a stale read never overwrites stock that moved in between. Omit it to write unconditionally.
quantity_basenumbernullableThe new quantity on hand, in the product's base unit, rounded to 6 decimal places. The difference from the current balance is recorded in lot traceability as an ADJUSTMENT stock movement, with its accounting entries. To send a change instead of a balance, use POST /product-lots/{lot_id}/adjustments. Cannot be null.
data.curl -X PATCH "https://public-api.nutrasoft.ca/v1/product-lots/123" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c6a52-8d0e-4b7a-9c2f-5e8d1b4a7c90" \
-d '{
"expiration_date": "2026-09-10T14:30:00Z"
}'{
"data": {
"id": 123,
"archived": false,
"created_at": "2026-09-10T14:30:00Z",
"expiration_date": "2026-09-10T14:30:00Z",
"lease": "string",
"location_code": "SAMPLE-01",
"location_id": 123,
"location_name": "Sample name",
"lot_number": "LOT-2026-0142",
"lot_origin": "RECEPTION",
"product_code": "SAMPLE-01",
"product_id": 123,
"product_name": "Sample name",
"qc_release_expires_at": "2026-09-10T14:30:00Z",
"qc_status": "AVAILABLE",
"qc_status_at": "2026-09-10T14:30:00Z",
"qc_status_note": "string",
"quantity_base": 12.5,
"unit_of_measure_id": 123,
"uom_base_conversion": 12.5,
"uom_base_id": 123,
"uom_qty": 12.5,
"updated_at": "2026-09-10T14:30:00Z"
}
}/product-lots/{lot_id}/adjustmentsinventory:write· idempotent with Idempotency-KeyAdds signed base_qty to the lot's current quantity in its product's base unit. Positive adds stock; negative removes it. For a balance of 100, 25 makes 125 and -25 makes 75. The lot, an ADJUSTMENT stock movement with your notes and applicable accounting entries are saved atomically. The movement appears in lot traceability as Created via API. Returns the updated lot. Unlike PATCH /product-lots/{lot_id}, this records a stock movement and applies a change rather than replacing the balance.
The lot is locked while applying the change. Zero changes are refused with 400; archived lots or insufficient stock return 409. An optional lease refuses a changed lot with 409 STALE_LEASE. Send an Idempotency-Key and reuse it on retries to avoid applying the same adjustment twice. Linked Shopify stock and enabled QuickBooks adjustment entries are synced after commit; a failed sync does not undo the adjustment.
lot_idintegerrequiredThe lot's id.
Idempotency-KeystringYour own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.
base_qtynumberrequiredSigned change in the product's base unit, rounded to 6 decimal places. Positive adds stock; negative removes stock. Must be nonzero after rounding. This is a change, not the new quantity on hand.
leasestringnullableOptional lease from your last read. A changed lot returns 409 STALE_LEASE.
notesstringnullableReason for the adjustment, saved in lot traceability.
data.curl -X POST "https://public-api.nutrasoft.ca/v1/product-lots/123/adjustments" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c6a52-8d0e-4b7a-9c2f-5e8d1b4a7c90" \
-d '{
"base_qty": 25
}'{
"data": {
"id": 123,
"archived": false,
"created_at": "2026-09-10T14:30:00Z",
"expiration_date": "2026-09-10T14:30:00Z",
"lease": "string",
"location_code": "SAMPLE-01",
"location_id": 123,
"location_name": "Sample name",
"lot_number": "LOT-2026-0142",
"lot_origin": "RECEPTION",
"product_code": "SAMPLE-01",
"product_id": 123,
"product_name": "Sample name",
"qc_release_expires_at": "2026-09-10T14:30:00Z",
"qc_status": "AVAILABLE",
"qc_status_at": "2026-09-10T14:30:00Z",
"qc_status_note": "string",
"quantity_base": 12.5,
"unit_of_measure_id": 123,
"uom_base_conversion": 12.5,
"uom_base_id": 123,
"uom_qty": 12.5,
"updated_at": "2026-09-10T14:30:00Z"
}
}/product-lots/{lot_id}/archiveinventory:write· idempotent with Idempotency-KeyArchiving is how a lot is retired: lots are never deleted. Refused with 409 for a lot already archived and for a product's unnumbered lot.
lot_idintegerrequiredThe lot's id.
Idempotency-KeystringYour own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.
data.curl -X POST "https://public-api.nutrasoft.ca/v1/product-lots/123/archive" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY" \
-H "Idempotency-Key: 3f1c6a52-8d0e-4b7a-9c2f-5e8d1b4a7c90"{
"data": {
"id": 123,
"archived": false,
"created_at": "2026-09-10T14:30:00Z",
"expiration_date": "2026-09-10T14:30:00Z",
"lease": "string",
"location_code": "SAMPLE-01",
"location_id": 123,
"location_name": "Sample name",
"lot_number": "LOT-2026-0142",
"lot_origin": "RECEPTION",
"product_code": "SAMPLE-01",
"product_id": 123,
"product_name": "Sample name",
"qc_release_expires_at": "2026-09-10T14:30:00Z",
"qc_status": "AVAILABLE",
"qc_status_at": "2026-09-10T14:30:00Z",
"qc_status_note": "string",
"quantity_base": 12.5,
"unit_of_measure_id": 123,
"uom_base_conversion": 12.5,
"uom_base_id": 123,
"uom_qty": 12.5,
"updated_at": "2026-09-10T14:30:00Z"
}
}/product-lots/{lot_id}/unarchiveinventory:write· idempotent with Idempotency-KeyReturns an archived lot to active stock. Refused with 409 for a lot already active and for a product's unnumbered lot.
lot_idintegerrequiredThe lot's id.
Idempotency-KeystringYour own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.
data.curl -X POST "https://public-api.nutrasoft.ca/v1/product-lots/123/unarchive" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY" \
-H "Idempotency-Key: 3f1c6a52-8d0e-4b7a-9c2f-5e8d1b4a7c90"{
"data": {
"id": 123,
"archived": false,
"created_at": "2026-09-10T14:30:00Z",
"expiration_date": "2026-09-10T14:30:00Z",
"lease": "string",
"location_code": "SAMPLE-01",
"location_id": 123,
"location_name": "Sample name",
"lot_number": "LOT-2026-0142",
"lot_origin": "RECEPTION",
"product_code": "SAMPLE-01",
"product_id": 123,
"product_name": "Sample name",
"qc_release_expires_at": "2026-09-10T14:30:00Z",
"qc_status": "AVAILABLE",
"qc_status_at": "2026-09-10T14:30:00Z",
"qc_status_note": "string",
"quantity_base": 12.5,
"unit_of_measure_id": 123,
"uom_base_conversion": 12.5,
"uom_base_id": 123,
"uom_qty": 12.5,
"updated_at": "2026-09-10T14:30:00Z"
}
}