There’s one shopping list per profile — not multiple named lists. See Shopping List for how ingredients from different recipes get merged together. Every endpoint here operates on the active profile’s own list.
GET /api/shopping-list
GETAny session
Returns the active profile’s shopping list.
Response: { "shoppingList": { "recipeIds": string[], "checkedOff": string[] } }.
checkedOff holds each checked-off ingredient’s merged line key (its
name, lowercased), not a per-recipe ingredient ID.
DELETE /api/shopping-list
DELETEAny session
Clears the list — empties recipeIds and checkedOff both.
Response: { "shoppingList": { "recipeIds": [], "checkedOff": [] } }.
POST /api/shopping-list/recipes
POSTAny session
Adds a recipe to the list. Idempotent — adding the same recipe twice doesn’t duplicate it.
Request body: { "recipeId": string }
Response: { "shoppingList": ShoppingList }
DELETE /api/shopping-list/recipes/:recipeId
DELETEAny session
Removes a recipe from the list.
Response: { "shoppingList": ShoppingList }
PATCH /api/shopping-list/checked
PATCHAny session
Replaces the full set of checked-off line keys — the client always sends the complete list, not a single toggle.
Request body: { "checkedOff": string[] }
Response: { "shoppingList": ShoppingList }, or 400 if
checkedOff isn’t an array of strings.