# Labelify API - LLM Integration Guide Labelify does not use LLMs in its product. This document helps LLM applications (ChatGPT, Claude, etc.) interact with the Labelify API on behalf of users for nutrition label compliance tasks. ## What is Labelify? Labelify is online software that lets food businesses create their own Nutrition Facts tables in minutes. Users build recipes from a database of 5,000+ ingredients, and Labelify calculates nutritional values and generates regulation-compliant labels for Canadian and US markets—including bilingual formats and front-of-package symbols. ## API Basics Base URL: https://apie.labelify.ca Version: v1 Content-Type: application/json OpenAPI Specification: https://apie.labelify.ca/labelify_openapi.json Interactive Docs: https://apie.labelify.ca/summer The OpenAPI spec contains complete request/response schemas for all endpoints. LLMs should fetch this spec for detailed field definitions, validation rules, and enum values not fully documented here. ## Terminology Note In the Labelify UI, "Foods" are displayed as "Ingredients" to users. When a user mentions "ingredients," they may mean: - **Foods** (API): Raw ingredients in the database with nutritional data (e.g., "flour", "sugar") - **Recipe ingredients** (API): Foods added to a recipe with quantities (the `ingredients` array in a recipe) Context usually clarifies: "add an ingredient to my database" → create a Food; "add an ingredient to my recipe" → add to recipe's ingredients list. ## Authentication All API requests require the `Authorization` header with an API key. Header: Authorization Format: API-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Example: ``` Authorization: API-12345678-ABCD-1234-EFGH-567890ABCDEF ``` ### How to Get an API Key 1. Log into Labelify at https://app.labelify.ca 2. Click your user avatar (top-left sidebar) 3. Select "API" from the dropdown menu 4. Copy the displayed API key 5. To generate a new key, click "Regenerate API key" (invalidates previous key) Note: API access requires an active subscription with API permissions enabled. ## Rate Limits Authenticated requests (with API key): | Metric | Limit | |--------|-------| | Requests per minute | 500 | | Requests per hour | 5,000 | | Concurrent requests | 10 | When limits are exceeded, the API returns HTTP 429 with a `Retry-After` header indicating seconds to wait. ## Error Responses All errors return JSON with this structure: ```json { "code": 1005, "type": "InvalidOrMissing", "message_en": "Recipe not found", "message_fr": "Recette introuvable" } ``` Rate limit errors (429) include additional fields: ```json { "code": 4029, "type": "RateLimitExceeded", "message_en": "Rate limit exceeded. Please try again later.", "message_fr": "Limite de taux dépassée. Veuillez réessayer plus tard.", "retry_after": 60, "details": "500 per 1 minute" } ``` Common error codes: | Code | Type | Meaning | |------|------|---------| | 1001 | ValidationError | Invalid request data | | 1005 | InvalidOrMissing | Resource not found | | 1010 | VersionConflict | Stale data, refetch and retry | | 1101 | Unauthorized | Invalid or missing API key | | 1429 | ConcurrencyLimitExceeded | Too many concurrent requests | | 4029 | RateLimitExceeded | Rate limit exceeded | HTTP status codes: 400 (bad request), 403 (forbidden/unauthorized), 404 (not found), 422 (validation), 429 (rate limit) --- ## Core Resources ### Foods Foods are raw ingredients with nutritional data per reference amount (typically 100g). **List foods** ``` GET /v1/foods?page=1&page_size=20&short=true ``` Query parameters: - search: text search in name fields - source: filter by source (e.g., "CNF", "USDA", custom) - tags: comma-separated tag IDs - short: true returns minimal fields - expand: nutriments_100g,tags (additional data) **Get single food** ``` GET /v1/foods/{food_id} ``` **Get food by product code (SKU)** ``` GET /v1/foods/by_product_code/{product_code} ``` **Create food** ``` POST /v1/foods Content-Type: application/json { "name": "All-purpose flour", "mass_quantity": "100", "mass_unit": "g", "nutriments": { "calories": {"amount": "364", "unit": "kcal"}, "protein": {"amount": "10.33", "unit": "g"}, "carbohydrate": {"amount": "76.31", "unit": "g"}, "fat": {"amount": "0.98", "unit": "g"}, "fibre": {"amount": "2.7", "unit": "g"}, "sugars": {"amount": "0.27", "unit": "g"}, "sodium": {"amount": "2", "unit": "mg"} } } ``` Required: name Optional: brand, source, product_code, allergies, volume_quantity/volume_unit, common_name_en/fr **Update food** ``` POST /v1/foods/{food_id} ``` Updates provided fields. Sub-collections (nutriments, custom_units) are replaced entirely—items not in the request are deleted. **Update food (merge)** ``` PATCH /v1/foods/{food_id} ``` Updates provided fields. Sub-collections are merged—adds/updates items without deleting missing ones. **Delete food** ``` DELETE /v1/foods/{food_id} ``` --- ### Recipes Recipes combine foods with quantities. Nutritional values are calculated automatically. **List recipes** ``` GET /v1/recipes?page=1&page_size=20 ``` Query parameters: - search: text search in name - food_id: recipes containing this food - recipe_id: recipes containing this subrecipe - tags: comma-separated tag IDs - expand: nutriments,nutriments_100g,previews_CAN2016,previews_US,tags **Get single recipe** ``` GET /v1/recipes/{recipe_id} ``` Optional query parameters: - resized_view_qty: calculate nutrition for specific serving (e.g., "150") - resized_view_unit: unit for resized view (e.g., "g") **Get recipe by product code (SKU)** ``` GET /v1/recipes/by_product_code/{product_code} ``` **Create recipe** ``` POST /v1/recipes Content-Type: application/json { "name": "Chocolate Chip Cookies", "ingredients": [ {"food_id": "abc123", "quantity": "250", "unit": "g", "order": 1}, {"food_id": "def456", "quantity": "200", "unit": "g", "order": 2}, {"food_id": "ghi789", "quantity": "100", "unit": "g", "order": 3} ] } ``` Required: name Optional: instructions_en/fr, notes_en/fr, product_code, common_name_en/fr **Update recipe** ``` POST /v1/recipes/{recipe_id} ``` Updates provided fields. Ingredients list is replaced entirely—ingredients not in the request are deleted. **Update recipe (merge)** ``` PATCH /v1/recipes/{recipe_id} ``` Updates provided fields. Ingredients are merged—adds/updates without deleting missing ones. **Delete recipe** ``` DELETE /v1/recipes/{recipe_id} ``` **Get recipe nutriments** ``` GET /v1/recipes/{recipe_id}/nutriments?qty=100&unit=g ``` Returns nutritional values for specified quantity. Omit parameters for full recipe. --- ### Ingredients Manage individual ingredients within a recipe. **Add ingredient to recipe** ``` POST /v1/recipes/{recipe_id}/ingredients Content-Type: application/json { "food_id": "abc123", "quantity": "100", "unit": "g", "order": 1 } ``` Alternative: use `source_recipe_id` instead of `food_id` to add a sub-recipe as ingredient. Optional fields: - ingredient_list_display_as_overwrite_en/fr: custom name in ingredient list - ingredient_list_class_name: renaming/grouping class (see below) - ingredient_list_at_end: place at end of list (boolean, requires compatible class_name) - ingredient_list_hide_subingredients: hide sub-recipe ingredients in parentheses (boolean) - ingredient_list_merge_subingredients: merge sub-recipe ingredients into main list by weight (boolean) - refuse_pct: percentage waste (e.g., "10" for 10% peel) #### Ingredient List Class Names Assign `ingredient_list_class_name` to control renaming and grouping. To place ingredients at the end of the list, you must ALSO set `ingredient_list_at_end: true`. **CAN2016 behavior:** | Class Name | Rename | End allowed | |------------|--------|-------------| | sugar | Groups as "Sugars (item1, item2, ...)" | No | | spice | Renamed to "Spices" | Yes (requires `at_end: true`) | | seasoning | Renamed to "Seasonings" | Yes (requires `at_end: true`) | | herb | Renamed to "Herbs" | Yes (requires `at_end: true`) | | natural_flavour | Renamed to "Natural flavours" | Yes (requires `at_end: true`) | | artificial_flavour | Renamed to "Artificial flavours" | Yes (requires `at_end: true`) | | colour | Keeps original name | Yes (requires `at_end: true`) | | at_end | Keeps original name | Yes (requires `at_end: true`) | **US behavior:** All class names stay in weight order (`at_end` has no effect). Only renaming applies: spice→"spices", natural_flavour→"natural flavor", artificial_flavour→"artificial flavor". Others keep original names. **Sugar grouping example (CAN2016):** ``` POST /v1/recipes/{recipe_id}/ingredients { "food_id": "glucose-syrup-id", "quantity": "15", "unit": "g", "ingredient_list_class_name": "sugar" } ``` Multiple ingredients with class_name "sugar" are combined: - Input: glucose syrup (15g), fructose (10g), honey (5g) - all tagged as "sugar" - Output ingredient list: "...Sugars (glucose syrup, fructose, honey)..." **Placing spices at end (CAN2016):** ``` POST /v1/recipes/{recipe_id}/ingredients { "food_id": "cumin-id", "quantity": "2", "unit": "g", "ingredient_list_class_name": "spice", "ingredient_list_at_end": true } ``` Without `at_end: true`, the ingredient would be renamed to "Spices" but stay in weight order. #### Sub-Recipe Ingredient Display When a recipe contains another recipe as an ingredient (sub-recipe), control how its ingredients appear: **Default behavior** - Sub-recipe name with ingredients in parentheses: ``` Tomato Sauce (tomatoes, water, salt, spices), Cheese, Flour ``` **Hide sub-ingredients** (`ingredient_list_hide_subingredients: true`): ``` Tomato Sauce, Cheese, Flour ``` **Merge sub-ingredients** (`ingredient_list_merge_subingredients: true`): Sub-recipe ingredients are extracted and merged into the main list by weight: ``` Flour, Tomatoes, Cheese, Water, Salt, Spices ``` Example - merge sub-recipe ingredients: ``` POST /v1/recipes/{recipe_id}/ingredients { "source_recipe_id": "tomato-sauce-recipe-id", "quantity": "200", "unit": "g", "ingredient_list_merge_subingredients": true } ``` **Get ingredient** ``` GET /v1/recipes/{recipe_id}/ingredients/{ingredient_id} ``` **Update ingredient** ``` POST /v1/recipes/{recipe_id}/ingredients/{ingredient_id} ``` **Delete ingredient** ``` DELETE /v1/recipes/{recipe_id}/ingredients/{ingredient_id} ``` --- ### Labels (Nutrition Facts Tables) Labels render regulation-compliant Nutrition Facts tables from recipes. **List labels** ``` GET /v1/labels?recipe_id={recipe_id} ``` **Create label** ``` POST /v1/labels Content-Type: application/json { "recipe_id": "abc123", "concrete_type": "CAN2016", "serving_qty": "30", "serving_unit": "g", "reference_qty": "100", "reference_unit": "g", "household_serving_en": "1 cookie (30 g)", "household_serving_fr": "1 biscuit (30 g)", "label_format": "standard_bilingual" } ``` concrete_type options: - "CAN2016" - Canadian 2016 regulations (current, recommended) - "US" - United States FDA format #### CAN2016 Label Formats | label_format | Description | Use Case | |--------------|-------------|----------| | standard | Standard (English only) | Single-language products | | standard_bilingual | Standard Bilingual | Most common - English/French side-by-side | | narrow | Narrow Standard | Tall, narrow packages | | horizontal_bilingual | Horizontal Bilingual | Wide packages | | simplified | Simplified Standard | Small packages with limited nutrients | | simplified_bilingual | Simplified Bilingual | Small packages, bilingual | | horizontal_simplified_bilingual | Horizontal Simplified Bilingual | Wide small packages | | linear | Linear | Very small packages, single line | | linear_simplified | Linear Simplified | Tiny packages | | dual | Dual - Foods Requiring Preparation | "As sold" vs "As prepared" (e.g., cake mix) | | dual_bilingual | Dual Bilingual | Preparation dual, bilingual | | dual_amount | Dual - Different Amounts | Multiple serving sizes | | dual_amount_bilingual | Dual Bilingual - Different Amounts | Multiple servings, bilingual | | aggregate | Aggregate - Different Foods | Assorted products (e.g., variety pack) | | aggregate_bilingual | Aggregate Bilingual | Assorted products, bilingual | | aggregate_amount | Aggregate - Different Amounts | Assorted with varying amounts | | aggregate_amount_bilingual | Aggregate Bilingual - Different Amounts | Assorted amounts, bilingual | #### US Label Formats | label_format | Description | Use Case | |--------------|-------------|----------| | standard | Standard Vertical | Most common US format | | tabular | Tabular | Side-by-side layout | | simplified | Simplified | Products with limited nutrients | | linear | Linear | Very small packages | | dual | Dual Column | "As packaged" vs "As prepared" | | aggregate | Aggregate | Assorted/variety products | | tabular_dual | Tabular Dual | Tabular with dual columns | **Get label** ``` GET /v1/labels/{label_id} ``` **Update label** ``` POST /v1/labels/{label_id} ``` Updates provided fields. **Update label (merge)** ``` PATCH /v1/labels/{label_id} ``` Updates provided fields. Use when you want to preserve unspecified sub-entity values. **Delete label** ``` DELETE /v1/labels/{label_id} ``` **Render label** ``` GET /v1/labels/{label_id}/render.pdf GET /v1/labels/{label_id}/render.png GET /v1/labels/{label_id}/render.svg ``` Returns binary file. For HTML content in JSON: ``` GET /v1/labels/{label_id}/render.html?json=true ``` **Measure label dimensions** ``` GET /v1/labels/{label_id}/measure ``` Returns dimensions in centimeters: - width, height, area: numeric values - display_width, display_height, display_area: formatted strings (e.g., "5.08 cm") --- ### FOP Labels (Front-of-Package) Canadian Front-of-Package nutrition symbols showing high sodium, sugars, or saturated fat warnings. **List FOP labels** ``` GET /v1/fop_labels?recipe_id={recipe_id} ``` **Create FOP label** ``` POST /v1/fop_labels Content-Type: application/json { "recipe_id": "abc123", "concrete_type": "CAN2016", "serving_qty": "30", "serving_unit": "g", "reference_qty": "100", "reference_unit": "g" } ``` **Get FOP label** ``` GET /v1/fop_labels/{fop_label_id} ``` **Delete FOP label** ``` DELETE /v1/fop_labels/{fop_label_id} ``` **Render FOP label** ``` GET /v1/fop_labels/{fop_label_id}/render.pdf GET /v1/fop_labels/{fop_label_id}/render.png GET /v1/fop_labels/{fop_label_id}/render.svg ``` --- ### Tags Tags organize foods and recipes with custom metadata. Useful for system-to-system workflows like marking imported items, flagging incomplete data, or categorization. Food tags and recipe tags are separate systems. **List food tags** ``` GET /v1/tags/food_tags ``` **Create food tag** ``` POST /v1/tags/food_tags Content-Type: application/json { "name": "imported", "color": "#3B82F6" } ``` **Edit food tag** ``` POST /v1/tags/food_tags/{food_tag_id} ``` **Delete food tag** ``` DELETE /v1/tags/food_tags/{food_tag_id} ``` **List recipe tags** ``` GET /v1/tags/recipe_tags ``` **Create recipe tag** ``` POST /v1/tags/recipe_tags Content-Type: application/json { "name": "missing-values", "color": "#EF4444" } ``` **Edit/Delete recipe tags**: Same pattern as food tags at `/v1/tags/recipe_tags/{recipe_tag_id}` #### Tag Associations Associate tags with foods: **List tags on a food** ``` GET /v1/tags/food_associations/{food_id}/tags ``` **Add tag to food** ``` POST /v1/tags/food_associations/{food_id}/tags Content-Type: application/json { "food_tag_id": "tag123" } ``` **Set all tags on a food (replace)** ``` POST /v1/tags/food_associations/{food_id} Content-Type: application/json { "food_tag_ids": ["tag123", "tag456"] } ``` **Remove tag from food** ``` DELETE /v1/tags/food_associations/{food_id}/tags/{food_tag_id} ``` #### Filtering by Tags Include `tags` parameter in list queries: ``` GET /v1/foods?tags=tag123,tag456 GET /v1/recipes?tags=tag789 ``` Include tag data in responses with expand: ``` GET /v1/foods?expand=tags GET /v1/recipes?expand=tags ``` #### Batch Tag Operations **Merge tags** (combine multiple into one): ``` POST /v1/tags/food_tags/batch_merge?ids=tag1,tag2,tag3 POST /v1/tags/recipe_tags/batch_merge?ids=tag1,tag2 ``` **Batch delete tags**: ``` POST /v1/tags/food_tags/batch_remove?ids=tag1,tag2 POST /v1/tags/recipe_tags/batch_remove?ids=tag1,tag2 ``` --- ## Common Workflows ### 1. Analyze Recipe Nutrition ``` GET /v1/recipes/{recipe_id}?expand=nutriments,nutriments_100g ``` Response includes: - `nutriments`: values for the complete recipe - `nutriments_100g`: values per 100g (for comparison) - `ingredients`: list with individual nutriment breakdowns ### 2. Generate Compliance Label Step 1: Verify recipe exists ``` GET /v1/recipes/{recipe_id} ``` Step 2: Create label ``` POST /v1/labels { "recipe_id": "{recipe_id}", "concrete_type": "CAN2016", "serving_qty": "30", "serving_unit": "g", "reference_qty": "55", "reference_unit": "g", "household_serving_en": "1 serving" } ``` Step 3: Download rendered label ``` GET /v1/labels/{label_id}/render.pdf ``` ### 3. Build Recipe from Scratch Step 1: Search for ingredients ``` GET /v1/foods?search=flour&short=true GET /v1/foods?search=sugar&short=true GET /v1/foods?search=butter&short=true ``` Step 2: Create recipe with found food IDs ``` POST /v1/recipes { "name": "Shortbread Cookies", "ingredients": [ {"food_id": "{flour_id}", "quantity": "200", "unit": "g"}, {"food_id": "{butter_id}", "quantity": "150", "unit": "g"}, {"food_id": "{sugar_id}", "quantity": "75", "unit": "g"} ] } ``` Step 3: Response includes calculated nutriments automatically ### 4. Update Food, Propagate to Recipes ``` PATCH /v1/foods/{food_id} { "nutriments": { "sodium": {"amount": "150"} } } ``` All recipes using this food automatically recalculate their nutritional values. --- ## Pagination All list endpoints support pagination: ``` GET /v1/recipes?page=1&page_size=50 ``` Parameters: - page: integer starting at 1 - page_size: 1-200 (default 20) Response includes page_info: ```json { "items": [...], "page_info": { "page": 1, "page_size": 20, "total": 150, "has_next": true, "has_previous": false } } ``` --- ## Utility Endpoints **List allergens** ``` GET /v1/utils/allergies ``` Returns standard allergen names (milk, eggs, fish, etc.) **List available units** ``` GET /v1/utils/available_units ``` Returns valid measurement units (g, kg, ml, L, cup, tbsp, etc.) **List yield units** ``` GET /v1/utils/yield_units ``` Returns valid recipe yield units (%, servings, pieces, etc.) **List concrete types** ``` GET /v1/utils/concrete_types ``` Returns valid label formats (CAN2016, US) **List nutriment rules** ``` GET /v1/utils/nutriments_rules ``` Returns metadata for all nutrients (names, units, daily values) --- ## Nutriments Reference When creating or updating foods, each nutriment requires `amount` and `unit` fields. ### Core Nutriments | Key | Unit | Name | |-----|------|------| | calories | kcal | Calories | | fat | g | Total Fat | | fat_saturated | g | Saturated Fat | | fat_trans | g | Trans Fat | | cholesterol | mg | Cholesterol | | sodium | mg | Sodium | | carbohydrate | g | Carbohydrate | | fibre | g | Fibre | | sugars | g | Sugars | | protein | g | Protein | | potassium | mg | Potassium | | calcium | mg | Calcium | | iron | mg | Iron | | added_sugars | g | Added Sugars (US only) | ### Common Additional Nutriments | Key | Unit | Name | |-----|------|------| | vitamin_a_RAE | ug | Vitamin A (RAE) | | vitamin_c | mg | Vitamin C | | vitamin_d | ug | Vitamin D | | vitamin_e | mg | Vitamin E | | vitamin_k | ug | Vitamin K | | thiamin | mg | Thiamine | | riboflavin | mg | Riboflavin | | niacin_NE | mg | Niacin (NE) | | vitamin_b6 | mg | Vitamin B6 | | folate_DFE | ug | Folate (DFE) | | vitamin_b12 | ug | Vitamin B12 | | biotin | ug | Biotin | | pantothenic_acid | mg | Pantothenic Acid | | phosphorus | mg | Phosphorus | | magnesium | mg | Magnesium | | zinc | mg | Zinc | | selenium | ug | Selenium | | copper | mg | Copper | | manganese | mg | Manganese | Full list available via `GET /v1/utils/nutriments_rules`. --- ## Bilingual Support Labelify supports English and French. Most text fields have _en and _fr variants: - name_en, name_fr - common_name_en, common_name_fr - ingredient_list_en, ingredient_list_fr - household_serving_en, household_serving_fr - instructions_en, instructions_fr - notes_en, notes_fr When creating resources, provide either `name` (uses default language) --- ## Version Control Entities include `version_id` for optimistic concurrency control. When updating, you may include the current version_id. If the server version differs (another update occurred), the API returns error code 1010 (VersionConflict). Refetch and retry. --- ## What LLMs Can Help Users With 1. **Food Data Entry**: Input foods from spec sheets, supplier documents, or nutrition panels into the system 2. **Recipe Review**: Analyze recipes for completeness, flag missing nutrients or allergens, suggest improvements 3. **Recipe Analysis**: Explain nutritional values for recipes 4. **Label Generation**: Create Nutrition Facts labels with correct formats 5. **Ingredient Management**: Search database, find substitutes, organize foods with tags 6. **Compliance Checks**: Verify labels meet Canadian/US regulations, check FOP symbol requirements 7. **Format Conversion**: Transform recipe data from various formats into API-compatible JSON 8. **Bulk Operations**: Process multiple recipes or foods programmatically 9. **Integration**: Build interoperability with other software (ERP, inventory, POS systems)