Raw materials, packaging, recipes and finished goods in one resource with two families. Read-only in v1.
idintegeractivebooleanallergensarray of objectsnullableOn GET /products/{product_id} only; null on the list.
allergensallergen_idintegercodestringnullableis_major_allergenbooleannamestringnullablesourcestringdeclared: recorded directly against this product.
costnumbernullableCost per inventory unit. Requires cost:read; null means redacted, not zero. Separate from operations_cost.
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 integernamestringnullableoperations_costnumbernullableOperations cost per inventory unit. Requires cost:read.
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_lifeintegernullabletag_idsarray of integernullableOn GET /products/{product_id} only; null on the list.
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
]
},
"name": "Sample name",
"operations_cost": 12.5,
"product_code": "SAMPLE-01",
"product_type": "RAW_MATERIAL",
"recipe": null,
"shelf_life": 10,
"tag_ids": null,
"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
]
},
"name": "Sample name",
"operations_cost": 12.5,
"product_code": "SAMPLE-01",
"product_type": "RAW_MATERIAL",
"recipe": null,
"shelf_life": 10,
"tag_ids": [
123
],
"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"
}
}