Raw materials, packaging, recipes and finished goods in one resource with two families. Created in NutraSoft, updated by one operation for every type.
idintegeractivebooleanallergensarray of objectsnullableOn GET /products/{product_id} only; null on the list.
allergensallergen_idintegercodestringnullableis_major_allergenbooleannamestringnullablesourcestringdeclared: recorded directly against this product.
costnumbernullableCost (Gross) per inventory unit. Requires cost:read; null means redacted, not zero. Separate from operations_cost. On an account that updates costs from bills, the next bill receiving the product recalculates it.
created_atstringnullabledescriptionstringnullableis_deletedbooleankindstringDerived from product_type. Says which of material and recipe is populated.
materialrecipematerialobjectnullablePopulated only when kind is material.
materialbanned_country_idsarray of integerfood_idintegernullableThe nutrition source. Fetch with GET /foods/{food_id}.
substitute_product_idsarray of integersupplier_idsarray of integermaximum_on_handnumbernullableMax. Stock Level, in inventory units. 0 means no maximum.
minimum_on_handnumbernullableMin. Stock Level, in inventory units (see uom_qty). Stock on hand below it counts as low. 0 means no minimum.
namestringnullableoperations_costnumbernullableOperations cost per inventory unit. Requires cost:read.
pricenumbernullableSelling price per inventory unit. Requires cost:read; null means redacted, not zero.
product_codestringnullableproduct_typestringRAW_MATERIALPACKAGINGMISCELLANEOUSRECIPESUB_RECIPEFINISHED_GOODrecipeobjectnullablePopulated only when kind is recipe.
recipebyproduct_product_idsarray of integeringredients_enstringnullableThe generated ingredient declaration.
ingredients_frstringnullableportion_in_gramsnumbernullableqc_checkpoint_idsarray of integerrecipe_grams_yieldnumbernullableshelf_lifeintegernullableDays the product is usable after receipt. 0 means not set.
tag_idsarray of integernullableOn GET /products/{product_id} only; null on the list.
tax1booleannullableWhether tax 1 applies to this product. Requires cost:read.
tax2booleannullableWhether tax 2 applies to this product. Requires cost:read.
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| Field | Material | Recipe |
|---|---|---|
| product_type | RAW_MATERIAL, PACKAGING, MISCELLANEOUS | RECIPE, SUB_RECIPE, FINISHED_GOOD |
| kind | material | recipe |
| material.banned_country_ids | Yes | No, material is null |
material.food_idThe nutrition source. Fetch with GET /foods/{food_id}. | Yes | No, material is null |
| material.substitute_product_ids | Yes | No, material is null |
| material.supplier_ids | Yes | No, material is null |
| recipe.byproduct_product_ids | No, recipe is null | Yes |
| recipe.ingredients_enThe generated ingredient declaration. | No, recipe is null | Yes |
| recipe.ingredients_fr | No, recipe is null | Yes |
| recipe.portion_in_grams | No, recipe is null | Yes |
| recipe.qc_checkpoint_ids | No, recipe is null | Yes |
| recipe.recipe_grams_yield | No, recipe is null | Yes |
| Allergensallergens[].allergen_id | Yes | Yes |
| Tagstag_ids | Yes | Yes |
| Units of measureunit_of_measure_id | Yes | Yes |
| Product lotslinked by product_id | Yes | Yes |
/productscatalog:readOne table, two families: every row carries kind, material or recipe. allergens, tag_ids, material and recipe are null here: retrieve a product for them.
namestringnullablePartial match.
product_codestringnullablePartial match.
product_typesarray of stringnullableRepeat the parameter to match any of several types.
RAW_MATERIALPACKAGINGMISCELLANEOUSRECIPESUB_RECIPEFINISHED_GOODsearchstringnullableFree-text match.
sortstringnullableComma-separated field:direction pairs, e.g. id:desc. Sortable: created_at, id, name, product_code, product_type, 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/products?limit=50" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY"{
"data": [
{
"id": 123,
"active": true,
"allergens": null,
"cost": 12.5,
"created_at": "2026-09-10T14:30:00Z",
"description": "string",
"is_deleted": false,
"kind": "material",
"material": {
"banned_country_ids": [
123
],
"food_id": 123,
"substitute_product_ids": [
123
],
"supplier_ids": [
123
]
},
"maximum_on_hand": 12.5,
"minimum_on_hand": 12.5,
"name": "Sample name",
"operations_cost": 12.5,
"price": 12.5,
"product_code": "SAMPLE-01",
"product_type": "RAW_MATERIAL",
"recipe": null,
"shelf_life": 10,
"tag_ids": null,
"tax1": false,
"tax2": false,
"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"
}/products/{product_id}catalog:readExactly one of material and recipe is populated, as kind says. The bill of materials is not published in v1.
product_idintegerrequiredThe product's id.
data.curl "https://public-api.nutrasoft.ca/v1/products/123" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY"{
"data": {
"id": 123,
"active": true,
"allergens": [
{
"allergen_id": 123,
"code": "SAMPLE-01",
"is_major_allergen": false,
"name": "Sample name",
"source": "string"
}
],
"cost": 12.5,
"created_at": "2026-09-10T14:30:00Z",
"description": "string",
"is_deleted": false,
"kind": "material",
"material": {
"banned_country_ids": [
123
],
"food_id": 123,
"substitute_product_ids": [
123
],
"supplier_ids": [
123
]
},
"maximum_on_hand": 12.5,
"minimum_on_hand": 12.5,
"name": "Sample name",
"operations_cost": 12.5,
"price": 12.5,
"product_code": "SAMPLE-01",
"product_type": "RAW_MATERIAL",
"recipe": null,
"shelf_life": 10,
"tag_ids": [
123
],
"tax1": false,
"tax2": false,
"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"
}
}/products/{product_id}catalog:write· idempotent with Idempotency-KeyThe same fields for every product type. Omitted fields are left unchanged; an explicit null clears description and is refused for every other field. Packaging size, units of measure, product code and type cannot be changed. price and cost are per inventory unit and minimum_on_hand and maximum_on_hand are in inventory units. Writing needs only catalog:write; reading price, cost, tax1 and tax2 back needs cost:read. The change is recorded in the product's history in NutraSoft. When a field Shopify or QuickBooks mirrors changes on a product linked to them, the product is updated there too, as a save in NutraSoft does; a failed push does not fail the request. HubSpot is not updated.
product_idintegerrequiredThe product'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.
activebooleannullablecostnumbernullableCost (Gross) per inventory unit.
descriptionstringnullableNull or an empty string clears it.
maximum_on_handnumbernullableMax. Stock Level, in inventory units. 0 means no maximum.
minimum_on_handnumbernullableMin. Stock Level, in inventory units. 0 means no minimum.
namestringnullableSurrounding spaces are trimmed.
pricenumbernullableSelling price per inventory unit.
shelf_lifeintegernullableDays the product is usable after receipt. Lots already on hand keep their expiration dates.
tax1booleannullableWhether tax 1 applies to this product.
tax2booleannullableWhether tax 2 applies to this product.
data.curl -X PATCH "https://public-api.nutrasoft.ca/v1/products/123" \
-H "Authorization: Bearer $NUTRASOFT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c6a52-8d0e-4b7a-9c2f-5e8d1b4a7c90" \
-d '{
"minimum_on_hand": 40,
"price": 24.99
}'{
"data": {
"id": 123,
"active": true,
"allergens": [
{
"allergen_id": 123,
"code": "SAMPLE-01",
"is_major_allergen": false,
"name": "Sample name",
"source": "string"
}
],
"cost": 12.5,
"created_at": "2026-09-10T14:30:00Z",
"description": "string",
"is_deleted": false,
"kind": "material",
"material": {
"banned_country_ids": [
123
],
"food_id": 123,
"substitute_product_ids": [
123
],
"supplier_ids": [
123
]
},
"maximum_on_hand": 12.5,
"minimum_on_hand": 12.5,
"name": "Sample name",
"operations_cost": 12.5,
"price": 12.5,
"product_code": "SAMPLE-01",
"product_type": "RAW_MATERIAL",
"recipe": null,
"shelf_life": 10,
"tag_ids": [
123
],
"tax1": false,
"tax2": false,
"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"
}
}