Working with Recipes
Recipes are the core of Eclarion. A recipe contains everything about a food product: name, ingredients, nutritional values, allergens, packaging, and production steps — all in a single API resource. This guide walks you through the typical workflow of creating and managing recipes via the API.
For the full list of fields, types, and JSON examples per collection, see the API v3 Reference.
How recipes work
A recipe in Eclarion is a complete product specification. One GET request returns everything:
curl -H "Authorization: Bearer YOUR_AUTH_TOKEN" \
https://api.eclarion.com/v3/recipes/123
The response includes all nested collections inline — nutritional values, allergens, microbiological values, physiochemical values, organoleptic values, preparation instructions, packages, steps with line items, recipe-level line items, claims, custom fields, images, and documents. No separate requests needed.
Step 1: Look up your brand and category
Every recipe belongs to a brand and a category. You'll need their IDs before creating a recipe:
# Get your brands
curl -H "Authorization: Bearer YOUR_AUTH_TOKEN" \
https://api.eclarion.com/v3/brands
# Get your categories
curl -H "Authorization: Bearer YOUR_AUTH_TOKEN" \
https://api.eclarion.com/v3/categories
Categories organize your products (e.g. "Ingredients", "Recipes", "Finished Products"). Use the id values from these responses in your recipe.
Step 2: Create a minimal recipe
Start with the required fields: productname, ingredientdeclaration (both with your account's primary locale), brand_id, and category_id:
curl -X POST \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"recipe": {
"productname": { "en": "Fruit Yoghurt", "nl": "Vruchtenyoghurt" },
"ingredientdeclaration": { "en": "milk, sugar, strawberry 8%", "nl": "melk, suiker, aardbei 8%" },
"brand_id": 42,
"category_id": 1
}
}' \
https://api.eclarion.com/v3/recipes
This returns the created recipe with its id. You can now add more data by updating it, or include everything in one POST.
Text fields like productname are multilingual — provide at least the primary locale, and add other languages as needed.
Step 3: Add nested collections
The real power of the API is sending everything in one request. You can include all nested collections when creating or updating a recipe.
Using reference strings
For nested collections, you use reference strings instead of internal IDs. The API resolves them automatically. Get the available references from the public endpoints:
| Collection | Reference endpoint |
|---|---|
| Nutritional values | /v3/nutrients |
| Allergen values | /v3/allergens |
| Microbiological values | /v3/microbiologies, precisions, metric units |
| Organoleptic values | /v3/organoleptic_properties |
| Preparation instructions | /v3/preparation_instructions |
| Package levels | /v3/package_levels |
| Measurement units (line items) | /v3/measurement_units |
These reference endpoints are public and don't require authentication.
Multilingual nested values
Some nested collection values support translations. These use the same locale-keyed object as recipe fields:
- Organoleptic values:
valueis multilingual - Preparation instruction values:
valueis multilingual - Line items:
instructionsis multilingual (per-ingredient notes) - Packages:
nameanddescriptionare multilingual - Steps:
nameandinstructionsare multilingual
For the full field details and JSON examples for each collection, see the Create a recipe endpoint in the API reference.
Recipe steps vs. recipe-level line items
Ingredients can be organized in two ways, and this is a common point of confusion:
- Recipe-level
line_items— ingredients directly on the recipe, without step structure. Use this for simple recipes without production phases. - Steps with
line_items— ingredients organized into named production phases (e.g. "Mixing", "Baking", "Finishing"). Each step has a position, name, instructions, and its own ingredients.
Steps are new in v3 and are the recommended approach for recipes with a production process. You can use either approach, but not both on the same recipe.
"steps": [
{
"position": 1,
"name": { "en": "Mixing" },
"instructions": { "en": "Combine all dry ingredients." },
"line_items": [
{ "ingredient_id": 150, "weight": 500, "measurement_unit": "gram", "instructions": { "en": "Sifted" } },
{ "ingredient_id": 151, "weight": 200 }
]
},
{
"position": 2,
"name": { "en": "Baking" },
"instructions": { "en": "Bake at 180°C for 25 minutes." }
}
]
Packaging levels
Recipes typically have up to three packaging levels that represent the supply chain hierarchy:
consumer_unit— the retail product the consumer buys (e.g. "Fruit Yoghurt 750g")trade_unit— the case or tray for wholesale (e.g. "6x Fruit Yoghurt 750g")pallet— the full pallet for logistics (e.g. "54 trays on pallet")
Each level can have its own EAN code, dimensions, and weight. The number_of_products field indicates how many of the previous level fit inside (e.g. 6 consumer units per trade unit).
See the API reference for all available package fields (dimensions, weights, GS1 properties).
Updating a recipe
Use PATCH with partial data — only send the fields you want to change:
curl -X PATCH \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"recipe": {"code": "R-002", "netcontent": "750 g"}}' \
https://api.eclarion.com/v3/recipes/123
Update, add, or remove nested records
Nested collections come in two types, each with their own way of identifying records:
Reference-based collections
nutritional_values, allergen_values, microbiological_values, physiochemical_values, organoleptic_values, preparation_instruction_values — identified by reference:
| Action | How | Example |
|---|---|---|
| Update | Use the reference | {"reference": "protein", "value": 7.5} |
| Add | Same syntax (auto-detected) | {"reference": "fiber", "value": 3.0} |
| Remove | reference + "remove": true | {"reference": "fat", "remove": true} |
ID-based collections
line_items, packages, steps — identified by id:
| Action | How | Example |
|---|---|---|
| Update | Include the id | {"id": 456, "weight": 200} |
| Add | Omit the id | {"ingredient_id": 123, "weight": 100} |
| Remove | id + "remove": true | {"id": 456, "remove": true} |
You can mix all three operations in a single request. Records not mentioned are left untouched:
{
"recipe": {
"nutritional_values": [
{ "reference": "protein", "value": 7.5 },
{ "reference": "fiber", "value": 2.1 },
{ "reference": "fat", "remove": true }
]
}
}
Claims
Claims are boolean flags that indicate product certifications or characteristics (e.g. organic, vegan, halal). Set them as key-value pairs where the key is the claim reference and the value is true or false:
{
"recipe": {
"claims": {
"organic": true,
"vegan": true,
"gluten_free": false
}
}
}
The available claims are fixed across all accounts:
| Reference | Description |
|---|---|
estimated_sign | Estimated sign (℮) |
free_of_gmo | Free of GMO |
vegetarian | Vegetarian |
vegan | Vegan |
halal | Halal |
kosher | Kosher |
organic | Organic |
gluten_free | Gluten free |
msc | MSC certified |
irradiated | Irradiated |
lactose_free | Lactose free |
rfa_certified | Rainforest Alliance certified |
Unknown claim keys are silently ignored. Claims you don't include in an update are left unchanged.
Custom fields
Custom fields are additional data fields that your account administrator has configured. They vary per account, so you'll first need to look up which fields are available:
curl -H "Authorization: Bearer YOUR_AUTH_TOKEN" \
https://api.eclarion.com/v3/custom_fields
This returns your account's custom fields with their internal_name, label, field_type, and section:
{
"custom_fields": [
{
"id": 1,
"internal_name": "a1b2",
"label": { "en": "Batch Number", "nl": "Batchnummer" },
"field_type": "text",
"section": "base"
},
{
"id": 2,
"internal_name": "c3d4",
"label": { "en": "Target pH", "nl": "Doel pH" },
"field_type": "number",
"section": "physiochemical_values"
}
]
}
Use the internal_name as key when setting custom field values on a recipe:
{
"recipe": {
"custom_fields": {
"a1b2": "Batch 2024-03",
"c3d4": "4.2"
}
}
}
Field types determine what kind of data is expected:
| Field type | Value format |
|---|---|
text | Any string value |
number | Numeric string (e.g. "4.2") |
multilanguage | Locale-keyed object (e.g. {"en": "Note", "nl": "Opmerking"}) |
Unknown custom field keys are silently ignored. Custom fields you don't include in an update are left unchanged.
Images and documents
Unlike other nested collections, images and documents use dedicated endpoints with multipart file uploads. They are read inline on the recipe response, but created, updated, and deleted through their own URLs.
Reading images and documents
Images and documents are included in every recipe response automatically:
{
"recipe": {
"id": 123,
"productname": { "en": "Fruit Yoghurt" },
"images": [
{
"id": 1,
"url": "https://...",
"filename": "front-of-pack.jpg",
"content_type": "image/jpeg",
"byte_size": 245000,
"description": { "en": "Front of package" },
"position": 1
}
],
"documents": [
{
"id": 1,
"url": "https://...",
"filename": "spec-sheet.pdf",
"content_type": "application/pdf",
"byte_size": 1200000,
"description": { "en": "Technical specification" },
"document_type": "specification",
"include_in_specification": true,
"position": 1
}
]
}
}
Uploading files
Use multipart/form-data (not JSON) to upload files. Accepted formats: JPEG, PNG, GIF for images; PDF for documents. Maximum file size: 10 MB.
# Upload an image
curl -X POST \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-F "file=@front-of-pack.jpg" \
-F "position=1" \
-F "description[en]=Front of package" \
-F "description[nl]=Voorkant verpakking" \
https://api.eclarion.com/v3/recipes/123/images
# Upload a document
curl -X POST \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-F "file=@spec-sheet.pdf" \
-F "position=1" \
-F "include_in_specification=true" \
-F "description[en]=Technical specification" \
https://api.eclarion.com/v3/recipes/123/documents
Updating metadata
Update description, position, or (for documents) include_in_specification. File replacement is not supported — upload a new file and delete the old one instead.
curl -X PATCH \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"position": 2, "description": {"en": "Updated caption"}}' \
https://api.eclarion.com/v3/recipes/123/images/456
Deleting
curl -X DELETE \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
https://api.eclarion.com/v3/recipes/123/images/456
Deleting a recipe
Deletes a recipe:
curl -X DELETE \
-H "Authorization: Bearer YOUR_AUTH_TOKEN" \
https://api.eclarion.com/v3/recipes/123
Complete example
Here's a full recipe creation request with the most common fields. This gives you a good starting point to adapt:
{
"recipe": {
"productname": { "en": "Fruit Yoghurt", "nl": "Vruchtenyoghurt" },
"labeldescription": { "en": "Yoghurt with strawberry", "nl": "Yoghurt met aardbei" },
"ingredientdeclaration": { "en": "milk, sugar, strawberry 8%", "nl": "melk, suiker, aardbei 8%" },
"code": "R-001",
"category_id": 1,
"brand_id": 42,
"netcontent": "750 g",
"usagestorageinstruction": { "en": "Keep refrigerated between 2°C and 7°C" },
"best_before_date": { "en": "See lid" },
"ean": "1234567890128",
"minimum_lifespan_from_production": 21,
"minimum_storage_temperature": 2,
"maximum_storage_temperature": 7,
"nutritional_values": [
{ "reference": "energykj", "value": 380 },
{ "reference": "energykcal", "value": 90 },
{ "reference": "fat", "value": 1.5 },
{ "reference": "protein", "value": 4.2 },
{ "reference": "carbohydrates", "value": 14.8 }
],
"allergen_values": [
{ "reference": "milk", "value": "2" },
{ "reference": "gluten", "value": "0" }
],
"microbiological_values": [
{
"reference": "salmonella",
"precision": "absent_in",
"value": 25,
"metric_unit": "gram",
"test_method": "ISO 6579"
}
],
"organoleptic_values": [
{ "reference": "color", "value": { "en": "Light pink with fruit pieces" } },
{ "reference": "taste", "value": { "en": "Fresh, mildly sweet" } }
],
"steps": [
{
"position": 1,
"name": { "en": "Mixing", "nl": "Mengen" },
"line_items": [
{ "ingredient_id": 150, "weight": 650, "instructions": { "en": "Sifted" } },
{ "ingredient_id": 151, "weight": 80 },
{ "ingredient_id": 152, "weight": 60 }
]
}
],
"claims": {
"vegetarian": true,
"gluten_free": true
},
"custom_fields": {
"a1b2": "Batch 2024-03",
"c3d4": "4.2"
},
"packages": [
{
"level": "consumer_unit",
"name": { "en": "Fruit Yoghurt 750g" },
"ean": "1234567890123",
"number_of_products": 1,
"net_weight": 750,
"net_weight_unit": "gram",
"gross_weight": 800,
"gross_weight_unit": "gram"
},
{
"level": "trade_unit",
"name": { "en": "6x Fruit Yoghurt 750g" },
"ean": "0987654321098",
"number_of_products": 6,
"net_weight": 4500,
"net_weight_unit": "gram",
"gross_weight": 4950,
"gross_weight_unit": "gram"
}
]
}
}
Tips
- Start minimal, then expand. Create a recipe with just the required fields, verify it works, then add nested collections.
- Cache reference data. Allergens, nutrients, and other reference lists rarely change. Fetch them once and reuse.
- Use PATCH, not PUT. Partial updates are efficient — only send what changed.
- Reference-based collections don't need
id. Use thereferencefield to identify records. For ID-based collections (line_items,packages,steps), include theidwhen updating.
Next steps
- Multilingual Fields — how translations work for all text fields
- Migration Guide — upgrade from v1/v2 to v3
- API v3 Reference — full interactive documentation with all fields and types