EF Endless Flavour

REST API

Ingredient API

Base URL: http://127.0.0.1:5001/api. JSON in and JSON out. Designed for recipe pickers on the Endless Flavour website. Machine index: GET /api.

Authentication

Anonymous reads return a sample of 10 ingredients. The full catalog needs a signed-in User, Editor, or Admin session, or an API account token. API accounts cannot sign in to the website.

  • Create an API account under Admin → Users → Add user, then send Authorization: Bearer <token> or X-API-Key: <token>.
  • The optional deployment key API_WRITE_KEY still unlocks reads and writes when no API account is used.
  • JSON writes never use a browser cookie. Use an API token (or API_WRITE_KEY).
  • A missing or wrong key on a write (or on a non-sample read) returns 401 with {"error":"unauthorized"}.

How to filter

All filters are query parameters on GET /api/ingredients (and /api/search). Combine freely. Tags are AND; cuisines are OR. Nutrition values are per 100 g. form=fresh also matches stored raw.

ParameterExampleMeaning
qtilapiaSearch text
categoryfishCategory slug, including children
cuisinedutchSingle cuisine
cuisinesfrench&cuisines=italianAny of these cuisines
tags / tagvegan&tags=high-proteinMust have every tag
exclude_allergenmilkDrop rows that contain the allergen
form / formsfreshCulinary form
min_protein, max_kcal, …min_protein=20Macro bounds per 100 g
ids / slugs12,15Exact id or slug list
estimatedtrueEstimated vs USDA-measured macros
sort, ordersort=protein&order=descname, protein, kcal, fat, fiber, updated, id
page, per_pagepage=2&per_page=50Pagination (max 100 per page)
GET /api/ingredients?category=fish&form=fresh&cuisine=ugandan
GET /api/ingredients?cuisines=dutch&cuisines=belgian&sort=name
GET /api/ingredients?tags=vegan&exclude_allergen=gluten&max_kcal=150
GET /api/ingredients?q=pepper&min_protein=15&order=desc&sort=protein

The list response is {page, per_page, pages, total, data[]}. Each card includes category, cuisines, tags, allergens, aliases, image URLs, and macros.

Ingredient CRUD

Look up by numeric id or slug. Names are unique ignoring case and surrounding space.

GET /api/ingredientsList + filter
POST /api/ingredientsCreate (201)
GET /api/ingredients/<id-or-slug>Detail (micronutrients + servings)
PATCH /api/ingredients/<id-or-slug>Partial update
PUT /api/ingredients/<id-or-slug>Replace provided fields
DELETE /api/ingredients/<id-or-slug>Delete
POST /api/ingredients
Content-Type: application/json

{
  "name": "Witlof",
  "category": "leafy-greens",
  "typical_form": "fresh",
  "description": "Belgian endive for stews and salads.",
  "cuisines": [{"slug": "belgian", "primary": true}, {"slug": "dutch"}],
  "tags": ["vegan", "fresh"],
  "allergens": [],
  "aliases": ["witloof", "Belgian endive"],
  "energy_kcal": 17,
  "protein_g": 0.9,
  "fat_g": 0.1,
  "carbs_g": 4.0,
  "sugars_g": 0.7,
  "fiber_g": 3.1,
  "sodium_mg": 2,
  "nutrition_estimated": true
}

category is a slug. cuisines, tags, allergens, and aliases replace the whole list when sent. PATCH only changes keys you include.

Reference data

Use these to populate filter chips. POST creates a row (same write-key rules). GET on a slug returns that row plus a page of ingredients.

GET  /api/categories
GET  /api/categories/fish
POST /api/categories          {"name":"Wild herbs","parent":"produce"}

GET  /api/cuisines
GET  /api/cuisines/dutch
POST /api/cuisines            {"name":"Dutch","region":"Europe"}

GET  /api/tags
POST /api/tags                {"name":"High iron","type":"nutrition"}

GET  /api/allergens
POST /api/allergens           {"name":"Lupin"}

GET  /api/forms
GET  /api/stats
GET  /api/health

Errors

Failed validation is 400 with {"error":"bad_request","message":"…"}. Missing rows are 404. Duplicate names are rejected. Website admin mutations require a verified admin session; JSON mutations still go through require_api_write.