A coach can mark foods in the library as recommended for shopping. The trainee browses those foods by category, adds them to a personal checklist and ticks them off in the store. Mount: /v1/trainee/shopping, router traineeShoppingRouter in src/modules/shopping/shopping.routes.ts. Lane: trainee. The router mounts authenticateTrainee only, without the app access guard. Preview tokens can call the three GET routes. See Authentication.

Which foods are offered

The browse endpoints only return foods that pass foodScope in shopping.repository.ts:
  • a system food (studioId null) or one of the studio’s own foods,
  • with includeInShopping true in its effective values. The effective value is the studio’s FoodLibraryOverride when one exists for that food, else the base row. This is what effectiveFoodWhere computes.
  • not a plan-only row. Foods saved privately by the AI plan import (source equal to ai-private) never appear in lists.
Names, brands and categories are returned with the studio’s overrides applied.

Endpoints

GET /v1/trainee/shopping/categories

Lists the food categories that have at least one shopping food, with counts. Auth: trainee token. No parameters. A food counts once for each entry in its categories array. A food with no categories counts under its single category. A food with neither counts under uncategorized. The result is sorted by count descending, then by value. Response: 200.
value is the stored category string, usually one of the food category slugs listed on Trainee food catalog. No display name is returned. The app maps values to labels. Errors: none beyond the lane errors.

GET /v1/trainee/shopping/foods

Lists the shopping foods, optionally filtered. Auth: trainee token.
string
A category value from the endpoint above. Matches foods whose categories contain it or whose category equals it. Sending uncategorized matches only foods that literally store that value.
string
Case-insensitive match on the food name.
Both filters are applied against effective values. Ordered by base name, limited to 500 rows. Response: 200.
Errors: none beyond the lane errors.

GET /v1/trainee/shopping/list

Returns the trainee’s shopping checklist. Auth: trainee token. No parameters. Unchecked items first, then newest first. Response: 200.
name and brand are a snapshot taken when the item was added. They do not change if the coach later renames the food. Errors: none beyond the lane errors.

POST /v1/trainee/shopping/list

Adds a food to the checklist. Auth: trainee token. Refused for preview tokens.
string
required
A food id from GET /v1/trainee/shopping/foods.
The food must be a shopping food for the trainee’s studio. The item is upserted on the unique pair of clientId and foodId, so adding the same food again returns the existing item unchanged, including its checked state. Response: 201. The ShoppingListItem row, same shape as a list entry. Errors:

PATCH /v1/trainee/shopping/list/:id

Checks or unchecks one item. Auth: trainee token. Refused for preview tokens.
string
required
The shopping list item id.
boolean
required
The new state.
Response: 200.
Errors:

DELETE /v1/trainee/shopping/list/:id

Removes one item from the checklist. Auth: trainee token. Refused for preview tokens.
string
required
The shopping list item id.
Response: 204, no body. Errors: NOT_FOUND (404) shopping list item not found.

DELETE /v1/trainee/shopping/list

Removes every checked item from the checklist. Unchecked items stay. Auth: trainee token. Refused for preview tokens. No parameters. Response: 204, no body, also when nothing was checked. Errors: none beyond the lane errors. The checklist is deleted with the account by DELETE /v1/trainee/me. See Profile and home.