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:

CollectionReference 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: value is multilingual
  • Preparation instruction values: value is multilingual
  • Line items: instructions is multilingual (per-ingredient notes)
  • Packages: name and description are multilingual
  • Steps: name and instructions are 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:

  1. consumer_unit — the retail product the consumer buys (e.g. "Fruit Yoghurt 750g")
  2. trade_unit — the case or tray for wholesale (e.g. "6x Fruit Yoghurt 750g")
  3. 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:

ActionHowExample
UpdateUse the reference{"reference": "protein", "value": 7.5}
AddSame syntax (auto-detected){"reference": "fiber", "value": 3.0}
Removereference + "remove": true{"reference": "fat", "remove": true}

ID-based collections

line_items, packages, steps — identified by id:

ActionHowExample
UpdateInclude the id{"id": 456, "weight": 200}
AddOmit the id{"ingredient_id": 123, "weight": 100}
Removeid + "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:

ReferenceDescription
estimated_signEstimated sign (℮)
free_of_gmoFree of GMO
vegetarianVegetarian
veganVegan
halalHalal
kosherKosher
organicOrganic
gluten_freeGluten free
mscMSC certified
irradiatedIrradiated
lactose_freeLactose free
rfa_certifiedRainforest 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 typeValue format
textAny string value
numberNumeric string (e.g. "4.2")
multilanguageLocale-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 the reference field to identify records. For ID-based collections (line_items, packages, steps), include the id when updating.

Next steps