Captures the non-obvious bits of Mealie's REST API: two-step create+patch flow, food/unit id resolution (parser returns id:null for unknown names), locale seeding to stop the parser fuzzy-matching teaspoon→tablespoon, required ingredientReferences:[] on instructions, and the family meal-type tagging convention (Breakfast/Lunch/Dinner/Dessert). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.5 KiB
name, description
| name | description |
|---|---|
| mealie | Adding and editing recipes in the family Mealie instance, including the non-obvious ingredient-parsing requirements |
-
POST /api/recipes body
{"name": "..."}Returns the slug as a raw JSON string (e.g."my-recipe"), not an object. If a recipe with the same name exists, Mealie auto-suffixes (-1,-2). To avoid duplicates, GET /api/recipes first and match by name before posting. -
PATCH /api/recipes/{slug} with the full Recipe-Input body:
name,description,prepTime,cookTime,totalTime(free strings)recipeYield(string),recipeServings(number),recipeYieldQuantity(number)recipeIngredient: [...]— see ingredient-parsing belowrecipeInstructions: [{text, ingredientReferences: []}, ...]tags: [{id, name, slug}, ...],recipeCategory: [{id, name, slug}, ...]notes: [{title, text}, ...]orgURL: "..."— source link- DO NOT include
slugin the PATCH body; it triggers a rename attempt that conflicts with the current slug and returns 400 "Recipe already exists".
DELETE /api/recipes/{slug} removes the recipe.
Mealie has an NLP parser (also `brute` and `openai` variants) that splits raw strings like `"1½ tablespoons mayo"` into structured `{quantity, unit, food, note}` objects. The unit and food are objects with their own `id` referencing rows in `/api/units` and `/api/foods`.POST /api/parser/ingredients
body: {"parser":"nlp", "ingredients":["1½ tablespoons mayo", "4 ears fresh corn, husked", ...]}
returns one ParsedIngredient per input.
Parser gotchas that bite every time:
-
Unit / food objects with
id: nullcannot be PATCHed onto a recipe — server raisesValueError: Expected 'id' to be provided for unit/food. The parser returnsid: nullwhenever the unit/food name isn't already in the database. Workflow: after parsing, resolve each name against/api/unitsand/api/foods(auto-creating missing ones via POST), then seting.unit.idanding.food.idbefore PATCHing the recipe. -
Seed the locale before parsing or the parser fuzzy-matches against whatever you created previously. Concrete bite: with only
tablespoonin the unit table, every "1 teaspoon" parses tounit_id = <tablespoon's id>because the Levenshtein distance is 1. Pre-seed once per instance:- POST /api/groups/seeders/units
{"locale":"en-US"} - POST /api/groups/seeders/foods
{"locale":"en-US"}These populate the standard US units (cup, teaspoon, tablespoon, ounce, etc.) and common foods. After seeding the parser stops conflating teaspoon/tablespoon. Already done on the family Mealie 2026-05-22.
- POST /api/groups/seeders/units
-
Sections in an ingredient list (e.g. "Cookie dough" vs "Buttercream" in the same recipe): set
titleon the FIRST ingredient of each section. Convention used in the family import script: prefix the raw input with[Section Name], the helper strips the bracket and promotes it totitleon the first ingredient of that group. -
Instructions also have a hidden required field: each step object must include
ingredientReferences: []. The OpenAPI schema says it defaults to[], but PATCH'smodel_dump(exclude_unset=True)excludes it and the SQLAlchemy model then raisesRecipeInstruction.__init__() missing 1 required positional argument: 'ingredient_references'. Always sendingredientReferences: []explicitly.
Create with POST /api/organizers/tags or POST /api/organizers/categories — body
{"name":"..."} — response contains the id and slug to reuse.
Existing IDs in the family Mealie:
- Tag
Mexican Street Eats Class=bb6841c5-3b6d-4569-b707-3d14bea83d7b - Category
Mexican=6ee64be6-83ce-4d6e-926c-62edfb791a41
- Build food + unit name→id caches once (GET /api/foods, /api/units, paginate).
- For each raw ingredient line, POST to /api/parser/ingredients.
- For each parsed result with
unit.id: nullorfood.id: null, look up by lowercased name in the cache; POST to create if missing; update cache. - For sectioned recipes, strip
[Section]prefix and settitleon first item. - Build PATCH body with
recipeIngredient,recipeInstructions(each withingredientReferences: []),tagsandrecipeCategory(with ids). - POST to /api/recipes for the name, then PATCH /api/recipes/{slug} with the body.