This section documents the shopping list endpoints of the MiseOS API.
Shopping lists are generated from approved ingredient requests for a delivery date.
AI normalization merges ingredient name variants and synonyms across languages into a single normalized name.
If AI normalization fails, the shopping list is still generated using the original ingredient names.
Manual items can also be added to shopping lists after generation.
The diagrams below give a quick overview of how shopping lists are created and managed.
- Generation workflow shows how approved ingredient requests are transformed into a shopping list, including AI normalization and fallback behavior.
- Lifecycle shows the allowed shopping list states and when transitions happen.
Together, they help clarify both the process flow and state rules before diving into endpoint details.
Shopping list generation workflow#
Shopping lists are generated from approved ingredient requests for a specific delivery date.
The workflow below illustrates how ingredient requests are aggregated, normalized using AI, and transformed into a shopping list.
flowchart TD
A[Ingredient Requests Submitted]
B[Requests Approved]
C[Shopping List Generated]
D[AI Ingredient Normalization]
E[Shopping List Created - DRAFT]
F[Items Marked as Ordered]
G[Shopping List Finalized]
A --> B
B --> C
C --> D
D --> E
E --> F
F --> G
Shopping list lifecycle#
stateDiagram-v2
[*] --> DRAFT : list generated
DRAFT --> FINALIZED : finalize
DRAFT --> [*] : deleted
FINALIZED --> [*]
Shopping lists are initially created in the DRAFT state.
While in draft, items can be added, removed, or updated.
Once all items are marked as ordered, the list can be finalized.
Finalized lists are immutable and represent completed purchase orders.
Shopping Lists Endpoints#
| Method | URL | Auth |
|---|---|---|
GET | /shopping-lists | HEAD_CHEF, SOUS_CHEF |
GET | /shopping-lists/{id} | HEAD_CHEF, SOUS_CHEF |
POST | /shopping-lists | HEAD_CHEF, SOUS_CHEF |
POST | /shopping-lists/{id}/finalize | HEAD_CHEF, SOUS_CHEF |
PATCH | /shopping-lists/{id}/delivery-date | HEAD_CHEF, SOUS_CHEF |
DELETE | /shopping-lists/{id} | HEAD_CHEF, SOUS_CHEF |
POST | /shopping-lists/{id}/items | HEAD_CHEF, SOUS_CHEF |
PUT | /shopping-lists/{id}/items/{itemId} | HEAD_CHEF, SOUS_CHEF |
DELETE | /shopping-lists/{id}/items/{itemId} | HEAD_CHEF, SOUS_CHEF |
PATCH | /shopping-lists/{id}/items/{itemId}/ordered | HEAD_CHEF, SOUS_CHEF |
PATCH | /shopping-lists/{id}/items/ordered | HEAD_CHEF, SOUS_CHEF |
ShoppingList response object#
The shopping list object has the following structure:
{
"id": 1,
"deliveryDate": "2026-04-01",
"status": "DRAFT",
"createdBy": { "id": 1, "firstName": "Gordon", "lastName": "Ramsay" },
"itemCount": 3,
"items": [
{
"id": 1,
"ingredientName": "Løg",
"quantity": 14.0,
"unit": "KG",
"supplier": "Inco",
"notes": "Claire (løg: 7.0 KG) | Marco (onions: 7.0 KG)",
"ordered": false,
"createdAt": "2026-03-27 10:00",
"updatedAt": null
},
{
"id": 2,
"ingredientName": "Smør",
"quantity": 5.0,
"unit": "KG",
"supplier": "Arla",
"notes": "Manual entry by: Gordon Ramsay",
"ordered": false,
"createdAt": "2026-03-27 10:05",
"updatedAt": null
},
{
"id": 3,
"ingredientName": "Frisk Dild",
"quantity": 10.0,
"unit": "BUNCH",
"supplier": "Grønttorvet",
"notes": "Claire (Frisk Dild: 10.0 BUNCH)",
"ordered": false,
"createdAt": "2026-03-27 10:00",
"updatedAt": null
}
],
"allOrdered": false,
"normalized": true,
"createdAt": "2026-03-27 10:00",
"finalizedAt": null
}Status values: DRAFT | FINALIZED
allOrdered indicates whether every item in the shopping list has been marked as ordered.
normalized = true:
AI successfully normalized ingredient names across languages.
normalized = false:
AI normalization failed and the system fell back to original ingredient names.
notes describes the origin of the ingredient entry.
Examples:
Aggregated ingredient requests
Claire (løg: 7.0 KG) | Marco (onions: 7.0 KG)Manual entries
Manual entry by: Gordon Ramsay
GET /shopping-lists#
Returns shopping lists with optional filters.
Query parameters
| Parameter | Type | Description |
|---|---|---|
status | String | Filter by DRAFT or FINALIZED |
deliveryDate | LocalDate | Filter by yyyy-MM-dd |
Example Request#
curl -H "Authorization: Bearer <token>" \
"https://miseos.corral.dk/api/v1/shopping-lists?status=DRAFT&deliveryDate=2026-04-01"Response 200 — array of shopping list objects.
Errors
| Status | Cause |
|---|---|
400 | Invalid status or date format |
GET /shopping-lists/{id}#
Returns one shopping list by ID.
Example Request#
curl -H "Authorization: Bearer <token>" \
https://miseos.corral.dk/api/v1/shopping-lists/1Response 200 — shopping list object.
Errors
| Status | Cause |
|---|---|
404 | Shopping list not found |
POST /shopping-lists#
Generates a shopping list from approved ingredient requests for a delivery date.
Ingredient names from the requests are aggregated and sent to the AI normalization service.
The targetLanguage determines the language used for the normalized ingredient names in the final shopping list.
For example:
løgonionscebolla
With targetLanguage = EN these may all be normalized to:
Onions
Supported languages#
targetLanguage must be one of the supported values:
| Code | Language |
|---|---|
DA | Danish |
EN | English |
ES | Spanish |
IT | Italian |
PT | Portuguese |
FR | French |
DE | German |
PL | Polish |
NL | Dutch |
If an unsupported value is provided, the system defaults to EN (English).
Request body
| Field | Type | Description |
|---|---|---|
deliveryDate | LocalDate | Delivery date for the shopping list |
targetLanguage | String | Language used for AI ingredient normalization |
{
"deliveryDate": "2026-04-01",
"targetLanguage": "DA"
}Example Request#
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"deliveryDate": "2026-04-01",
"targetLanguage": "DA"
}' \
https://miseos.corral.dk/api/v1/shopping-listsResponse 201 — generated shopping list object.
Errors
| Status | Cause |
|---|---|
400 | Invalid payload (missing deliveryDate or targetLanguage) |
409 | Shopping list already exists for that date |
409 | No approved ingredient requests for that date |
POST /shopping-lists/{id}/finalize#
Finalizes a shopping list. All items must be marked as ordered.
Example Request#
curl -X POST \
-H "Authorization: Bearer <token>" \
https://miseos.corral.dk/api/v1/shopping-lists/1/finalizeResponse 200 — updated shopping list object with status: FINALIZED.
Errors
| Status | Cause |
|---|---|
404 | Shopping list not found |
409 | Not all items are ordered |
409 | List is already finalized |
PATCH /shopping-lists/{id}/delivery-date#
Updates delivery date for a draft list.
Request body
{
"deliveryDate": "2026-04-03"
}Example Request#
curl -X PATCH \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"deliveryDate": "2026-04-03"
}' \
https://miseos.corral.dk/api/v1/shopping-lists/1/delivery-dateResponse 200 — updated shopping list object.
Errors
| Status | Cause |
|---|---|
400 | Date is missing, invalid, or in the past |
404 | Shopping list not found |
409 | Another shopping list already exists for that date |
409 | List is finalized |
DELETE /shopping-lists/{id}#
Deletes a draft shopping list.
Example Request#
curl -X DELETE \
-H "Authorization: Bearer <token>" \
https://miseos.corral.dk/api/v1/shopping-lists/1Response 204 — no content.
Errors
| Status | Cause |
|---|---|
404 | Shopping list not found |
409 | List is finalized |
POST /shopping-lists/{id}/items#
Adds a manual item to a draft shopping list.
Request body
{
"ingredientName": "Smør",
"quantity": 5.0,
"unit": "KG",
"supplier": "Arla"
}The system automatically adds a note like: Manual entry by: <FirstName LastName>.
Example Request#
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"ingredientName": "Smør",
"quantity": 5.0,
"unit": "KG",
"supplier": "Arla"
}' \
https://miseos.corral.dk/api/v1/shopping-lists/1/itemsResponse 201 — updated shopping list object.
Errors
| Status | Cause |
|---|---|
400 | Invalid payload |
404 | Shopping list not found |
409 | List is finalized |
PUT /shopping-lists/{id}/items/{itemId}#
Updates a shopping list item (draft lists only).
Request body
{
"quantity": 8.0,
"unit": "KG",
"supplier": "Ny Leverandør"
}quantity and unit are required. supplier is optional.
Example Request#
curl -X PUT \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"quantity": 8.0,
"unit": "KG",
"supplier": "Ny Leverandør"
}' \
https://miseos.corral.dk/api/v1/shopping-lists/1/items/2Response 200 — updated shopping list object.
Errors
| Status | Cause |
|---|---|
400 | Invalid payload |
404 | Shopping list or item not found |
409 | List is finalized |
DELETE /shopping-lists/{id}/items/{itemId}#
Removes an item from a draft list.
Example Request#
curl -X DELETE \
-H "Authorization: Bearer <token>" \
https://miseos.corral.dk/api/v1/shopping-lists/1/items/2Response 200 — updated shopping list object.
Errors
| Status | Cause |
|---|---|
404 | Shopping list or item not found |
409 | List is finalized |
PATCH /shopping-lists/{id}/items/{itemId}/ordered#
Marks one item as ordered.
Example Request#
curl -X PATCH \
-H "Authorization: Bearer <token>" \
https://miseos.corral.dk/api/v1/shopping-lists/1/items/2/orderedResponse 200 — updated shopping list object.
Errors
| Status | Cause |
|---|---|
404 | Shopping list or item not found |
409 | Item is already marked as ordered |
PATCH /shopping-lists/{id}/items/ordered#
Marks all items in the shopping list as ordered.
This is a convenience endpoint used when the entire list has been ordered from suppliers. It performs the same function as marking each item individually but reduces the number of API calls needed.
Example Request#
curl -X PATCH \
-H "Authorization: Bearer <token>" \
https://miseos.corral.dk/api/v1/shopping-lists/1/items/orderedResponse 200 — updated shopping list object with allOrdered: true.
Errors
| Status | Cause |
|---|---|
404 | Shopping list not found |
409 | One or more items are already marked as ordered |