NutraSoft food manufacturing ERP software
Esc
  • GuideGetting startedThree steps to your first call and your first order, then the two shapes every response takes.
  • GuideAuthentication and keysBearer keys, test and live environments, rotation without an outage, and what each scope grants.
  • GuideTest modeA second NutraSoft account of your own, seeded with realistic data, that you can safely break.
  • PageErrorsEvery error code the API returns, what it means, what to do about it and whether to retry.
  • PageConventionsThe rules every operation follows: money, time, paging, sorting, nulls, PATCH and identifiers.
Menu

Product lots

Stock in base units: apply signed adjustments with traceability, or update an absolute balance without a movement. Retired lots are archived.

inventory:readinventory:writehttps://public-api.nutrasoft.ca/v1

The Product lot object

  • idinteger
  • archivedboolean

    Archived lots are retired but stay readable with their history.

  • created_atstringnullable
    • UTC timestamp
  • expiration_datestringnullable
    • UTC timestamp
  • leasestring

    The 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_codestringnullable
  • location_idintegernullable
  • location_namestringnullable
  • lot_numberstringnullable

    Null on the product's unnumbered lot, which holds stock that was never lotted.

  • lot_originstringnullable

    How the lot came into existence.

    • one ofRECEPTIONINVOICEPRODUCTIONMANUAL_ADJUSTMENTPACKAGINGSHIPPINGINVENTORY_COUNTPRODUCT_FORMSHOPIFY
Show 13 more attributes
  • product_codestringnullable
  • product_idintegernullable
  • product_namestringnullable
  • qc_release_expires_atstringnullable

    When a RELEASED status stops counting. Null means it does not expire.

    • UTC timestamp
  • qc_statusstringnullable

    Two statuses are sellable: AVAILABLE and RELEASED. A production output under positive release moves from ON_HOLD to RELEASED, never to AVAILABLE.

    • one ofAVAILABLEON_HOLDQUARANTINERELEASEDREJECTED
  • qc_status_atstringnullable
    • UTC timestamp
  • qc_status_notestringnullable
  • quantity_basenumbernullable

    On hand, in the product's base unit.

  • unit_of_measure_idintegernullable
  • uom_base_conversionnumbernullable

    Second conversion factor. See uom_qty.

  • uom_base_idintegernullable
  • uom_qtynumbernullable

    First conversion factor. One inventory unit is uom_qty * uom_base_conversion base units.

  • updated_atstringnullable
    • UTC timestamp

Operations

List product lots

get/product-lotsinventory:read

Stock, lot by lot, in each product's base unit. Two quality statuses are sellable: AVAILABLE and RELEASED.

Query parameters

  • product_idintegernullable
    • > 0
  • product_codestringnullable

    Exact match, ignoring case. Sent with product_id, both must match the same product.

  • lot_numberstringnullable

    Partial match.

  • location_idintegernullable
    • > 0
  • warehouse_idintegernullable

    Lots in this warehouse, plus lots with no location.

    • > 0
  • archivedbooleannullable

    Omit for both.

  • searchstringnullable

    Free-text match on the lot number.

  • sortstringnullable

    Comma-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.

  • limitinteger

    Rows to return. Maximum 200.

    • >= 1
    • <= 200
    • default 50
  • offsetinteger

    Rows to skip. Maximum 10000: to read further, narrow the filters or page with updated_since.

    • >= 0
    • <= 10000
    • default 0
  • updated_sincestringnullable

    Only 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.

    • UTC timestamp
Returns
200 A page of ProductLot objects in data, with pagination.
Errors
400 401 403 429 500 503
GET /product-lots
curl "https://public-api.nutrasoft.ca/v1/product-lots?limit=50" \
  -H "Authorization: Bearer $NUTRASOFT_API_KEY"
Response200
{
  "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"
}

Retrieve a product lot

get/product-lots/{lot_id}inventory:read

Path parameters

  • lot_idintegerrequired

    The lot's id.

    • > 0
Returns
200 One ProductLot object in data.
Errors
400 401 403 404 429 500 503
GET /product-lots/{lot_id}
curl "https://public-api.nutrasoft.ca/v1/product-lots/123" \
  -H "Authorization: Bearer $NUTRASOFT_API_KEY"
Response200
{
  "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"
  }
}

Create a product lot

post/product-lotsinventory:write· idempotent with Idempotency-Key

Puts 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.

Headers

  • Idempotency-Keystring

    Your own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.

    • max 200 chars

Request body

  • product_idintegerrequired

    The product this lot is stock of. Must be one of yours.

    • > 0
  • expiration_datestringnullable

    ISO 8601. Null for stock that does not expire.

    • UTC timestamp
  • location_idintegernullable

    Where the stock sits: one of your warehouses or a location in one.

    • > 0
  • lot_numberstringnullable

    Unique per product and location. Omit it only for the product's unnumbered lot, of which there is at most one.

    • max 100 chars
  • quantity_basenumber

    On hand, in the product's base unit. Stored as sent.

    • >= 0
    • default 0
Returns
201 One ProductLot object in data.
Errors
400 401 403 409 429 500 503
POST /product-lots
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
}'
Response201
{
  "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"
  }
}

Update a product lot

patch/product-lots/{lot_id}inventory:write· idempotent with Idempotency-Key

Omitted 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.

Path parameters

  • lot_idintegerrequired

    The lot's id.

    • > 0

Headers

  • Idempotency-Keystring

    Your own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.

    • max 200 chars

Request body

  • expiration_datestringnullable
    • UTC timestamp
  • leasestringnullable

    The 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.

    • max 64 chars
  • quantity_basenumbernullable

    The 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.

    • >= 0
Returns
200 One ProductLot object in data.
Errors
400 401 403 404 409 429 500 503
PATCH /product-lots/{lot_id}
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"
}'
Response200
{
  "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"
  }
}

Adjust a product lot

post/product-lots/{lot_id}/adjustmentsinventory:write· idempotent with Idempotency-Key

Adds 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.

Path parameters

  • lot_idintegerrequired

    The lot's id.

    • > 0

Headers

  • Idempotency-Keystring

    Your own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.

    • max 200 chars

Request body

  • base_qtynumberrequired

    Signed 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.

  • leasestringnullable

    Optional lease from your last read. A changed lot returns 409 STALE_LEASE.

    • max 64 chars
  • notesstringnullable

    Reason for the adjustment, saved in lot traceability.

    • max 2000 chars
Returns
201 One ProductLot object in data.
Errors
400 401 403 404 409 429 500 503
POST /product-lots/{lot_id}/adjustments
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
}'
Response201
{
  "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"
  }
}

Archive a product lot

post/product-lots/{lot_id}/archiveinventory:write· idempotent with Idempotency-Key

Archiving 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.

Path parameters

  • lot_idintegerrequired

    The lot's id.

    • > 0

Headers

  • Idempotency-Keystring

    Your own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.

    • max 200 chars
Returns
200 One ProductLot object in data.
Errors
400 401 403 404 409 429 500 503
POST /product-lots/{lot_id}/archive
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"
Response200
{
  "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"
  }
}

Unarchive a product lot

post/product-lots/{lot_id}/unarchiveinventory:write· idempotent with Idempotency-Key

Returns an archived lot to active stock. Refused with 409 for a lot already active and for a product's unnumbered lot.

Path parameters

  • lot_idintegerrequired

    The lot's id.

    • > 0

Headers

  • Idempotency-Keystring

    Your own unique string for this request, up to 200 characters. A retry with the same key returns the first response instead of running again.

    • max 200 chars
Returns
200 One ProductLot object in data.
Errors
400 401 403 404 409 429 500 503
POST /product-lots/{lot_id}/unarchive
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"
Response200
{
  "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"
  }
}