API Service Contracts Demo 1 - COS301-SE-2026/Mealchemy GitHub Wiki
-
Mealchemy API Service Contracts
- Table of Contents
- 1. Authentication Services
- 2. Preference Services:
- 3. Vault Services:
- 4. Vault Folder Services:
- 5. Vault Folder Recipe Services
- 9. Pantry Services:
-
10. Shopping List Services:
- Get Shopping Lists By User
- Get Shopping List By ID
- Create Shopping List
- Update Shopping List
- Delete Shopping List
- Add Shopping List Item
- Update Shopping List Item
- Update Shopping List Item Purchased Status
- Delete Shopping List Item
- Batch Delete Shopping List Items
- Select All Shopping List Items
- Deselect All Shopping List Items
- Complete Shop
- Create Shopping List From Recipe
Description: Registers a new user account and returns an authentication token.
Endpoint: POST /auth/register
Inputs:
| Name | Type | Description |
|---|---|---|
| string | A valid, non-blank email address | |
| password | string | User's password, minimum 8 characters |
| displayName | string | User's display name, max 80 characters |
Outputs:
| Name | Type | Description |
|---|---|---|
| user_id | integer | The newly created user's unique identifier |
| token | string | Access token for authenticated requests |
| onboarding_required | boolean | Whether the user still needs to complete onboarding |
Usage / Interaction Rules:
- Send a POST request to /auth/register with email, password, and displayName in JSON format
- Returns 201 Created on success
- Returns 400 Bad Request if validation fails (e.g. invalid email)
Description: Authenticates an existing user and returns an access token.
Endpoint: POST /auth/login
Inputs:
| Name | Type | Description |
|---|---|---|
| string | A valid, non-blank email address | |
| password | string | User's non-blank password |
Outputs:
| Name | Type | Description |
|---|---|---|
| user_id | integer | The authenticated user's unique identifier |
| token | string | Access token for authenticated requests |
| onboarding_required | boolean | Whether the user still needs to complete onboarding |
Usage / Interaction Rules:
- Send a POST request to /auth/login with email and password in JSON format
- Returns 200 OK on success
- Returns 401 Unauthorized if credentials are invalid
Description: Terminates the current user's authenticated session.
Endpoint: POST /auth/logout
Inputs: None
Outputs: None
Usage / Interaction Rules:
- Send a POST request to /auth/logout - no request body required
- Returns 204 No Content on success
Description: Retrieves the dietary preferences and restrictions for the authenticated user.
Endpoint: GET /user/preferences
Inputs:
| Name | Type | Description |
|---|---|---|
| userId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
Outputs:
| Name | Type | Description |
|---|---|---|
| dietary_restrictions | List<string> | User's dietary restrictions (e.g. vegan, halal) |
| allergies | List<string> | User's listed allergies |
| disliked_ingredients | List<string> | Ingredients the user wants excluded |
| flavour_profile | List<string> | User's preferred flavour profiles |
Usage / Interaction Rules:
- Send a GET request to /user/preferences - no request body required
- Caller must be authenticated; JWT token must be present in the request header
- Returns 200 OK with the user's current preferences
Description: Replaces the authenticated user's dietary preferences with the provided values.
Endpoint: PUT /user/preferences
Inputs:
| Name | Type | Description |
|---|---|---|
| userId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
| dietary_restrictions | List<string> | Updated dietary restrictions |
| allergies | List<string> | Updated allergies |
| disliked_ingredients | List<string> | Updated disliked ingredients |
| flavour_profile | List<string> | Updated flavour profiles |
Outputs:
| Name | Type | Description |
|---|---|---|
| dietary_restrictions | List<string> | Saved dietary restrictions |
| allergies | List<string> | Saved allergies |
| disliked_ingredients | List<string> | Saved disliked ingredients |
| flavour_profile | List<string> | Saved flavour profiles |
Usage / Interaction Rules:
- Send a PUT request to /user/preferences with a JSON body containing any combination of the four preference fields
- Caller must be authenticated; JWT token must be present in the request header
- Returns 200 OK with the full updated preferences object
Description: Retrieves all vaults belonging to a specific owner.
Endpoint: GET /vaults/owner/{ownerId}
Inputs:
| Name | Type | Description |
|---|---|---|
| ownerId | integer | Path variable identifying the owner |
Outputs:
| Name | Type | Description |
|---|---|---|
| vault_id | integer | Unique vault identifier |
| owner_id | integer | ID of the vault's owner |
| vault_type | VaultType (enum) | Type/category of the vault |
| name | string | Name of the vault |
| created_at | OffsetDateTime | Timestamp of vault creation |
Usage / Interaction Rules:
- Send a GET request to /vaults/owner/{ownerId}
- Returns 200 OK with a list of vaults
Description: Retrieves a single vault by its unique identifier.
Endpoint: GET /vaults/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the vault |
Outputs:
| Name | Type | Description |
|---|---|---|
| vault_id | integer | Unique vault identifier |
| owner_id | integer | ID of the vault's owner |
| vault_type | VaultType (enum) | Type/category of the vault |
| name | string | Name of the vault |
| created_at | OffsetDateTime | Timestamp of vault creation |
Usage / Interaction Rules:
- Send a GET request to /vaults/{id}
- Returns 200 OK with the vault object
Description: Creates a new vault for a given owner.
Endpoint: POST /vaults
Inputs:
| Name | Type | Description |
|---|---|---|
| owner_id | integer | The owner of the vault |
| vault_type | VaultType (enum) | Type/category of the vault |
| name | string | Name of the vault |
Outputs:
| Name | Type | Description |
|---|---|---|
| vault_id | integer | Unique vault identifier |
| owner_id | integer | ID of the vault's owner |
| vault_type | VaultType (enum) | Type/category of the vault |
| name | string | Name of the vault |
| created_at | OffsetDateTime | Timestamp of vault creation |
Usage / Interaction Rules:
- Send a POST request to /vaults with owner_id, vault_type, and name in JSON format
- Returns 200 OK with the created vault
Description: Replaces an existing vault's details with the provided values.
Endpoint: PUT /vaults/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the vault to update |
| owner_id | integer | Updated owner ID |
| vault_type | VaultType (enum) | Updated vault type |
| name | string | Updated vault name |
Outputs:
| Name | Type | Description |
|---|---|---|
| vault_id | integer | Unique vault identifier |
| owner_id | integer | ID of the vault's owner |
| vault_type | VaultType (enum) | Type/category of the vault |
| name | string | Name of the vault |
| created_at | OffsetDateTime | Timestamp of vault creation |
Usage / Interaction Rules:
- Send a PUT request to /vaults/{id} with updated fields in JSON format
- Returns 200 OK with the updated vault
Description: Permanently deletes a vault by its ID.
Endpoint: DELETE /vaults/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the vault to delete |
Outputs: None
Usage / Interaction Rules:
- Send a DELETE request to /vaults/{id}
- Returns 200 OK (no body)
Description: Retrieves all folders within a specific vault.
Endpoint: GET /folders/vault/{vaultId}
Inputs:
| Name | Type | Description |
|---|---|---|
| vaultId | integer | Path variable identifying the vault |
Outputs:
| Name | Type | Description |
|---|---|---|
| folder_id | integer | Unique folder identifier |
| vault_id | integer | ID of the parent vault |
| folder_name | string | Name of the folder |
| created_at | OffsetDateTime | Timestamp of folder creation |
Usage / Interaction Rules:
- Send a GET request to /folders/vault/{vaultId}
- Returns 200 OK with a list of folders
Description: Retrieves a single vault folder by its name.
Endpoint: GET /folders/folder/name/{name}
Inputs:
| Name | Type | Description |
|---|---|---|
| name | string | Path variable for the folder name |
Outputs:
| Name | Type | Description |
|---|---|---|
| folder_id | integer | Unique folder identifier |
| vault_id | integer | ID of the parent vault |
| folder_name | string | Name of the folder |
| created_at | OffsetDateTime | Timestamp of folder creation |
Usage / Interaction Rules:
- Send a GET request to /folders/folder/name/{name}
- Returns 200 OK with the matched folder
Description: Retrieves a single vault folder by its unique identifier.
Endpoint: GET /folders/folder/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the folder |
Outputs:
| Name | Type | Description |
|---|---|---|
| folder_id | integer | Unique folder identifier |
| vault_id | integer | ID of the parent vault |
| folder_name | string | Name of the folder |
| created_at | OffsetDateTime | Timestamp of folder creation |
Usage / Interaction Rules:
- Send a GET request to /folders/folder/{id}
- Returns 200 OK with the folder object
Description: Creates a new folder within a specified vault.
Endpoint: POST /folders
Inputs:
| Name | Type | Description |
|---|---|---|
| vault_id | integer | The vault to create the folder in |
| folder_name | string | Name of the new folder |
Outputs:
| Name | Type | Description |
|---|---|---|
| folder_id | integer | Unique folder identifier |
| vault_id | integer | ID of the parent vault |
| folder_name | string | Name of the folder |
| created_at | OffsetDateTime | Timestamp of folder creation |
Usage / Interaction Rules:
- Send a POST request to /folders with vault_id and folder_name in JSON format
- Returns 200 OK with the created folder
Description: Updates an existing folder's details.
Endpoint: PUT /folders/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the folder to update |
| vault_id | integer | Updated parent vault ID |
| folder_name | string | Updated folder name |
Outputs:
| Name | Type | Description |
|---|---|---|
| folder_id | integer | Unique folder identifier |
| vault_id | integer | ID of the parent vault |
| folder_name | string | Name of the folder |
| created_at | OffsetDateTime | Timestamp of folder creation |
Usage / Interaction Rules:
- Send a PUT request to /folders/{id} with updated fields in JSON format
- Returns 200 OK with the updated folder
Description: Permanently deletes a folder by its ID.
Endpoint: DELETE /folders/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the folder to delete |
Outputs: None
Usage / Interaction Rules:
- Send a DELETE request to /folders/{id}
- Returns 200 OK (no body)
Description: Retrieves all recipe entries associated with a specific folder.
Endpoint: GET /recipefolders/recipes/{folderId}
Inputs:
| Name | Type | Description |
|---|---|---|
| folderId | integer | Path variable identifying the folder |
Outputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Unique ID of the association record |
| folder_id | integer | The folder the recipe belongs to |
| recipe_id | integer | The linked recipe |
| added_at | OffsetDateTime | When the recipe was added to the folder |
Usage / Interaction Rules:
- Send a GET request to /recipefolders/recipes/{folderId}
- Returns 200 OK with a list of recipe-folder associations
Description: Retrieves all folder entries that contain a specific recipe.
Endpoint: GET /recipefolders/folders/{recipeId}
Inputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Path variable identifying the recipe |
Outputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Unique ID of the association record |
| folder_id | integer | The folder the recipe belongs to |
| recipe_id | integer | The linked recipe |
| added_at | OffsetDateTime | When the recipe was added to the folder |
Usage / Interaction Rules:
- Send a GET request to /recipefolders/folders/{recipeId}
- Returns 200 OK with a list of folder-recipe associations
Description: Retrieves a single folder-recipe association by its ID.
Endpoint: GET /recipefolders/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the association record |
Outputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Unique ID of the association record |
| folder_id | integer | The folder the recipe belongs to |
| recipe_id | integer | The linked recipe |
| added_at | OffsetDateTime | When the recipe was added to the folder |
Usage / Interaction Rules:
- Send a GET request to /recipefolders/{id}
- Returns 200 OK
Description: Links a recipe to a folder by creating a new association record.
Endpoint: POST /recipefolders
Inputs:
| Name | Type | Description |
|---|---|---|
| folder_id | integer | The folder to link to |
| recipe_id | integer | The recipe to link |
Outputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Unique ID of the association record |
| folder_id | integer | The folder the recipe belongs to |
| recipe_id | integer | The linked recipe |
| added_at | OffsetDateTime | When the recipe was added to the folder |
Usage / Interaction Rules:
- Send a POST request to /recipefolders with folder_id and recipe_id in JSON format
- Returns 200 OK with the created association record
Description: Updates an existing folder-recipe association.
Endpoint: PUT /recipefolders/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the association to update |
| folder_id | integer | Updated folder ID |
| recipe_id | integer | Updated recipe ID |
Outputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Unique ID of the association record |
| folder_id | integer | The folder the recipe belongs to |
| recipe_id | integer | The linked recipe |
| added_at | OffsetDateTime | When the recipe was added to the folder |
Usage / Interaction Rules:
- Send a PUT request to /recipefolders/{id} with updated fields in JSON format
- Returns 200 OK with the updated association record
Description: Removes a folder-recipe association by its ID.
Endpoint: DELETE /recipefolders/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the association to delete |
Outputs: None
Usage / Interaction Rules:
- Send a DELETE request to /recipefolders/{id}
- Returns 200 OK (no body)
Description: Retrieves all recipes
Endpoint: GET /recipes/all
Inputs: None
Outputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Unique recipe identifier |
| ownerId | integer | ID of the recipe's owner |
| title | string | Recipe title |
| description | string | null |
| cuisineType | string | Cuisine category, validated against Flavour Profile Options |
| prepTimeMins | integer | Preparation time in minutes |
| cookingTimeMins | integer | Cooking time in minutes |
| servingSize | integer | Number of servings |
| photoUrl | string | null |
| videoUrl | string | null |
| externalUrl | string | null |
| isCommunityPublished | boolean | Whether the recipe is visible to the community |
| createdAt | OffsetDateTime | Timestamp of recipe creation |
| updatedAt | OffsetDateTime | Timestamp of last update |
| parentRecipeId | integer | ID of the source recipe this was copied from, if any |
Usage / Interaction Rules:
- Send a GET request to /recipes/all - no request body
- Returns 200 OK with a list of all recipes
Description: Retrieves one recipe by ID.
Endpoint: GET /recipes/single/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the recipe |
Outputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Unique recipe identifier |
| ownerId | integer | ID of the recipe's owner |
| title | string | Recipe title |
| description | string | null |
| cuisineType | string | Cuisine category, validated against Flavour Profile Options |
| prepTimeMins | integer | Preparation time in minutes |
| cookingTimeMins | integer | Cooking time in minutes |
| servingSize | integer | Number of servings |
| photoUrl | string | null |
| videoUrl | string | null |
| externalUrl | string | null |
| isCommunityPublished | boolean | Whether the recipe is visible to the community |
| createdAt | OffsetDateTime | Timestamp of recipe creation |
| updatedAt | OffsetDateTime | Timestamp of last update |
| parentRecipeId | integer | ID of the source recipe this was copied from, if any |
Usage / Interaction Rules:
- Send a GET request to /recipes/single/{id} - no request body
- Returns 200 OK with the recipe object
Description: Creates a bare recipe with no ingredients or steps.
Endpoint: POST /recipes/create
Inputs:
| Name | Type | Description |
|---|---|---|
| ownerId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
| title | string | Recipe title, required, not blank |
| description | string | Optional recipe description |
| cuisineType | string | Required, not blank; must match an existing Flavour Profile Options value |
| prepTimeMins | integer | Required |
| cookingTimeMins | integer | Required |
| servingSize | integer | Required |
| photoUrl | string | Optional |
| videoUrl | string | Optional |
| externalUrl | string | Optional |
| isCommunityPublished | boolean | Optional |
Outputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Unique recipe identifier |
| ownerId | integer | ID of the recipe's owner |
| title | string | Recipe title |
| description | string | null |
| cuisineType | string | Cuisine category, validated against Flavour Profile Options |
| prepTimeMins | integer | Preparation time in minutes |
| cookingTimeMins | integer | Cooking time in minutes |
| servingSize | integer | Number of servings |
| photoUrl | string | null |
| videoUrl | string | null |
| externalUrl | string | null |
| isCommunityPublished | boolean | Whether the recipe is visible to the community |
| createdAt | OffsetDateTime | Timestamp of recipe creation |
| updatedAt | OffsetDateTime | Timestamp of last update |
| parentRecipeId | integer | ID of the source recipe this was copied from, if any |
Usage / Interaction Rules:
- Send a POST request to /recipes/create
- owner is inferred from the JWT/session and is not accepted in the request body
- Returns 400 Bad Request if cuisineType is invalid
- Returns 200 OK with the created recipe
Description: Creates a complete recipe including ingredients and steps in one request. Can optionally be linked to a source recipe (e.g. when copying/forking an existing recipe).
Endpoint: POST /recipes/{sourceId}/copy
Inputs:
| Name | Type | Description |
|---|---|---|
| sourceId | integer | Path variable for the source recipe being copied from, if any |
| ownerId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
| title | string | Required, not blank |
| description | string | Optional |
| cuisineType | string | Required, not blank |
| prepTimeMins | integer | Required |
| cookingTimeMins | integer | Required |
| servingSize | integer | Required |
| photoUrl | string | Optional |
| videoUrl | string | Optional |
| externalUrl | string | Optional |
| isCommunityPublished | boolean | Optional |
| ingredients | List<object> | Required, non-empty. Each item: ingId (integer, required), quantity (decimal, required), unit (string, required, not blank), sortOrder (integer, required) |
| steps | List<object> | Required, non-empty. Each item: stepNr (integer, required, min 1), content (string, required, not blank) |
Outputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Unique recipe identifier |
| ownerId | integer | ID of the recipe's owner |
| title | string | Recipe title |
| description | string | null |
| cuisineType | string | Cuisine category, validated against Flavour Profile Options |
| prepTimeMins | integer | Preparation time in minutes |
| cookingTimeMins | integer | Cooking time in minutes |
| servingSize | integer | Number of servings |
| photoUrl | string | null |
| videoUrl | string | null |
| externalUrl | string | null |
| isCommunityPublished | boolean | Whether the recipe is visible to the community |
| createdAt | OffsetDateTime | Timestamp of recipe creation |
| updatedAt | OffsetDateTime | Timestamp of last update |
| parentRecipeId | integer | ID of the source recipe this was copied from, if any |
Usage / Interaction Rules:
- Send a POST request to /recipes/{sourceId}/copy
- ingredients and steps must each contain at least one entry
- Returns 400 Bad Request if cuisineType is invalid
- Returns 400 Bad Request if one of the supplied ingredients doesn't exist in the catalogue
- Returns 200 OK with the created recipe
Description: Updates a recipe's top-level fields. Does not modify ingredients or steps.
Endpoint: PUT /recipes/edit/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the recipe to update |
| ownerId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
| title | string | Recipe title, required, not blank |
| description | string | Optional recipe description |
| cuisineType | string | Required, not blank; must match an existing Flavour Profile Options value |
| prepTimeMins | integer | Required |
| cookingTimeMins | integer | Required |
| servingSize | integer | Required |
| photoUrl | string | Optional |
| videoUrl | string | Optional |
| externalUrl | string | Optional |
| isCommunityPublished | boolean | Optional |
Outputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Unique recipe identifier |
| ownerId | integer | ID of the recipe's owner |
| title | string | Recipe title |
| description | string | null |
| cuisineType | string | Cuisine category, validated against Flavour Profile Options |
| prepTimeMins | integer | Preparation time in minutes |
| cookingTimeMins | integer | Cooking time in minutes |
| servingSize | integer | Number of servings |
| photoUrl | string | null |
| videoUrl | string | null |
| externalUrl | string | null |
| isCommunityPublished | boolean | Whether the recipe is visible to the community |
| createdAt | OffsetDateTime | Timestamp of recipe creation |
| updatedAt | OffsetDateTime | Timestamp of last update |
| parentRecipeId | integer | ID of the source recipe this was copied from, if any |
Usage / Interaction Rules:
- Send a PUT request to /recipes/edit/{id}
- User can only update recipes they own
- Returns 403 Forbidden if the caller doesn't own the recipe
- Returns 400 Bad Request if cuisineType is invalid
- Returns 200 OK with the updated recipe
Description: Deletes a recipe owned by the logged-in user.
Endpoint: DELETE /recipes/delete/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the recipe to delete |
Outputs: None
Usage / Interaction Rules:
- Send a DELETE request to /recipes/delete/{id}
- User can only delete recipes they own
- Returns 403 Forbidden if the caller doesn't own the recipe
- Returns 204 No Content
Description: Adds an ingredient line to a recipe.
Endpoint: POST /ingredients/recipe/{recipeId}/ingredient/create
Inputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Path variable for the parent recipe |
| ownerId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
| ingId | integer | Required; must exist in the ingredient catalogue |
| quantity | decimal | Required |
| unit | string | Required, not blank |
| sortOrder | integer | Required |
Outputs:
| Name | Type | Description |
|---|---|---|
| ingredientId | integer | Unique recipe-ingredient line identifier |
| recipeId | integer | ID of the parent recipe |
| ingId | integer | Linked ingredient catalogue ID |
| quantity | decimal | Quantity amount |
| unit | string | Unit of measurement |
| sortOrder | integer | Display order within the recipe |
Usage / Interaction Rules:
- Send a POST request to /ingredients/recipe/{recipeId}/ingredient/create
- User can only modify ingredients on recipes they own
- Returns 403 Forbidden if the caller doesn't own the recipe
- Returns 400 Bad Request if the supplied ingId doesn't exist in the catalogue
- Returns 200 OK with the created ingredient line
Description: Updates an existing ingredient line on a recipe.
Endpoint: PUT /ingredients/recipe/{recipeId}/ingredient/{id}/edit
Inputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Path variable for the parent recipe |
| id | integer | Path variable for the ingredient line to update |
| ingId | integer | Required; must exist in the ingredient catalogue |
| quantity | decimal | Required |
| unit | string | Required, not blank |
| sortOrder | integer | Required |
Outputs:
| Name | Type | Description |
|---|---|---|
| ingredientId | integer | Unique recipe-ingredient line identifier |
| recipeId | integer | ID of the parent recipe |
| ingId | integer | Linked ingredient catalogue ID |
| quantity | decimal | Quantity amount |
| unit | string | Unit of measurement |
| sortOrder | integer | Display order within the recipe |
Usage / Interaction Rules:
- Send a PUT request to /ingredients/recipe/{recipeId}/ingredient/{id}/edit
- User can only modify ingredients on recipes they own
- Returns 403 Forbidden if the caller doesn't own the recipe
- Returns 403 Forbidden if the ingredient line doesn't belong to the specified recipe
- Returns 400 Bad Request if the new ingId doesn't exist in the catalogue
- Returns 200 OK with the updated ingredient line
Description: Removes an ingredient line from a recipe.
Endpoint: DELETE /ingredients/recipe/{recipeId}/ingredient/{id}/delete
Inputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Path variable for the parent recipe |
| id | integer | Path variable for the ingredient line to delete |
Outputs: None
Usage / Interaction Rules:
- Send a DELETE request to /ingredients/recipe/{recipeId}/ingredient/{id}/delete
- User can only modify ingredients on recipes they own
- Returns 403 Forbidden if the caller doesn't own the recipe
- Returns 403 Forbidden if the ingredient line doesn't belong to the specified recipe
- Returns 204 No Content
Description: Adds a step to a recipe.
Endpoint: POST /steps/recipe/{recipeId}/step/create
Inputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Path variable for the parent recipe |
| stepNr | integer | Required, min 1 |
| content | string | Required, not blank |
Outputs:
| Name | Type | Description |
|---|---|---|
| stepId | integer | Unique step identifier |
| recipeId | integer | ID of the parent recipe |
| stepNr | integer | Step number/order |
| content | string | Instruction text |
Usage / Interaction Rules:
- Send a POST request to /steps/recipe/{recipeId}/step/create
- User can only modify steps on recipes they own
- Returns 403 Forbidden if the caller doesn't own the recipe
- Returns 200 OK with the created step
Description: Updates an existing step.
Endpoint: PUT /steps/recipe/{recipeId}/step/{id}/edit
Inputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Path variable for the parent recipe |
| id | integer | Path variable for the step to update |
| stepNr | integer | Required, min 1 |
| content | string | Required, not blank |
Outputs: Same shape as Add Recipe Step
Usage / Interaction Rules:
- Send a PUT request to /steps/recipe/{recipeId}/step/{id}/edit
- User can only modify steps on recipes they own
- Returns 403 Forbidden if the caller doesn't own the recipe
- Returns 403 Forbidden if the step doesn't belong to the specified recipe
- Returns 200 OK with the updated step
Description: Removes a step from a recipe.
Endpoint: DELETE /steps/recipe/{recipeId}/step/{id}/delete
Inputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Path variable for the parent recipe |
| id | integer | Path variable for the step to delete |
Outputs: None
Usage / Interaction Rules:
- Send a DELETE request to /steps/recipe/{recipeId}/step/{id}/delete
- User can only modify steps on recipes they own
- Returns 403 Forbidden if the caller doesn't own the recipe
- Returns 403 Forbidden if the step doesn't belong to the specified recipe
- Returns 204 No Content
Description: Retrieves all pantry items belonging to the logged-in user.
Endpoint: GET /api/pantry
Inputs:
| Name | Type | Description |
|---|---|---|
| userId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
Outputs:
| Name | Type | Description |
|---|---|---|
| p_ingredient_id | integer | Unique pantry ingredient identifier |
| ing_id | integer | Linked ingredient catalogue ID |
| name | string | Ingredient name |
| category | string | Ingredient category (e.g. pasta, rice, spice, dairy, vegetable) |
| quantity | decimal | Current quantity available in the pantry |
| unit | string | Unit of measurement (e.g. g, kg, ml, cup, item) |
| created_at | OffsetDateTime | Timestamp when the pantry item was created |
| updated_at | OffsetDateTime | Timestamp when the pantry item was last updated |
Usage / Interaction Rules:
- Send a GET request to /api/pantry - no request body required
- Returns 200 OK with a list of pantry items
Description: Adds a new ingredient to the logged-in user's pantry.
Endpoint: POST /api/pantry
Inputs:
| Name | Type | Description |
|---|---|---|
| userId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
| ing_id | integer | Ingredient catalogue ID, required |
| quantity | decimal | Quantity to add, should be greater than zero |
| unit | string | Unit of measurement |
Outputs:
| Name | Type | Description |
|---|---|---|
| p_ingredient_id | integer | Unique pantry ingredient identifier |
| ing_id | integer | Linked ingredient catalogue ID |
| name | string | Ingredient name |
| category | string | Ingredient category |
| quantity | decimal | Current quantity available in the pantry |
| unit | string | Unit of measurement |
| created_at | OffsetDateTime | Timestamp when the pantry item was created |
| updated_at | OffsetDateTime | Timestamp when the pantry item was last updated |
Usage / Interaction Rules:
- Send a POST request to /api/pantry
- name is required and quantity should be greater than zero
- Returns 200 OK with the created pantry item
Description: Updates an existing pantry item.
Endpoint: PUT /api/pantry/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the pantry item to update |
| ing_id | integer | Ingredient catalogue ID |
| quantity | decimal | Updated quantity |
| unit | string | Updated unit of measurement |
Outputs:
| Name | Type | Description |
|---|---|---|
| p_ingredient_id | integer | Unique pantry ingredient identifier |
| ing_id | integer | Linked ingredient catalogue ID |
| name | string | Ingredient name |
| category | string | Ingredient category |
| quantity | decimal | Current quantity available in the pantry |
| unit | string | Unit of measurement |
| created_at | OffsetDateTime | Timestamp when the pantry item was created |
| updated_at | OffsetDateTime | Timestamp when the pantry item was last updated |
Usage / Interaction Rules:
- Send a PUT request to /api/pantry/{id}
- User can only update their own pantry items
- Quantity changes should be reflected immediately in recipe generation and shopping-list logic
- Returns 200 OK with the updated pantry item
- If a quantity is updated to zero, the pantry item is deleted instead of being updated, and returns 204 No Content
Description: Deletes a pantry item from the logged-in user's pantry.
Endpoint: DELETE /api/pantry/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the pantry item to delete |
Outputs: None
Usage / Interaction Rules:
- Send a DELETE request to /api/pantry/{id}
- User can only delete their own pantry items
- Deleted pantry items should no longer appear in pantry lists
- Returns 204 No Content
Description: Searches the logged-in user's pantry by ingredient name.
Endpoint: GET /api/pantry/search?q={query}
Inputs:
| Name | Type | Description |
|---|---|---|
| q | string | Query parameter for the ingredient name to search |
Outputs:
| Name | Type | Description |
|---|---|---|
| p_ingredient_id | integer | Unique pantry ingredient identifier |
| ing_id | integer | Linked ingredient catalogue ID |
| name | string | Ingredient name |
| category | string | Ingredient category |
| quantity | decimal | Current quantity available in the pantry |
| unit | string | Unit of measurement |
| created_at | OffsetDateTime | Timestamp when the pantry item was created |
| updated_at | OffsetDateTime | Timestamp when the pantry item was last updated |
Usage / Interaction Rules:
- Send a GET request to /api/pantry/search?q={query} - no request body required
- Returns 200 OK with a list of matching pantry items scoped to the logged-in user's pantry
Description: Retrieves all shopping lists belonging to the logged-in user.
Endpoint: GET /api/shopping-lists
Inputs:
| Name | Type | Description |
|---|---|---|
| userId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
Outputs:
| Name | Type | Description |
|---|---|---|
| shopping_list_id | integer | Unique shopping list identifier |
| user_id | integer | ID of the list's owner |
| name | string | Name of the shopping list |
| status | ShoppingListStatus (enum) | ACTIVE or COMPLETED |
| created_at | OffsetDateTime | Timestamp of list creation |
Usage / Interaction Rules:
- Send a GET request to /api/shopping-lists - no request body required
- Returns 200 OK with a list of shopping lists
Description: Retrieves a single shopping list, including its items.
Endpoint: GET /api/shopping-lists/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the shopping list |
Outputs:
| Name | Type | Description |
|---|---|---|
| shopping_list_id | integer | Unique shopping list identifier |
| user_id | integer | ID of the list's owner |
| name | string | Name of the shopping list |
| status | ShoppingListStatus (enum) | ACTIVE or COMPLETED |
| created_at | OffsetDateTime | Timestamp of list creation |
| items | List<ShoppingListItem> | Items belonging to the list |
Usage / Interaction Rules:
- Send a GET request to /api/shopping-lists/{id}
- User can only retrieve their own shopping lists
- Returns 200 OK with the shopping list object and its items
Description: Creates a new shopping list for the logged-in user.
Endpoint: POST /api/shopping-lists
Inputs:
| Name | Type | Description |
|---|---|---|
| userId | string | Extracted automatically from the JWT token via @AuthenticationPrincipal |
| name | string | Name of the shopping list, required, max 200 characters |
| status | ShoppingListStatus (enum) | Optional, defaults to ACTIVE |
Outputs:
| Name | Type | Description |
|---|---|---|
| shopping_list_id | integer | Unique shopping list identifier |
| user_id | integer | ID of the list's owner |
| name | string | Name of the shopping list |
| status | ShoppingListStatus (enum) | ACTIVE or COMPLETED |
| created_at | OffsetDateTime | Timestamp of list creation |
Usage / Interaction Rules:
- Send a POST request to /api/shopping-lists
- Owner is inferred from the JWT/session and is not accepted in the request body
- Returns 200 OK with the created shopping list
Description: Updates an existing shopping list's information.
Endpoint: PUT /api/shopping-lists/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the shopping list to update |
| name | string | Updated name, cannot be blank |
| status | ShoppingListStatus (enum) | Updated status, must be a valid enum value |
Outputs:
| Name | Type | Description |
|---|---|---|
| shopping_list_id | integer | Unique shopping list identifier |
| user_id | integer | ID of the list's owner |
| name | string | Name of the shopping list |
| status | ShoppingListStatus (enum) | ACTIVE or COMPLETED |
| created_at | OffsetDateTime | Timestamp of list creation |
Usage / Interaction Rules:
- Send a PUT request to /api/shopping-lists/{id}
- User can only update their own shopping lists
- Returns 200 OK with the updated shopping list
Description: Permanently deletes a shopping list by its ID.
Endpoint: DELETE /api/shopping-lists/{id}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the shopping list to delete |
Outputs: None
Usage / Interaction Rules:
- Send a DELETE request to /api/shopping-lists/{id}
- User can only delete their own shopping lists
- Deleting a shopping list also removes its related shopping list items via ON DELETE CASCADE
- Returns 204 No Content
Description: Adds a new item to an existing shopping list.
Endpoint: POST /api/shopping-lists/{id}/items
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the parent shopping list |
| ing_id | integer | Optional ingredient catalogue ID (either ing_id or name must be provided, not both) |
| name | string | Manually entered item name (used only when ing_id is null) |
| quantity | decimal | Optional quantity amount |
| unit | string | Optional unit of measurement |
| purchased | boolean | Optional, defaults to false |
Outputs:
| Name | Type | Description |
|---|---|---|
| item_id | integer | Unique shopping list item identifier |
| shopping_list_id | integer | ID of the parent shopping list |
| ing_id | integer | Linked ingredient catalogue ID, or null |
| name | string | Item name |
| category | string | Ingredient category, resolved from catalogue but always null for manual items |
| quantity | decimal | Quantity amount |
| unit | string | Unit of measurement |
| purchased | boolean | Purchased state |
Usage / Interaction Rules:
- Send a POST request to /api/shopping-lists/{id}/items with item fields in JSON format
- User can only add items to their own shopping lists
- Either ing_id or name must be provided (providing neither or both returns 400 Bad Request)
- Returns 200 OK with the created item
Description: Updates an existing item on a shopping list.
Endpoint: PUT /api/shopping-lists/{id}/items/{itemId}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the parent shopping list |
| itemId | integer | Path variable for the item to update |
| ing_id | integer | Optional ingredient catalogue ID (either ing_id or name must be provided, not both) |
| name | string | Manually entered item name; used only when ing_id is null |
| quantity | decimal | Updated quantity amount |
| unit | string | Updated unit of measurement |
| purchased | boolean | Updated purchased state |
Outputs:
| Name | Type | Description |
|---|---|---|
| item_id | integer | Unique shopping list item identifier |
| shopping_list_id | integer | ID of the parent shopping list |
| ing_id | integer | Linked ingredient catalogue ID, or null |
| name | string | Item name |
| category | string | Ingredient category, resolved from catalogue (always null for manual items) |
| quantity | decimal | Quantity amount |
| unit | string | Unit of measurement |
| purchased | boolean | Purchased state |
Usage / Interaction Rules:
- Send a PUT request to /api/shopping-lists/{id}/items/{itemId} with updated fields in JSON format
- User can only update items on their own shopping lists
- Either ing_id or name must be provided (providing neither or both returns 400 Bad Request)
- The item must belong to the shopping list specified in the URL path, or the request returns 404 Not Found
- Returns 200 OK with the updated item
Description: Updates the purchased state for one shopping list item.
Endpoint: PATCH /api/shopping-lists/{id}/items/{itemId}/purchased
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the parent shopping list |
| itemId | integer | Path variable for the item to update |
| purchased | boolean | New purchased state |
Outputs:
| Name | Type | Description |
|---|---|---|
| item_id | integer | Unique shopping list item identifier |
| shopping_list_id | integer | ID of the parent shopping list |
| ing_id | integer | Linked ingredient catalogue ID, or null |
| name | string | Item name |
| category | string | Ingredient category, resolved from catalogue (always null for manual items) |
| quantity | decimal | Quantity amount |
| unit | string | Unit of measurement |
| purchased | boolean | Updated purchased state |
Usage / Interaction Rules:
- Send a PATCH request to /api/shopping-lists/{id}/items/{itemId}/purchased
- User can only update purchased state for items on their own shopping lists
- This endpoint does not update pantry quantities by itself. Use PUT /api/shopping-lists/{id}/complete-shop for that
- Returns 200 OK with the updated item
Description: Deletes one item from a shopping list.
Endpoint: DELETE /api/shopping-lists/{id}/items/{itemId}
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the parent shopping list |
| itemId | integer | Path variable for the item to delete |
Outputs: None
Usage / Interaction Rules:
- Send a DELETE request to /api/shopping-lists/{id}/items/{itemId}
- User can only delete items from their own shopping lists
- Returns 204 No Content
Description: Deletes multiple selected items from a shopping list at once.
Endpoint: POST /api/shopping-lists/{id}/items/batch-delete
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the parent shopping list |
| item_ids | List<integer> | IDs of the items to delete |
Outputs: None
Usage / Interaction Rules:
- Send a POST request to /api/shopping-lists/{id}/items/batch-delete
- User can only delete items from their own shopping lists
- All item_ids must belong to the shopping list specified in the URL path, if any item is not found or belongs to a different list, the request fails with 404 Not Found and no items are deleted
- Returns 204 No Content
Description: Marks every item on a shopping list as purchased.
Endpoint: PUT /api/shopping-lists/{id}/items/select-all
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the shopping list |
Outputs:
| Name | Type | Description |
|---|---|---|
| shopping_list_id | integer | Unique shopping list identifier |
| user_id | integer | ID of the list's owner |
| name | string | Name of the shopping list |
| status | ShoppingListStatus (enum) | ACTIVE or COMPLETED |
| created_at | OffsetDateTime | Timestamp of list creation |
| items | List<ShoppingListItem> | Items belonging to the list, all with purchased: true |
Usage / Interaction Rules:
- Send a PUT request to /api/shopping-lists/{id}/items/select-all - no request body required
- User can only select all items on their own shopping lists
- Every item on the list is marked purchased: true, regardless of its prior state
- Returns 200 OK with the full shopping list and updated items
Description: Marks every item on a shopping list as not purchased.
Endpoint: PUT /api/shopping-lists/{id}/items/deselect-all
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the shopping list |
Outputs:
| Name | Type | Description |
|---|---|---|
| shopping_list_id | integer | Unique shopping list identifier |
| user_id | integer | ID of the list's owner |
| name | string | Name of the shopping list |
| status | ShoppingListStatus (enum) | ACTIVE or COMPLETED |
| created_at | OffsetDateTime | Timestamp of list creation |
| items | List<ShoppingListItem> | Items belonging to the list, all with purchased: false |
Usage / Interaction Rules:
- Send a PUT request to /api/shopping-lists/{id}/items/deselect-all - no request body required
- User can only deselect all items on their own shopping lists
- Every item on the list is marked purchased: false, regardless of its prior state
- Returns 200 OK with the full shopping list and updated items
Description: Moves all purchased, catalogue-linked items into the user's pantry and removes them from the shopping list.
Endpoint: PUT /api/shopping-lists/{id}/complete-shop
Inputs:
| Name | Type | Description |
|---|---|---|
| id | integer | Path variable for the shopping list |
Outputs:
| Name | Type | Description |
|---|---|---|
| added_to_pantry_count | integer | Number of catalogue-linked items moved into the pantry |
| skipped_manual_items | List<string> | Names of manually entered purchased items removed from the list but not added to the pantry |
| shopping_list_deleted | boolean | Whether the shopping list was deleted because it became empty |
Usage / Interaction Rules:
- Send a PUT request to /api/shopping-lists/{id}/complete-shop - no request body required
- User can only complete-shop their own shopping lists
- Only items marked purchased: true are processed; unpurchased items are left untouched on the list
- Catalogue-linked purchased items are inserted into the pantry as new rows and removed from the list. Existing pantry rows for the same ingredient are never merged
- Manually entered purchased items are removed from the list but not added to the pantry
- If every item on the list is removed as a result, the shopping list itself is deleted
- Returns 200 OK with the completion summary
Description: Creates a shopping list from the ingredients for a recipe. This endpoint is planned and not yet implemented.
Endpoint: POST /api/shopping-lists/from-recipe/{recipeId}
Inputs:
| Name | Type | Description |
|---|---|---|
| recipeId | integer | Path variable identifying the recipe |
| name | string | Name for the new shopping list |
| include_available_pantry_items | boolean | Optional, defaults to false |
Outputs:
| Name | Type | Description |
|---|---|---|
| shopping_list_id | integer | Unique shopping list identifier |
| user_id | integer | ID of the list's owner |
| name | string | Name of the shopping list |
| status | ShoppingListStatus (enum) | ACTIVE or COMPLETED |
| created_at | OffsetDateTime | Timestamp of list creation |
| items | List<ShoppingListItem> | Generated items for the recipe |
Usage / Interaction Rules:
- Send a POST request to /api/shopping-lists/from-recipe/{recipeId} with name and optional include_available_pantry_items in JSON format
- Recipe must be visible to the logged-in user
- include_available_pantry_items: false (default) compares recipe ingredients against the user's pantry and only adds missing ones
- include_available_pantry_items: true adds every recipe ingredient regardless of pantry contents
- Generated shopping_list_items use ing_id where a catalogue ingredient match exists, name otherwise
- Returns 200 OK with the created shopping list and its items