EF Endless Flavour Ingredient catalog

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

Reads are public. Writes (POST, PATCH, PUT, DELETE) will require user auth later. Until then they check an optional API key so you can lock the catalog down without changing URLs.

  • If API_WRITE_KEY is unset, writes succeed (local development).
  • If it is set, send Authorization: Bearer <key> or X-API-Key: <key>.
  • A missing or wrong key 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 and later auth

Failed validation is 400 with {"error":"bad_request","message":"…"}. Missing rows are 404. Duplicate names are rejected. When login lands, keep these paths: attach a session or JWT in Authorization and keep require_api_write as the single gate on mutating routes.