# Diet coach > Plans a week of meals from the food in a person's own kitchen, writes a recipe for every meal, and tracks what they > ate. The website is the API: every page has one address under /clients/{id}, answering in HTML, Markdown or JSON by > the Accept header. Reading a person's data needs the read-only key they make in Settings, under AI assistant access. ## Docs - [/llms.txt](/llms.txt): this full guide: the key, the representations, a worked example from the workspace to a recipe by links alone, every resource and every action. - [/skills/agent-api.md](/skills/agent-api.md): the same guidance as a skill file to load into an assistant. ## Start here - [Public introduction](/): available without an account; send `Accept: text/markdown`. - [/clients/{id}](/clients/{id}): a person's workspace. Send `Authorization: Bearer ` and `Accept: text/markdown`, then follow the links under "## Links". The person gives you the address and the key. ## Notes - GET never changes anything, on any path. - A plan being worked out is an ordinary 200 with its job_state and progress sentence; poll with If-None-Match. - No credentials: 401 with WWW-Authenticate: Bearer. Someone else's address: 404, never 403. A key writing: 403. ## In one paragraph Send `Accept: text/markdown` (or `application/json`) and `Authorization: Bearer ` to `https://dietapp.404.mn/clients/{id}`, the person's workspace. Every response ends with its links. Follow them: you never need to build a URL. GET never changes anything, so reading is always safe. ## The key 1. The person signs in to their own workspace with Google. An agent never signs in as a person. 2. In **Settings → AI assistant access** they make a key. It is shown once; the app keeps only its SHA-256 hash. 3. They give you the key and their workspace address, `https://dietapp.404.mn/clients/{id}` (it is in their browser's address bar). The key is: - **read-only.** It can GET every resource in every representation, the HTML pages included (rendered read-only). Every write answers `403` with the code `read_only_token`. Three owner-only pages, `settings`, `trainers` and `agent-tokens`, answer `403` too. - **for one person.** On anyone else's address it gets `404`, exactly as if that person didn't exist. A revoked or unknown key gets the same `404`. - **revocable** at any time from the same Settings section, and never a Google token. No key and no session: `401` with `WWW-Authenticate: Bearer`, before anything is looked up. ## Discovery Responses link to this guide with `Link: ; rel="describedby"; type="text/plain"`. HTML advertises the same page as `rel="alternate"`, `type="text/markdown"`; request that address with `Accept: text/markdown`. A `401` explains where the owner can create a key and links to the key instructions. ## Representations Pick one with `Accept`; no `Accept` (or `*/*`) gets the HTML page. - **Markdown**: `# title`, a line or two of facts per section (`## heading`), then `## Links` with lines `- **rel**: [title](href)`, then `## Actions` with lines `` - `METHOD href {field:type}` — title ``. - **JSON**: one object, `{"title", ...data, "sections"?, "links": [{rel, href, title?}], "affordances": [{rel, method, href, fields?, title?}]}`. - **HTML**: the website itself. Links are real ``, and it works with JavaScript off. Links in Markdown and JSON are absolute. An empty section says so: in Markdown its heading stays with an italic note; in JSON it is `"present": false` with a note; HTML simply leaves it out. Every response carries `Vary: Accept` and an `ETag`. Send it back in `If-None-Match` exactly as you got it (a compressed response's tag arrives marked weak, `W/"…"`, and works the same) and an unchanged resource answers `304` with no body, so polling costs almost nothing. ## Things that are true here - **GET never writes.** Not on any path, in any representation, for anyone. A plan is never started by reading a page: planning starts from an explicit `POST plan` (the person's page sends `{"auto": true}` when a week has no plan yet) or from the background sweep that plans the next week when a window ends. - **A plan in progress is an ordinary `200`.** `plan` reports `job_state` (`queued`, `running`, `done` or `failed`) and, while it runs, the planner's own progress sentence. There is no `202`, no `Retry-After` and no percentage. Poll with `If-None-Match`. - **Planning includes recipes.** A week shows as planned only when every planned meal has its written recipe; there is no "recipes pending" state. A meal with no recipe has no recipe resource (`404`). The stored planner result is `plan_result` in the plan's JSON. - **Follow links; don't build URLs.** Days are ISO dates and meals carry the person's own meal names, in their own order and number. Read them from the links. - **Affordances describe the product.** `## Actions` and `affordances` list what the person (or their coach) can do on that resource. They are not granted to a key: a bearer request sees links but no write affordances, and every write it sends gets `403`. - **Other people don't exist to you.** Another person's address answers `404`, never `403`. ## Worked example: from the workspace to a recipe, by links alone ``` $ curl -s -H "Accept: text/markdown" -H "Authorization: Bearer $KEY" https://dietapp.404.mn/clients/7 ``` The workspace answers with its sections and, under `## Links`, among others (abridged): ```markdown ## Links - **pantry**: [Kitchen](https://dietapp.404.mn/clients/7/pantry) - **progress**: [History](https://dietapp.404.mn/clients/7/progress) - **plan**: [Plan for the week of 2026-10-12](https://dietapp.404.mn/clients/7/plan) ``` Fetch the `plan` link. The week answers with its `job_state` and one `day` link per day: ```markdown ## Links - **up**: [Your workspace](https://dietapp.404.mn/clients/7) - **day**: [Wednesday 14 October](https://dietapp.404.mn/clients/7/plan/days/2026-10-14) ``` Fetch a `day` link: it has one `meal` link per planned meal, named as the person names them. Fetch a `meal` link: it gives the meal's totals, its ingredients from the kitchen and its record if logged, and a `recipe` link. Fetch the `recipe` link: the ingredients and the numbered method, with a `painting` link to the dish's picture. Each step publishes `rel=up` back to its parent. Older weeks are under `plans` (newest first); links inside an old plan stay inside it (`/clients/7/plans/42/days/…`). ## What there is to read Every path is relative to `https://dietapp.404.mn/clients/{id}/`. | Path | What it is | |---|---| | (the root) | The workspace: history, the next meal to cook, today's ledger, the week, the kitchen, conversations | | `pantry` | The kitchen: every food, its quantity and measurement rule, recently removed foods | | `pantry/{ref}` | One food, by its short id (`#a1b2`, sent as `%23a1b2`), its exact name or its row id; `pantry`'s `item` links address each one | | `profile` | Macro targets, tolerances, meal structure and the goal history | | `client` | Name, notes, body details, activity level, time zone, and the energy estimate built from them | | `equipment` | Kitchen equipment the recipe writer may use | | `recipe-orders` | Standing orders for every recipe | | `cooking-time` | How long the person wants to spend cooking | | `notes`, `notes/{key}` | The coach's notes about the person | | `trainers` | Who else can see this workspace (owner only: a key gets `403`) | | `agent-tokens` | The assistant keys (owner only: a key gets `403`) | | `settings` | An index of the pages above (owner only: a key gets `403`) | | `progress` | The dated meal diary, the 30-day calorie series against the goal in force each day, and trends. `?query=` filters, `?before=DATE&limit=N` pages back (`rel=next`) | | `meals` | The meals eaten on one day (`?date=`, default today) with the day's total, or `?before=DATE&limit=N` the meals before a day; `POST meals` records one, with a time like `an_hour_ago` and foods from the kitchen or described | | `meals/{meal_id}` | One eaten meal: its time, its foods and what each took from the kitchen; `PUT` replaces it, `DELETE` undoes it and gives the food back | | `conversations` | The coach conversations, newest first. `?query=Q&limit=N` searches them | | `conversations/{id}` | One conversation, verbatim, with each turn's state, the coach's actions and its lookups | | `conversations/{id}/turns/{tid}` | One turn | | `conversations/{id}/images/{name}` | A photo sent in that conversation (binary) | | `plan` | The week containing the person's today: job state, progress, days, follow-ups | | `plan/progress?after=N` | The planner's journal for that week after event N | | `plan/fix` | When the week couldn't be planned: what would work (other goals, or foods to buy) | | `plan/days/{date}` | One day: a link per meal | | `plan/days/{date}/meals/{meal}` | One meal: totals, ingredients, its record | | `plan/days/{date}/meals/{meal}/recipe` | The recipe | | `plans`, `plans/{plan_id}/…` | Every plan, newest first; each holds the same subtree as `plan` | | `art/{key}.webp` | A per-person picture (a food's icon), behind the same key | Dish paintings and library icons are public and content-addressed at `https://dietapp.404.mn/art/{sha}.webp`; the resources that show them link them. ## Errors A refusal is `{"error": {"code", "message", "field"?}}` in JSON, or a short Markdown note with the same sentence. The `message` is written for the person; the `code` is stable. `401`: no credentials. `403`: a key tried to write, or to read an owner-only page. `404`: not here, or not yours. `304`: unchanged. ## Every action These are the writes the person's own session, the website's forms and their coach make. A key can make none of them; they are listed so you can tell the person exactly what to do, or what to ask their coach. Each line is the method, the path and the body's fields with their types, then what it does, who may call it and a worked call. - `DELETE /clients/{id}` — Delete your account Deletes the signed-in owner's account and its record with everything in it: goals, details, kitchen, plans, meal log, coach conversations and photos. The record's trainers lose access. It signs the person out and can't be undone. Who may: the client's own account. Example: `DELETE /clients/{id}`. - `POST /clients {name:str, notes:str, myself:bool}` — Add a client record Creates a client record with a name and optional notes for you, the trainer, with your trainer access and an invite link in one step; myself=true creates your own record instead (one per account). Who may: any signed-in person. Example: `POST /clients` with `{"name": "Priya", "notes": "Wants more protein in the mornings."}`. - `POST /join/{code}/accept` — Accept a trainer's invite Links your account to the client record the invite names, with its trainer's access. If you already have your own record, the trainer is added to it instead. Who may: any signed-in person. Example: `POST /join/EXAMPLE-INVITE-CODE-0001/accept`. - `POST /join/{code}/decline` — Decline a trainer's invite Links nothing: the record stays unclaimed, the trainer's pre-accept goals and notes are cleared, and every open invite to the record stops working. A decline after the invite was accepted changes nothing. Who may: any signed-in person. Example: `POST /join/EXAMPLE-INVITE-CODE-0001/decline`. - `POST /clients/{id}/agent-tokens {label:str}` — Make an AI assistant access token Mints a read-only bearer token scoped to this client, with an optional label. The token is in this response only (201); afterwards only its label and dates are listed. Who may: the client's own account. Example: `POST /clients/{id}/agent-tokens` with `{"label": "Phone"}`. - `DELETE /clients/{id}/agent-tokens/{id}` — Revoke AI assistant access Revokes one token at once (addressed by its id, or by its label when no other token shares it); the row stays listed as revoked. Revoking a revoked token changes nothing. Who may: the client's own account. Example: `DELETE /clients/{id}/agent-tokens/Claude`. - `PATCH /clients/{id}/client {name:str, notes:str, height_cm:number, weight_kg:number, weight_lb:number, age_years:int, sex:str, activity_level:str, tz:str}` — Change client details Changes only the details sent: name, height_cm (50–275), weight_kg or weight_lb, age_years, sex (male, female or other), activity_level (sedentary, light, moderate, active, very_active) and tz (an IANA zone; it decides the client's today). Null clears a detail. The client's trainers also keep notes here, which only they read and write. Who may: the client's own account or their trainer. Example: `PATCH /clients/{id}/client` with `{"height_cm": 170, "tz": "America/Chicago"}`. - `POST /clients/{id}/conversations {local_date:str}` — Start a new conversation Starts a fresh conversation for today; the next message goes there, and today's earlier conversation stays in the archive. Who may: the client's own account. Example: `POST /clients/{id}/conversations`. - `DELETE /clients/{id}/cooking-time` — Clear the cooking-time budget Clears the cooking-time budget. Who may: the client's own account. Example: `DELETE /clients/{id}/cooking-time`. - `PUT /clients/{id}/cooking-time {text:str}` — Set the cooking-time budget Replaces the per-meal cooking-time budget with text, for example “30 minutes on weeknights”; blank text clears it. Who may: the client's own account. Example: `PUT /clients/{id}/cooking-time` with `{"text": "30 minutes on weeknights"}`. - `POST /clients/{id}/equipment {item:str, action:str}` — Add or remove kitchen equipment Adds or removes one named item. Removing an item that isn't listed changes nothing and says so. Who may: the client's own account. Example: `POST /clients/{id}/equipment` with `{"action": "add", "item": "rice cooker"}`. - `POST /clients/{id}/feedback {kind:str, source:str, summary:str, detail:str}` — Send feedback to the developers Relays feedback to the people who build this app, at once, with this client and conversation attached. Use it the moment the client hits a bug, asks for something the app can't do, or is frustrated by how something works (source user), or when you hit a limit in your own tools (source coach). Don't ask permission first. Who may: the client's own account. Example: `POST /clients/{id}/feedback` with `{"detail": "The client asked whether they can scan a package's barcode to log it.", "kind": "feature", "source": "user", "summary": "Wants to log meals by barcode"}`. - `POST /clients/{id}/invite` — Make a new invite link Makes a fresh invite link for a client who hasn't joined (after a decline, or a lost link); it clears the declined marker. Earlier links that weren't declined keep working. Who may: the client's trainer. Example: `POST /clients/{id}/invite`. - `POST /clients/{id}/meals {when:str, eaten_from:str, eaten_to:str, when_said:str, title:str, foods:obj[]}` — Record something eaten Records an eating occasion: when (a preset like an_hour_ago, or eaten_from and eaten_to, plus the person's own words in when_said), an optional title, and its foods. Each food is a pantry_item with the grams eaten, which takes that much from the kitchen and computes its macros, or a description with what's known of its macros (an estimate or a label), which touches no stock; one meal may mix both. Not for a time that hasn't happened. Who may: the client's own account. Example: `POST /clients/{id}/meals` with `{"foods": [{"grams": 100, "pantry_item": "Eggs"}, {"description": "Two slices of toast with butter from the café", "macros": {"calories": 230, "carbs": 30, "fat": 9, "protein": 7}}], "title": "Eggs and café toast", "when": "an_hour_ago"}`. - `DELETE /clients/{id}/meals/{meal_id}` — Undo an eaten meal Removes an eaten meal from the diary and gives back exactly what it took from the kitchen, bringing back a food it used up. A planned meal it belonged to goes back to not recorded. Who may: the client's own account. Example: `DELETE /clients/{id}/meals/1`. - `PUT /clients/{id}/meals/{meal_id} {when:str, eaten_from:str, eaten_to:str, when_said:str, title:str, foods:obj[]}` — Replace an eaten meal Replaces an eaten meal whole, with the same body as recording one: its time, title and foods. What it took from the kitchen is given back first and the new foods taken, in one step; it keeps its number and the planned meal it belongs to, and drops the recipe and rating of a cooked one. Who may: the client's own account. Example: `PUT /clients/{id}/meals/1` with `{"foods": [{"description": "Bowl of tomato soup", "macros": {"calories": 200, "carbs": 26, "fat": 8, "protein": 6}}], "when": "just_now"}`. - `DELETE /clients/{id}/notes/{key}` — Delete a coach note Removes the note under the key. Who may: the client's own account. Example: `DELETE /clients/{id}/notes/detail_preference`. - `PUT /clients/{id}/notes/{key} {content:str}` — Save a coach note Saves content under the key, replacing what was there (one value per key). Who may: the client's own account. Example: `PUT /clients/{id}/notes/detail_preference` with `{"content": "Short answers, numbers first."}`. - `POST /clients/{id}/onboarding/complete` — Finish onboarding Marks onboarding complete, permanently; once onboarded it changes nothing. Do this when the client is set up (goals, meal structure, kitchen) and confident. Who may: the client's own account. Example: `POST /clients/{id}/onboarding/complete`. - `POST /clients/{id}/pantry {items:item-json[], name:str, brand:str, price:str, quantity:obj, measurement_rule:str, package_noun:str, package_noun_plural:str, categories:str[], mechanical:str[], flavors:str[], processes:str[], macros_per_gram:obj, exact:obj, macro_bounds:obj, weekly_cap_g:str, note:str}` — Add foods to the kitchen Adds one item (the body is the item) or several ({items: [...]}). Each item is checked on its own: the valid ones land and the result names each refused one with its reason. Every item states its four macros per gram and how they are known: exact {source} or macro_bounds. Who may: the client's own account. Example: `POST /clients/{id}/pantry` with `{"items": [{"categories": ["DAIRY"], "exact": {"source": "label"}, "macros_per_gram": {"calories": "0.61", "carbs": "0.048", "fat": "0.033", "protein": "0.034"}, "measurement_rule": "CONTINUOUS", "mechanical": ["LIQUID"], "name": "Milk", "quantity": {"density_g_per_ml": "1.03", "total": "1", "type": "bulk", "unit": "gal"}}, {"categories": ["BREAD"], "macro_bounds": {"calories": {"hi": "2.7", "lo": "2.45"}, "carbs": {"hi": "0.52", "lo": "0.45"}, "fat": {"hi": "0.04", "lo": "0.02"}, "protein": {"hi": "0.095", "lo": "0.075"}, "rationale": "Low: a light, airy loaf; high: a dense dark one."}, "macros_per_gram": {"calories": "2.59", "carbs": "0.48", "fat": "0.033", "protein": "0.085"}, "measurement_rule": "SLICEABLE", "name": "Rye bread", "quantity": {"count": 1, "size": "680", "type": "packages"}}]}`. - `POST /clients/{id}/pantry/delete {item_ids:int[]}` — Delete selected foods Deletes the selected live foods together. If any selection is stale, nothing is deleted. Deleted foods remain restorable for the ordinary restore window. Who may: the client's own account. Example: `POST /clients/{id}/pantry/delete` with `{"item_ids": [1]}`. - `DELETE /clients/{id}/pantry/{ref}` — Remove a food Takes the item out of the kitchen; it can be restored exactly as it was for about 2 days. The result says how many open planned meals used it. Removing an item that's already gone changes nothing and says so. Who may: the client's own account. Example: `DELETE /clients/{id}/pantry/Tofu`. - `PATCH /clients/{id}/pantry/{ref} {name:str, brand:str, price:number, quantity:obj, measurement_rule:str, package_noun:str, package_noun_plural:str, categories:str[], mechanical:str[], flavors:str[], processes:str[], macros_per_gram:obj, exact:obj, macro_bounds:obj, weekly_cap_g:number, note:str}` — Edit a food Changes only the fields sent. quantity sets the remaining stock; weekly_cap_g caps the grams a week the planner may cook with (null or blank clears it). Changing the macros needs exact or macro_bounds. Who may: the client's own account. Example: `PATCH /clients/{id}/pantry/Cheddar` with `{"quantity": {"total": "300"}, "weekly_cap_g": "200"}`. - `POST /clients/{id}/pantry/{ref}/consume {grams:number}` — Take out food that wasn't eaten Takes grams of an item that left the kitchen without being eaten (spoiled, thrown away, given away, cooked for someone else); it never goes in the diary, and food that was eaten is a meal's pantry_item instead. Stops at what's there and says what is left; the last gram takes the item out of the pantry as used up. Who may: the client's own account. Example: `POST /clients/{id}/pantry/Lentils/consume` with `{"grams": "200"}`. - `POST /clients/{id}/pantry/{ref}/photo {photo:file, image:str}` — Attach a photo of a food Attaches the person's own photo of the item: a file (a multipart form, field photo: JPEG, PNG, GIF or WebP, up to 10 MB), or a photo they sent in this conversation, named by its handle ({"image": "img1"}). Its icon is then made from the photo, and the photo's icon replaces the painted one when it is usable; otherwise the painted icon stays. A new photo replaces the last one. Who may: the client's own account. Example: `POST /clients/{id}/pantry/Cheddar/photo` plus the file `photo`. - `POST /clients/{id}/pantry/{ref}/restore` — Restore a removed food Brings back an item removed in the last about 2 days, exactly as it was. An item that was used up by cooking can't be restored: add it again. Who may: the client's own account. Example: `POST /clients/{id}/pantry/Tempeh/restore`. - `POST /clients/{id}/plan {week:str, auto:bool}` — Plan a week Starts planning the plan that begins on the given day, recipes included, and returns at once with the plan id; the plan resource shows the progress. Plans follow on from each other: each starts the day after the one before it ends (a first plan on a Monday), and a refusal names the right start. auto is only for the automatic plan: it starts nothing when today already has a plan, the client isn't ready to plan, or the day given isn't the start of the plan that holds today. Who may: the client's own account. Example: `POST /clients/{id}/plan` with `{"week": "2026-10-12"}`. - `POST /clients/{id}/plan/days/{day}/meals/{meal}/ate {title:str, foods:obj[]}` — Record what was eaten instead Records something other than the planned meal: an optional title and its foods, each a pantry_item with the grams eaten (taken from the kitchen, macros computed) or a description with what's known of its macros (an estimate or a label, with an optional macros_range), touching no stock; one meal may mix both. Recording again replaces the earlier record. Only today or earlier. Who may: the client's own account. Example: `POST /clients/{id}/plan/days/2026-10-14/meals/Evening/ate` with `{"foods": [{"description": "Burrito bowl", "macros": {"calories": 740, "carbs": 95, "fat": 24, "protein": 38}}], "title": "Burrito bowl from the place downstairs"}`. - `POST /clients/{id}/plan/days/{day}/meals/{meal}/cooked {rating:str, comment:str}` — Record a meal as cooked Records that the planned meal was cooked and eaten, with an optional rating (loved or not_for_me) and comment: the planned grams of each food are taken from the kitchen and the plan's figures recorded. A fixed meal is recorded as given, with its own figures, and takes nothing from the kitchen. Recording again replaces the earlier record, giving its food back first. Only today or earlier. Who may: the client's own account. Example: `POST /clients/{id}/plan/days/2026-10-14/meals/Morning/cooked` with `{"rating": "loved"}`. - `DELETE /clients/{id}/plan/days/{day}/meals/{meal}/fixed` — Unfix a meal Clears one dated meal's fixed meal, so planning plans it again. With nothing fixed it changes nothing and says so. Not while planning is working on that meal or its week. Who may: the client's own account. Example: `DELETE /clients/{id}/plan/days/2026-10-17/meals/Morning/fixed`. - `PUT /clients/{id}/plan/days/{day}/meals/{meal}/fixed {desc:str, macros:obj}` — Fix a meal Marks one dated meal as already decided (the same first meal every day): planning keeps it as given and plans the rest of the day around its macros, and no meal record is written until it is eaten. Today or later only, and not while planning is working on that meal or its week. Who may: the client's own account. Example: `PUT /clients/{id}/plan/days/2026-10-17/meals/Morning/fixed` with `{"desc": "Greek yogurt with oats", "macros": {"calories": 430, "carbs": 60, "fat": 8, "protein": 30}}`. - `PUT /clients/{id}/plan/days/{day}/meals/{meal}/recipe {title:str, text:str}` — Replace a meal's recipe Replaces a planned meal's recipe with one written in the recipe writer's format (INGREDIENTS with grams of kitchen foods, OPTIONAL seasonings without grams, numbered METHOD). It is checked against the kitchen and the meal's range like a planned recipe; foods swapped in replace the meal's planned food. Only a meal from today on that isn't recorded yet: a recorded meal keeps the recipe it was recorded with (undo the record first). Who may: the client's own account. Example: `PUT /clients/{id}/plan/days/2026-10-15/meals/Evening/recipe` with `{"text": "--- INGREDIENTS ---\n- 170 g Chicken breast\n- 100 g Eggs\n- 100 g White rice\n- 150 g Broccoli\n- 15 g Olive oil\n--- OPTIONAL ---\n- soy sauce\n--- METHOD ---\n1. Boil the rice for 15 minutes.\n2. Stir-fry the chicken and broccoli in the oil.\n3. Scramble the eggs in, then toss with the rice.", "title": "Chicken and broccoli fried rice"}`. - `POST /clients/{id}/plan/days/{day}/meals/{meal}/skipped` — Record a meal as not eaten Records that the meal wasn't eaten: zero intake, nothing taken from the kitchen, and any food an earlier record of it took is given back. Only today or earlier. Who may: the client's own account. Example: `POST /clients/{id}/plan/days/2026-10-14/meals/Noon/skipped`. - `DELETE /clients/{id}/plan/days/{day}/meals/{meal}/target` — Clear a meal's pinned target Clears one dated meal's pinned target, so planning gives it its share of the day again. With nothing pinned it changes nothing and says so. Who may: the client's own account. Example: `DELETE /clients/{id}/plan/days/2026-10-16/meals/Noon/target`. - `PUT /clients/{id}/plan/days/{day}/meals/{meal}/target {protein:number, carbs:number, fat:number, calories:number}` — Pin a meal's target Pins one dated meal's target (all four numbers) for planning, replacing the share of the day it would get; tracked intake for that meal follows the pin (cooking for four is about 4×). A one-off for that date only. Who may: the client's own account. Example: `PUT /clients/{id}/plan/days/2026-10-16/meals/Noon/target` with `{"calories": 2140, "carbs": 240, "fat": 60, "protein": 160}`. - `POST /clients/{id}/plan/days/{day}/meals/{meal}/uncook` — Undo a meal's record Returns a recorded meal to not recorded: the food its record took goes back to the kitchen exactly and the diary entry is removed; the planned meal and its recipe stay. Who may: the client's own account. Example: `POST /clients/{id}/plan/days/2026-10-12/meals/Morning/uncook`. - `POST /clients/{id}/plan/replan {day:str, meal:str, excludes:str[]}` — Re-plan one meal Re-plans one meal of the plan for its week, with its recipe; the earlier meal stands until the new one and its recipe are ready. excludes are pantry items to leave out of this re-plan only. Past days can't be re-planned, and neither can a fixed meal (unfix it first). A meal already logged comes back as information: it wasn't re-planned. From the third attempt on a meal the result carries a warning: say no different meal is possible instead of trying again. Who may: the client's own account. Example: `POST /clients/{id}/plan/replan` with `{"day": "2026-10-15", "meal": "Noon"}`. - `PATCH /clients/{id}/profile {undershoot_tolerance:number, overshoot_tolerance:number, undershoot_percent:number, overshoot_percent:number, max_meal_mass:number, plan_days:int, schedule:obj[], meals:obj[], no_meals_on:str[], protein:number, carbs:number, fat:number, calories:number}` — Change part of the goals Changes only the profile fields sent, through the same checks as PUT profile; send schedule or meals alone to change just the meal structure. Calories must still equal the macros' 4/4/9 sum. Who may: the client's own account or their trainer. Example: `PATCH /clients/{id}/profile` with `{"calories": 2110, "carbs": 220, "fat": 70, "meals": [{"name": "Brunch", "percent": "45"}, {"name": "Supper", "percent": "55"}], "protein": 150}`. - `PUT /clients/{id}/profile {undershoot_tolerance:number, overshoot_tolerance:number, undershoot_percent:number, overshoot_percent:number, max_meal_mass:number, plan_days:int, schedule:obj[], meals:obj[], no_meals_on:str[], protein:number, carbs:number, fat:number, calories:number}` — Set goals and meal structure Sets the daily goal and the meal structure together: protein, carbs and fat in grams and calories, which must equal 4·protein + 4·carbs + 9·fat within 0.5%. The structure is schedule (day objects, or a flat list of meals {name, fraction}) or meals (one list; a meal with day belongs to that weekday only; no_meals_on names weekdays with no meals); each day's shares sum to 1 (fractions) or to 100 within one point (percent). Tolerances are fractions (undershoot 0–1, overshoot 0–2; defaults 0.15 and 0.25) or the same as percentages; max_meal_mass is grams, absent for no limit; plan_days is 1–14. Who may: the client's own account or their trainer. Example: `PUT /clients/{id}/profile` with `{"calories": 2110, "carbs": 230, "fat": 70, "meals": [{"fraction": "0.3", "name": "Morning"}, {"fraction": "0.7", "name": "Evening"}], "protein": 140}`. - `DELETE /clients/{id}/recipe-orders` — Clear standing orders Clears the standing orders. Who may: the client's own account. Example: `DELETE /clients/{id}/recipe-orders`. - `PUT /clients/{id}/recipe-orders {text:str}` — Set standing orders Replaces the recipe writer's standing orders wholesale with text; blank text clears them. Who may: the client's own account. Example: `PUT /clients/{id}/recipe-orders` with `{"text": "No cilantro; one-pan on weeknights."}`. - `POST /clients/{id}/trainers {email:str}` — Give a trainer access Gives the account with this email (it must have signed in once) read access to everything in this record and the right to set its goals. Adding someone who already has access changes nothing. Who may: the client's own account. Example: `POST /clients/{id}/trainers` with `{"email": "stranger@example.com"}`. - `DELETE /clients/{id}/trainers/{user_id}` — Remove a trainer's access Removes one trainer's access at once; ownership and the record are untouched. Removing someone who isn't a trainer here changes nothing. A trainer may remove only themselves (leaving this client's roster). Who may: the client's own account or their trainer. Example: `DELETE /clients/{id}/trainers/2`. - `POST /clients/{id}/turns {message:str, conversation_id:int, image_ids:str[], page_path:str, selection:str}` — Send a message to the coach Sends the person's message (and any photos uploaded for it) to the coach as a new turn in today's conversation, and answers with the turn once the coach has replied. Who may: the client's own account. Example: `POST /clients/{id}/turns` with `{"message": "How am I doing on protein this week?"}`. - `POST /clients/{id}/uploads {image:file}` — Upload a photo for your next message Stores one photo (multipart field image) for the next message and answers with its id; send it by listing that id in POST turns' image_ids within 24 hours. Who may: the client's own account. Example: `POST /clients/{id}/uploads` plus the file `image`. - `DELETE /clients/{id}/uploads/{token}` — Remove a photo you haven't sent Removes one uploaded photo that hasn't been sent yet. Who may: the client's own account. Example: `DELETE /clients/{id}/uploads/2YxpQYbS7mWm1u5w3G0kq6ci`. ## Your own tooling An MCP server with one tool, `fetch_diet_app {path}`, ships in the source as `mcp/server.py`: it GETs a path on this host as Markdown with your key and nothing else.