{
  "openapi": "3.1.1",
  "info": {
    "title": "Bryti API",
    "description": "Bryti's own API — the one the web app and the phone apps talk to.\nThere are no API keys yet: a caller is a signed-in session, carried in\nthe `bryti_session` cookie that `POST /api/v1/auth/login` (or a social\nlogin) sets. Every route is closed unless it says otherwise\n(ADR-056); the ones marked without security are the doors you can\nreach before you have a session.\n\nEverything lives under `/api/v1`. Errors are RFC 7807\n`application/problem+json` with a stable `type` URL under\n`https://bryti.app/problems/`, a `code`, and the `traceId` of the\nrequest. A build that sends `X-App-Version` below its platform's\nfloor gets `426` on every call (ADR-070) and must update. Rate limits\nand the AI quota answer `429`; a rate limit adds `Retry-After`.\n\nEvery answer carries `X-Trace-Id` and `Server-Timing`, and\n`X-Diagnostics` when asked for with `X-Diagnostics: 1` or\n`?diagnostics=1` (ADR-163).\n\nThe `code` is the part of a problem body meant to be branched on —\n`detail` is English written for a log. `x-problem-codes` says which\nones can arrive: on an **operation** it lists what that handler\nrefuses with, on a shared **response** what that gate refuses with,\nand on this **document** what the middleware and the exception\nhandlers can write on any operation at all.",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://bryti.app",
      "description": "Production. The web app's own origin proxies /api/* to the API so the session cookie stays first-party."
    },
    {
      "url": "http://localhost:5100",
      "description": "Local development (make dev-api)."
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "App"
        ],
        "summary": "Say which build is answering",
        "description": "Public. Always ok with the version and build id of the process that answered; the platform's liveness and readiness probes live beside it and are not part of the contract.",
        "operationId": "GetHealth",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        },
        "security": [ ],
        "x-rate-limit": "none"
      }
    },
    "/robots.txt": {
      "get": {
        "tags": [
          "App"
        ],
        "summary": "Tell crawlers to stay off the API host",
        "description": "Public. Plain text that disallows every path and sets the content signals to no; the API has no pages for a crawler and no sitemap.",
        "operationId": "GetRobots",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "anonymous-per-ip"
      }
    },
    "/health/live": {
      "get": {
        "tags": [
          "App"
        ],
        "summary": "Say whether the process is up",
        "description": "Public. The platform's liveness probe: always ok with the version and build id of the process that answered. Infrastructure rather than contract — the apps have no reason to call it.",
        "operationId": "GetHealthLive",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        },
        "security": [ ],
        "x-rate-limit": "none"
      }
    },
    "/health/ready": {
      "get": {
        "tags": [
          "App"
        ],
        "summary": "Say whether this build can serve",
        "description": "Public. The platform's readiness probe, which Railway gates deploy cutover on: ok when the database answers and this build's migrations are all applied, 503 otherwise. Infrastructure rather than contract — the apps have no reason to call it.",
        "operationId": "GetHealthReady",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          }
        },
        "security": [ ],
        "x-rate-limit": "none"
      }
    },
    "/api/v1/me": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Get the signed-in account and every household it belongs to",
        "description": "Also says whether the e-mail is confirmed, whether the account has a password, and whether the caller is a platform admin. 401 when the cookie belongs to an account that no longer exists (#978).",
        "operationId": "GetMe",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "patch": {
        "tags": [
          "Auth"
        ],
        "summary": "Rename the signed-in account",
        "description": "Only the display name changes here (1–80 characters, else 400); e-mail and password have their own confirmed paths (#298). Answers the updated account.",
        "operationId": "UpdateMe",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "account_name_length"
        ],
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Privacy"
        ],
        "summary": "Delete the caller's account and sign out",
        "description": "Any signed-in user (GDPR art. 17). Households nobody else uses go with it; shared ones lose this member. Also removes the caller's invites, waitlist entry and problem reports with their screenshots, then signs the session out. 400 while the caller owns a household others still use — make someone else an owner, or remove the others, first.",
        "operationId": "DeleteAccount",
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/me/password": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Change the account's password, or set a first one on a social-only account",
        "description": "The current password is required when the account has one: 400 with code wrong_current_password when it is wrong, plain 400 with the rules when the new one is too weak, and code password_pwned when it has been seen in a breach (ADR-290). This session stays signed in; every other session is asked to sign in again (#978).",
        "operationId": "ChangePassword",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangePasswordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "password_pwned",
          "password_rejected",
          "wrong_current_password"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/me/email": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Ask to move the login to a new e-mail address",
        "description": "Nothing changes until the link mailed to the new address is followed (POST /api/v1/auth/confirm-email-change); the old address gets a heads-up with a reset link (#1826). Always 204 for a well-formed address — never says whether it is taken. 400 when it does not look like an e-mail address.",
        "operationId": "ChangeEmail",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangeEmailRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "email_not_an_address"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/invites/accept": {
      "post": {
        "tags": [
          "Memberships & invites"
        ],
        "summary": "Join a household with an invite code",
        "description": "Any signed-in user; no household membership needed. Safe to retry: a caller who already belongs to the household answers 200 with the existing membership and the code stays unused. 400 when the code is unknown, expired or already used. A code made for a named person links the new account to them, unless that person has since been claimed or removed — then the join is unlinked. Everyone in the household gets a «joined» push (#254).",
        "operationId": "AcceptInvite",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AcceptInviteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "invite_not_usable"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/time-zones": {
      "get": {
        "tags": [
          "Households"
        ],
        "summary": "List the IANA time zones a household can be set to",
        "description": "Every id the server's own tz database knows — which is exactly what PATCH /households/{householdId} accepts as timeZoneId (ADR-228). Unsorted; the household's own zone first and the rest alphabetical is the client's job. Cached for a day (Cache-Control: private, max-age=86400).",
        "operationId": "ListTimeZones",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TimeZonesResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/calendar-events": {
      "get": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "List upcoming events from every feed, one slice per feed",
        "description": "Any member, a Junior included (#553). from defaults to today and to to from + 14 days; the window is clamped to 31 days. A feed that cannot be read comes back with a fixed error on its own slice — one broken calendar never hides the others (#979). busyEvenings names, per day, the event that touches that day's dinner hour — the planner's rule, so the week marks the day busy for the same reason (#3379).",
        "operationId": "GetUpcomingCalendarEvents",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpcomingCalendarEventsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/calendar/{token}.ics": {
      "get": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Fetch the planned dinners as an iCalendar feed from a private link",
        "description": "Public: a calendar app fetches it with no account (#3394); the token in the path is the proof. Each dinner cooked at home from 14 days back to 42 ahead is one event from when to start cooking until it is served, titled with the dish, with a UID stable per household and date. A token Bryti did not mint, or one the household has replaced or turned off, answers 404 without saying which. Rate-limited per IP.",
        "operationId": "GetDinnerCalendar",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "anonymous-per-ip"
      }
    },
    "/api/v1/households/{householdId}/loyalty-programs": {
      "get": {
        "tags": [
          "Stores"
        ],
        "summary": "List the loyalty programs available in the household's country",
        "description": "Any member (#579).",
        "operationId": "GetLoyaltyPrograms",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LoyaltyProgramResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/referral-codes": {
      "get": {
        "tags": [
          "Intake"
        ],
        "summary": "List the referral codes the household's members may hand out",
        "description": "Any member. Minted lazily up to the configured count, single-use, tied to the household. A household on a free plan gets an empty list — referral is a perk of paying.",
        "operationId": "ListReferralCodes",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ReferralCodeResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/app/version": {
      "get": {
        "tags": [
          "App"
        ],
        "summary": "Get the oldest app version each platform may still use",
        "description": "Public, and the one path the version gate never blocks: a stopped build must be able to ask whether it is still stopped (ADR-070, #725). Both fields null for a platform means no floor. Cached for 60 seconds (Cache-Control: public, max-age=60).",
        "operationId": "GetAppVersionFloor",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppVersionResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "anonymous-per-ip"
      }
    },
    "/api/v1/data/off-derived.jsonl.gz": {
      "get": {
        "tags": [
          "Data"
        ],
        "summary": "Download the Open Food Facts derivative Bryti publishes under ODbL",
        "description": "Public, no account (ODbL 4.6): every product row and every barcode → food-key mapping Bryti holds, as gzipped JSON lines whose first line names the licence, the source and the attribution. Rate-limited per IP; ETag is the file's SHA-256; 404 until the first sync has run (ADR-227).",
        "operationId": "GetOpenFoodFactsDerived",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "data-per-ip"
      }
    },
    "/api/v1/data/library-derived.jsonl.gz": {
      "get": {
        "tags": [
          "Data"
        ],
        "summary": "Download the CC BY-SA recipes Bryti's library adapted from open sources",
        "description": "Public, no account (CC BY-SA 4.0 share-alike): every recipe in Bryti's sourced library that carries a CC BY-SA licence — the bokmål adaptation of a Wikibooks or Unitools page — as gzipped JSON lines whose first line names the licence, the sources and the attribution. Rate-limited per IP; ETag is the file's SHA-256; always 200, with only the header line while no such recipe is seeded (#2828).",
        "operationId": "GetLibraryDerived",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "data-per-ip"
      }
    },
    "/api/v1/data/dinner-prices.json": {
      "get": {
        "tags": [
          "Data"
        ],
        "summary": "Read the published dinner price index as JSON",
        "description": "Public, no account (CC BY 4.0): per household type and published month, the median of the month's weekly measurements, each the median of the fully priced weeks among 1 000 drawn from Bryti's library at the shops' list prices, with p10, p90, kroner per portion, coverage and the number of chains behind the figure. No figure per chain and no price per product (#3619).",
        "operationId": "GetDinnerPrices",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DinnerPricesPublic"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "data-per-ip"
      }
    },
    "/api/v1/data/dinner-prices.csv": {
      "get": {
        "tags": [
          "Data"
        ],
        "summary": "Download the published dinner price index as CSV",
        "description": "Public, no account (CC BY 4.0): one row per household type and published month with the median, p10 and p90 week price, kroner per portion, coverage, chains, source and recipe-set version. Totals only (#3619).",
        "operationId": "GetDinnerPricesCsv",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "data-per-ip"
      }
    },
    "/api/v1/data/dinner-prices/subscribe": {
      "post": {
        "tags": [
          "Data"
        ],
        "summary": "Ask for the monthly dinner price by e-mail",
        "description": "Public, no account (#3619). Takes an address and a language (nb or en) and nothing else. Always 202 whatever the list already knew about the address: a new one, one already confirmed and a resend inside the day's cooldown look the same. A confirmation mail with a single-use link goes out for a new address, and again for an unconfirmed one at most once a day; an unconfirmed address is deleted after seven days. 400 (code email_invalid) only when the address is not one. Rate-limited per IP (dinner-price-signup-per-ip, 5 an hour by default): 429 with Retry-After.",
        "operationId": "SubscribeDinnerPrices",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DinnerPriceSubscribeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "security": [ ],
        "x-rate-limit": "dinner-price-signup-per-ip"
      }
    },
    "/api/v1/data/dinner-prices/confirm": {
      "get": {
        "tags": [
          "Data"
        ],
        "summary": "Confirm a dinner price e-mail address from the link in the mail",
        "description": "Public: the link opens in a browser that may never have seen Bryti (#3619). Safe to retry: a second click is still confirmed. 404 (code dinner_price_token_unknown) when no address carries the token; 410 (code dinner_price_token_expired) when the address outlived its seven days unconfirmed.",
        "operationId": "ConfirmDinnerPrices",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DinnerPriceConfirmResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "410": {
            "description": "Gone",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "dinner_price_token_expired",
          "dinner_price_token_unknown"
        ],
        "x-rate-limit": "anonymous-per-ip"
      }
    },
    "/api/v1/data/dinner-prices/unsubscribe": {
      "get": {
        "tags": [
          "Data"
        ],
        "summary": "Leave the dinner price e-mail list from the link in a mail",
        "description": "Public, one click, no account (#3619). Deletes the address, it is not marked. Safe to retry: a second click on the same link is still unsubscribed. 404 (code dinner_price_token_unknown) for a link Bryti did not mint.",
        "operationId": "UnsubscribeDinnerPrices",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DinnerPriceUnsubscribeResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "dinner_price_token_unknown"
        ],
        "x-rate-limit": "anonymous-per-ip"
      }
    },
    "/api/v1/billing/webhook": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Receive a billing event from Stripe",
        "description": "Not for the apps: public and unauthenticated, verified by the Stripe-Signature header over the raw body (#603); a bad signature is a 400 (code billing_webhook_rejected). Applies checkout completed, subscription changed or deleted, and payment failed. Safe to replay: the same event twice converges on the same state.",
        "operationId": "BillingWebhook",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/feedback": {
      "post": {
        "tags": [
          "Feedback"
        ],
        "summary": "Send a problem report from the app",
        "description": "Any signed-in person; no household needed. The message is theirs, and the app attaches which build, which screen, the last failed API calls (the newest 20 are kept) and optionally a screenshot — PNG, JPEG or WebP of at most 5 MB, 400 otherwise (#717). householdId is recorded only when the caller belongs to that household. The owner is alerted best effort; an alert failure never fails the report.",
        "operationId": "SubmitFeedback",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitFeedbackRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedbackSubmittedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/auth/register": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Create a password account and sign the caller in",
        "description": "Public. Answers 200 whether or not the address already has an account (#985): a fresh sign-up gets a confirmation mail and a session cookie, a known address gets a \"you already have an account\" mail and no session. Confirmation never gates the app; an address on the waitlist becomes this account's row (ADR-147). 400 carries the password or format errors, with code password_pwned when the password is one Have I Been Pwned has seen in a breach (ADR-290). Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "Register",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "password_pwned",
          "signup_rejected"
        ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/login": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Sign in with e-mail and password and get the account with its households",
        "description": "Public. Sets the session cookie and answers the same body as GET /api/v1/me. 401 for a wrong e-mail or password (never says which). 423 with code account_locked when the account is locked for a few minutes after too many wrong passwords (#986) — wait, or use forgot-password. Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "Login",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "423": {
            "description": "Locked",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "account_locked",
          "invalid_credentials"
        ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/logout": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Sign out and revoke every other copy of this session",
        "description": "Signed-in callers only. Rotates the security stamp, so a duplicated or stolen cookie fails its next check as well (#978). Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "Logout",
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/confirm-email": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Confirm the account's e-mail address with the token from the mailed link",
        "description": "Public: followed from a mail client, so the token in the body is the proof (#603). Safe to retry: an already confirmed address answers 204 again. 400 with code invalid_token when the link expired or was already used. Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "ConfirmEmail",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfirmEmailRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "invalid_token"
        ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/confirm-email-change": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Move the login to the new e-mail address with the token from the mailed link",
        "description": "Public: the token, bound to the new address, is the proof (#603). Marks the new address confirmed and revokes every other session (#978); a browser that follows the link while signed in as this account keeps its own. 400 with code invalid_token when the link expired, was used, or the address is taken. Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "ConfirmEmailChange",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfirmEmailChangeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "invalid_token"
        ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/resend-confirmation": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Send the e-mail confirmation mail again",
        "description": "Signed-in callers only. Answers 204 without mailing when the address is already confirmed. 429 with code resend_cooldown when a confirmation mail went out less than five minutes ago. Also rate-limited per client IP with the rest of /api/v1/auth (#1078): over that ceiling answers 429 with a Retry-After header.",
        "operationId": "ResendConfirmation",
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-problem-codes": [
          "resend_cooldown"
        ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/forgot-password": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Mail a password-reset link to the address, if it has an account",
        "description": "Public. Always 204 — never says whether the address exists, and a second request for the same account within five minutes is silently dropped (#1055). Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "ForgotPassword",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ForgotPasswordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/reset-password": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Set a new password with the token from the reset mail",
        "description": "Public: the reset token is the proof (#603). A reset also marks the address confirmed. 400 with code invalid_token when the link expired or was used; plain 400 with the password rules when the new password is too weak, or code password_pwned when it has been seen in a breach (ADR-290). Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "ResetPassword",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResetPasswordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "invalid_token",
          "password_pwned",
          "password_rejected"
        ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/providers": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "List the social login providers this server is configured for",
        "description": "Public: the sign-in screen asks which buttons to draw before anyone is signed in (#603). Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "GetAuthProviders",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AuthProviderResponse"
                  }
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/challenge/{provider}": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Start a social login by redirecting to the provider",
        "description": "Public. Browser flow: returnUrl (a site-relative path) is where the callback lands afterwards. App flow (#79): send codeChallenge and an allow-listed redirectUri, and the callback ends on the app's own URI with a single-use code for POST /api/v1/auth/exchange; 400 when only one of the two is given or the URI is not allowed. 404 for an unknown provider. Answers a 302 redirect to the provider, not JSON. Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "ChallengeProvider",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "returnUrl",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "redirectUri",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "codeChallenge",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "app_signin_incomplete",
          "unknown_login_provider"
        ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/external-callback": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Finish a social login the provider redirected back to",
        "description": "Public: reads the provider's external cookie and signs in the account behind that identity (#603) — the one already linked, else the one with the same e-mail, else a new one. Browser flow redirects to returnUrl (or to /login when nothing usable came back); app flow (#79) redirects to the app's URI with ?code=… or ?error=account. Answers a 302 redirect, never JSON. Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "ExternalCallback",
        "parameters": [
          {
            "name": "returnUrl",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Unauthorized",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/exchange": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Trade the app's single-use sign-in code for a session",
        "description": "Public: the PKCE verifier is the proof (#79, #603). The code lives two minutes and works once; 401 when it expired, was used, or the verifier does not match — start the sign-in again. Answers the same body as GET /api/v1/me. Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "ExchangeAuthCode",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExchangeAuthCodeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "signin_code_spent"
        ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/auth/apple/native": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Sign in with the identity token from the native Sign in with Apple sheet",
        "description": "Public: the token is checked against Apple's keys (#288, #603) and signs in the same account the web flow would — linked by Apple's stable sub, else by e-mail, else a new account. 401 when Apple does not vouch for the token. 404 when native Sign in with Apple is not configured on this server. Rate-limited per client IP with the rest of /api/v1/auth (#1078): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "AppleNativeSignIn",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppleNativeSignInRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "account_not_created",
          "apple_signin_rejected",
          "apple_signin_unavailable"
        ],
        "x-rate-limit": "auth-per-ip"
      }
    },
    "/api/v1/households/{householdId}/memberships": {
      "get": {
        "tags": [
          "Memberships & invites"
        ],
        "summary": "List the accounts in the household with their role and linked person",
        "operationId": "ListMemberships",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HouseholdMembershipResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/memberships/{userId}": {
      "patch": {
        "tags": [
          "Memberships & invites"
        ],
        "summary": "Change an account's role in the household",
        "description": "Owner only. 400 when the change would leave the household without an owner.",
        "operationId": "ChangeMembershipRole",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangeMembershipRoleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Memberships & invites"
        ],
        "summary": "Leave the household, or remove another account from it",
        "description": "Any member may remove their own membership; removing someone else is owner only (403 otherwise). 400 when the account is the household's last owner. The linked person, if any, stays in the household.",
        "operationId": "RemoveMembership",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "owner_required_remove_member"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/memberships/{userId}/member": {
      "patch": {
        "tags": [
          "Memberships & invites"
        ],
        "summary": "Link an account to the household person it belongs to, or unlink it",
        "description": "Any member may link their own account; linking someone else's is owner only (403 otherwise). memberId null unlinks. 400 when the person is not in this household. Planning uses the link to know who «you» are.",
        "operationId": "LinkMembershipMember",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LinkMembershipMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "owner_required_link_member"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/invites": {
      "post": {
        "tags": [
          "Memberships & invites"
        ],
        "summary": "Create a single-use invite code for a role, optionally for a named person",
        "description": "Owner only. The code is returned exactly once, here — only its hash is stored — and expires after 7 days. 400 when the named person is not in this household, already has an account, or already has a pending invite.",
        "operationId": "CreateInvite",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInviteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteCreatedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "get": {
        "tags": [
          "Memberships & invites"
        ],
        "summary": "List the household's pending invites",
        "description": "Owner only. Only unused, unexpired invites are listed; the codes themselves are never returned.",
        "operationId": "ListInvites",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/InviteResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/invites/{inviteId}": {
      "delete": {
        "tags": [
          "Memberships & invites"
        ],
        "summary": "Revoke an invite so its code can no longer be used",
        "description": "Owner only.",
        "operationId": "RevokeInvite",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "inviteId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households": {
      "post": {
        "tags": [
          "Households"
        ],
        "summary": "Create a household and become its owner",
        "description": "Any signed-in user. The caller becomes the household's owner, and a subscription on the default plan is opened with it; a first-time creator is promised the plan's welcome credits — one bundle per user, however many households they create (ADR-024). 403 with code invite_required or invite_code_invalid when intake is invite-only and no valid inviteCode came along, and intake_paused when intake is paused — the caller is then put on the waitlist (ADR-025).",
        "operationId": "CreateHousehold",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHouseholdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseholdResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}": {
      "get": {
        "tags": [
          "Households"
        ],
        "summary": "Get the household with its settings and sharing choices",
        "operationId": "GetHousehold",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseholdResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "patch": {
        "tags": [
          "Households"
        ],
        "summary": "Change one or more household settings, leaving the rest as they are",
        "description": "Owner only. Every field is «omit = leave it» (#689, #729, #754), so a settings screen may send just the field it changed. 400 for a blank name or an unknown measurementSystem / dryGoodsMeasure.",
        "operationId": "UpdateHousehold",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateHouseholdRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseholdResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Privacy"
        ],
        "summary": "Delete the household and everything it owns",
        "description": "Owner only. 400 with the reason while other people still use it — hand ownership over and leave, or remove them — or while a subscription is still linked (cancel it in the portal first). Every household-owned table goes with the household row (#299).",
        "operationId": "DeleteHousehold",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/onboarding/complete": {
      "post": {
        "tags": [
          "Households"
        ],
        "summary": "Finish the guided setup and write its recap into the day's thread",
        "description": "Any member, Junior included — refusing whoever holds the phone would only strand the setup (#413, ADR-043). The recap goes into today's thread, the one Home shows, under the morning message — not into a conversation of its own (#3378); conversationId is that thread. Safe to retry: a second call answers 200 with the original stamp and alreadyCompleted true, writing nothing; the defaultDinnerTime in the body is honoured on the completing call only. The recap is written from the household's own tables, not generated, so it costs no credits.",
        "operationId": "CompleteHouseholdOnboarding",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompleteOnboardingRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompleteOnboardingResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/members": {
      "get": {
        "tags": [
          "Members"
        ],
        "summary": "List the household's people with today's age and nutrition targets",
        "description": "Guests and inactive people are included. A person is not an account, and may have none.",
        "operationId": "ListHouseholdMembers",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HouseholdMemberResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Members"
        ],
        "summary": "Add a person to the household — adult, child, baby or usual guest",
        "description": "Adults only; a Junior member gets 403. A person is not an account: link one through Memberships. Without a birthDate the person is planned for as an adult (#599). Adding someone who eats dinner by default resizes every dinner still ahead and marks the shopping list stale (#340). 409 with code consent_required when isBreastfed or isPregnant is set and the household has given no explicit yes to storing health data (GDPR art. 9(2)(a), #2932) — add the person without it, then ask.",
        "operationId": "CreateHouseholdMember",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHouseholdMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseholdMemberResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "consent_required"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/members/{memberId}": {
      "patch": {
        "tags": [
          "Members"
        ],
        "summary": "Update a person's details, appetite, activity or nutrition overrides",
        "description": "Adults only; a Junior member gets 403. name, birthDate, defaultDinnerParticipant, isActive, isGuest and portionFactor are always applied; isBreastfed, isPregnant and nutritionOverrides are «omit = leave it» (#707). A change in who eats, or how much, resizes every dinner still ahead and marks the shopping list stale (#340). 409 with code consent_required when isBreastfed, isPregnant or nutritionOverrides is set and this person's explicit yes to storing health data is not on file (GDPR art. 9(2)(a), #2932); clearing any of them is always allowed.",
        "operationId": "UpdateHouseholdMember",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "memberId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateHouseholdMemberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseholdMemberResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "consent_required"
        ],
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Members"
        ],
        "summary": "Remove a person from the household",
        "description": "Adults only; a Junior member gets 403. Removing someone who ate dinner by default resizes every dinner still ahead and marks the shopping list stale (#340).",
        "operationId": "DeleteHouseholdMember",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "memberId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/constraints": {
      "get": {
        "tags": [
          "Constraints"
        ],
        "summary": "List the household's allergies, intolerances, diets and avoidances",
        "operationId": "ListDietaryConstraints",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DietaryConstraintResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Constraints"
        ],
        "summary": "Add a dietary constraint for the whole household or one person",
        "description": "Adults only; a Junior member gets 403. householdMemberId null means it applies to everyone at the table. 404 when the person is not in this household. 409 with code consent_required for an Allergy, Intolerance or DietaryPattern when the subject's explicit yes to storing health data is not on file (GDPR art. 9(2)(a), #2932); an Avoidance is a preference and needs none.",
        "operationId": "CreateDietaryConstraint",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDietaryConstraintRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DietaryConstraintResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "consent_required"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/constraints/{constraintId}": {
      "patch": {
        "tags": [
          "Constraints"
        ],
        "summary": "Change a constraint's kind, food, severity or notes",
        "description": "Adults only; a Junior member gets 403. The person it applies to cannot be changed here — delete it and add a new one. 409 with code consent_required when the new kind is health data and the subject's explicit yes is not on file (#2932).",
        "operationId": "UpdateDietaryConstraint",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "constraintId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDietaryConstraintRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DietaryConstraintResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "consent_required"
        ],
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Constraints"
        ],
        "summary": "Remove a dietary constraint",
        "description": "Adults only; a Junior member gets 403.",
        "operationId": "DeleteDietaryConstraint",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "constraintId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/preferences": {
      "get": {
        "tags": [
          "Preferences"
        ],
        "summary": "List the household's food likes and dislikes, declared and learned",
        "operationId": "ListFoodPreferences",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/FoodPreferenceResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Preferences"
        ],
        "summary": "Declare a like or dislike for the whole household or one person",
        "description": "Adults only; a Junior member gets 403. householdMemberId null means the whole household. 404 when the person is not in this household. Preferences made here carry source UserDeclared; learned ones come from Bryti.",
        "operationId": "CreateFoodPreference",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFoodPreferenceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FoodPreferenceResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/preferences/{preferenceId}": {
      "patch": {
        "tags": [
          "Preferences"
        ],
        "summary": "Change a preference's subject, subject type or sentiment",
        "description": "Adults only; a Junior member gets 403. The person it belongs to cannot be changed here — delete it and add a new one.",
        "operationId": "UpdateFoodPreference",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "preferenceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateFoodPreferenceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FoodPreferenceResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Preferences"
        ],
        "summary": "Remove a food preference",
        "description": "Adults only; a Junior member gets 403.",
        "operationId": "DeleteFoodPreference",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "preferenceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/rules": {
      "get": {
        "tags": [
          "Rules"
        ],
        "summary": "List the household's meal rules",
        "operationId": "ListMealRules",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MealRuleResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Rules"
        ],
        "summary": "Add a meal rule",
        "description": "Adults only; a Junior member gets 403. payload is a union on kind: CategoryFrequency, CategoryExcludedDays, CategoryOnDay, MaximumActiveCookingTime, DinnerTimeOnWeekday, PriceClassOnDays or RepeatAfterDays.",
        "operationId": "CreateMealRule",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMealRuleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MealRuleResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/rules/defaults": {
      "get": {
        "tags": [
          "Rules"
        ],
        "summary": "Get the national-guideline default rules and whether they apply right now",
        "description": "Defaults for the household's country (source Helsedirektoratet), with synthetic ids and never stored. Active while the household has no frequency rules of its own.",
        "operationId": "GetDefaultMealRules",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DefaultRulesResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/rules/parse": {
      "post": {
        "tags": [
          "Rules"
        ],
        "summary": "Turn a sentence into rule suggestions without saving anything",
        "description": "Adults only; a Junior member gets 403. «Fisk to ganger i uken, men ikke fredag» comes back as proposals, each with the CreateMealRuleRequest that files it — nothing is stored until the household POSTs it (#418). An empty sentence, or one with no rule in it, answers an empty list. Costs one chat turn of credits; 429 with code quota_exceeded when the household's AI allowance is used up.",
        "operationId": "ParseMealRules",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParseRulesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseRulesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-rate-limit": "ai-per-household"
      }
    },
    "/api/v1/households/{householdId}/rules/{ruleId}": {
      "patch": {
        "tags": [
          "Rules"
        ],
        "summary": "Replace a meal rule's payload, strength, scope and dates",
        "description": "Adults only; a Junior member gets 403. A full replacement despite the PATCH verb: every field is written.",
        "operationId": "UpdateMealRule",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMealRuleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MealRuleResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Rules"
        ],
        "summary": "Remove a meal rule",
        "description": "Adults only; a Junior member gets 403.",
        "operationId": "DeleteMealRule",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "ruleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/events": {
      "get": {
        "tags": [
          "Events"
        ],
        "summary": "Read the household's meal events a page at a time, earliest first",
        "description": "Guests, eating out, somebody away, an activity — whatever changes a day's table or dinner time. from and to are inclusive and both optional. ?limit= is 1–100, 50 by default. Each page hands back a nextCursor — give it back as ?cursor= for the next page; null on the last. A cursor nobody handed out is a 400. total is null: no surface counts evenings, and counting them would cost a second pass.",
        "operationId": "ListMealEvents",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedMealEventResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Events"
        ],
        "summary": "Add a meal event for a day: guests, eating out, somebody away, and so on",
        "description": "Adults, and a Junior member for one thing only: her own presence (PersonAbsent/PersonPresent for her own linked person, exactly one participant row, no serving time, cooking minutes, notes or guests, within 14 days). Anything else from a Junior is 403 junior_forbidden. The day's portions on the week's plan are re-derived for the new table. Eating out or travel closes the day and shrinks the week's shopping list on the spot (#1708, ADR-143); guests make the list stale instead, and the week asks before buying more (#340). A guest group's avoids — «nøtter», «melk», in the household's own words — bind the whole table that day as hard allergy constraints: the planner, the candidate gate, the serving notes and the tips all read them (#3109). A blank word, one over 200 characters or more than 20 of them is a 400.",
        "operationId": "CreateMealEvent",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMealEventRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MealEventResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/events/run": {
      "post": {
        "tags": [
          "Events"
        ],
        "summary": "Add a run of identical days — a trip, a weekend away — in one call",
        "description": "Adults only; a Junior member gets 403. One row per day in [from, through], all equal but for the date, written in a single transaction: either the whole run lands or none of it does. At most 31 days (400 beyond that, and 400 when through is before from). The rows come back in date order, and the reading side folds them into one card. Attendance and the week's shopping list are re-derived once for the run, not once per day (#2681, ADR-246).",
        "operationId": "CreateMealEventRun",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMealEventRunRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MealEventResponse"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "patch": {
        "tags": [
          "Events"
        ],
        "summary": "Rewrite a whole run: move it, change it, or shorten it, in one call",
        "description": "Adults only; a Junior member gets 403. days[i] is written on ids[i] — a full replacement, as the single-day PATCH is — and every id past the end of days is removed, which is how «Endre» shortens a trip. days is never longer than ids: a run cannot be extended (400, #2639/ADR-232). One transaction, so an id that is not this household's answers 404 with nothing written (#2681, ADR-246).",
        "operationId": "UpdateMealEventRun",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMealEventRunRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MealEventResponse"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Events"
        ],
        "summary": "Remove a whole run and give its days back their usual table",
        "description": "Adults only; a Junior member gets 403. Two ways to say which days: repeat ids in the query (ids=…&ids=…) to remove exactly those rows, or give type with from and through and let the span be resolved. ids is a query parameter and not a body — unlike the run PATCH, which carries ids[] in its request — because a DELETE with a body is awkward in most HTTP clients (#2737). With ids there is nothing to resolve and no 409: an id that is not this household's answers 404 with nothing written, in one transaction, the same rule the PATCH keeps (#2681, ADR-246). Without ids the span is resolved to exactly one run with the identity the reading side groups by — same kind, serving time, cooking minutes, notes and party, on days with no hole between them. No run covering exactly those days is 404; more than one run inside them is 409 and nothing is removed, because a single «Fjern» must not take away something the reader never saw named. Neither ids nor a whole span is 400.",
        "operationId": "DeleteMealEventRun",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/MealEventType"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "through",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "ids",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "run_not_unique"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/events/{eventId}": {
      "patch": {
        "tags": [
          "Events"
        ],
        "summary": "Replace a meal event's date, type, time, notes and participants",
        "description": "Adults only; a Junior member gets 403. A full replacement despite the PATCH verb: every field is written and the participant list is rebuilt from the request — a guest group's avoids included, so leaving them out takes them away (#3109).",
        "operationId": "UpdateMealEvent",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMealEventRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MealEventResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Events"
        ],
        "summary": "Remove a meal event and give the day back its usual table",
        "description": "Adults, and a Junior member for her own presence only — an event of exactly the shape she may write (PersonAbsent/PersonPresent, her own linked person as the one participant, nothing else on it, within 14 days). Anything else from a Junior is 403 junior_forbidden.",
        "operationId": "DeleteMealEvent",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/calendar-feeds": {
      "get": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "List the household's calendar feeds",
        "description": "Both kinds: Ics (a shared address the server fetches) and Device (a phone's own calendar the app pushes, #222). Pass deviceKey to have the row this very phone pushed marked isThisDevice — only that phone can act on «Oppdater nå» (#1186); the web app sends none. An ICS address is masked to scheme and host (https://host/…/•••) for every caller — it is a bearer key to the whole calendar, and only the answer to the create request carries it whole (#3581). isMine marks the device feeds the caller's own login pushed (ADR-087).",
        "operationId": "ListCalendarFeeds",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "deviceKey",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CalendarFeedResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Subscribe the household to an ICS calendar address",
        "description": "Adults only; a Junior member gets 403. The server fetches the address later, so it passes the same guard as recipe-URL import — http(s), standard ports, no loopback/private/link-local — and is refused with 400 otherwise (#979).",
        "operationId": "CreateCalendarFeed",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCalendarFeedRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarFeedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/calendar-feeds/{feedId}": {
      "delete": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Remove a calendar feed",
        "description": "Adults only; a Junior member gets 403. A device calendar is its owner's alone (ADR-087): another member removing it by id gets 403 (#3581).",
        "operationId": "DeleteCalendarFeed",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "feedId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/calendar-feeds/device": {
      "put": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Push this phone's own calendar events, replacing the last sync",
        "description": "Any member (US-510), but bound to the caller: the feed is keyed by the caller's login and deviceKey, created on the first sync and replaced whole on every one after — another member's key never matches, so nobody overwrites anyone else's (#1056, ADR-087). Only events from yesterday to 31 days ahead are kept, at most 500.",
        "operationId": "SyncDeviceCalendar",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SyncDeviceCalendarRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CalendarFeedResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/calendar-feeds/device/{deviceKey}": {
      "delete": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Remove this phone's calendar feed",
        "description": "Only the caller's own: another member's device key answers 404 rather than deleting theirs (ADR-087).",
        "operationId": "DeleteDeviceCalendar",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "deviceKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/calendar-link": {
      "get": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Read the household's private calendar link for the planned dinners",
        "description": "Any member, a Junior included: the dinners are already theirs to see. url and webcalUrl are null while the household has no link. The link is a bearer key to the household's planned dinners, so it is only ever sent to members (#3394).",
        "operationId": "GetDinnerCalendarLink",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DinnerCalendarLinkResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Make a new private calendar link and retire the old one",
        "description": "Adults only; a Junior member gets 403. The first call makes the link; every call after it makes a new one, and the link before it answers 404 from then on — the way to take the dinners back from a calendar the link was shared into (#3394).",
        "operationId": "RenewDinnerCalendarLink",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DinnerCalendarLinkResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Calendar feeds"
        ],
        "summary": "Turn the private calendar link off",
        "description": "Adults only; a Junior member gets 403. Every link the household has had answers 404 from then on, and the calendars subscribed to it stop getting the dinners (#3394).",
        "operationId": "StopDinnerCalendarLink",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/stores": {
      "get": {
        "tags": [
          "Stores"
        ],
        "summary": "List the stores the household shops at",
        "description": "Any member. Sorted by name, in display form (#1174).",
        "operationId": "GetHouseholdStores",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HouseholdStoreResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "put": {
        "tags": [
          "Stores"
        ],
        "summary": "Replace the household's stores with the given set",
        "description": "Adults only; a Junior member gets 403. The whole set is replaced on every save — send every store to keep, not just the new one. Safe to retry.",
        "operationId": "PutHouseholdStores",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PutHouseholdStoresRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HouseholdStoreResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/stores/nearby": {
      "get": {
        "tags": [
          "Stores"
        ],
        "summary": "Find stores near an address or a position",
        "description": "Any member. Send ?address= or ?lat=&lng= — coordinates win when both are sent (#1244). Within 2.5 km, nearest first; AddressFound is false when the address did not geocode, so ask for a better one. 400 when neither is sent, or the point is not on Earth. Each call goes to Kartverket, so it is rate-limited per household — ten map lookups a minute, in one bucket with geocode/reverse (#1619, ADR-127): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "FindNearbyStores",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "address",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "lat",
            "in": "query",
            "schema": {
              "type": "number",
              "format": "double"
            }
          },
          {
            "name": "lng",
            "in": "query",
            "schema": {
              "type": "number",
              "format": "double"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NearbyStoresResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-problem-codes": [
          "store_search_bad_point",
          "store_search_needs_place"
        ],
        "x-rate-limit": "geocode-per-household"
      }
    },
    "/api/v1/households/{householdId}/stores/geocode/reverse": {
      "get": {
        "tags": [
          "Stores"
        ],
        "summary": "Turn a position into a street address the household can correct before saving",
        "description": "Any member. The way back from «use my position» on the web (#1265, ADR-113); Address is null when nothing is near the point. 400 when lat or lng is missing or off the globe. Same per-household bucket as nearby — ten lookups a minute (#1619, ADR-127): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "ReverseGeocode",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "lat",
            "in": "query",
            "schema": {
              "type": "number",
              "format": "double"
            }
          },
          {
            "name": "lng",
            "in": "query",
            "schema": {
              "type": "number",
              "format": "double"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReverseGeocodeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-problem-codes": [
          "store_search_bad_point"
        ],
        "x-rate-limit": "geocode-per-household"
      }
    },
    "/api/v1/households/{householdId}/stores/address-suggestions": {
      "get": {
        "tags": [
          "Stores"
        ],
        "summary": "Suggest addresses while the household types one",
        "description": "Any member. ?query= shorter than three characters answers an empty list without asking the provider.",
        "operationId": "SuggestAddresses",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "query",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AddressSuggestionResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/conversations": {
      "post": {
        "tags": [
          "Chat"
        ],
        "summary": "Start a new conversation with Bryti",
        "description": "The body is optional: an empty POST starts an untitled thread, which is named after its first message. The starter is recorded so a data export carries only that person's own conversations (#984, ADR-084).",
        "operationId": "CreateConversation",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/CreateConversationRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "get": {
        "tags": [
          "Chat"
        ],
        "summary": "List the household's conversations, newest activity first",
        "description": "One page at a time (ADR-292, #3002): cursor and limit in, items/nextCursor out, 50 rows by default and 100 at most. Home writes a thread a day, so a household that stays a year has 365 of them. total is null — nobody counts the threads above a switcher. Threads without messages never list: the apps create threads lazily on the first send, so an empty one is an abandoned draft, hidden at once and deleted after a day. Opening the list also makes sure the deterministic «update the pantry?» nudge thread exists.",
        "operationId": "ListConversations",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedConversationSummaryResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/conversations/{conversationId}": {
      "delete": {
        "tags": [
          "Chat"
        ],
        "summary": "Delete a conversation and everything in it",
        "description": "Messages and proposals go with it.",
        "operationId": "DeleteConversation",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "get": {
        "tags": [
          "Chat"
        ],
        "summary": "Read a window of a conversation's newest messages",
        "description": "Always a window (ADR-292, #3002, #2965): the newest 40 messages when no limit is named, 200 at most, and nextCursor says where the older page starts — hand it back as ?cursor= to read it. hasEarlier is the same fact as a non-null nextCursor. before is the old spelling of the same question, a timestamp instead of a cursor; it still answers, and disappears once the app version floor has been raised past the builds that ask with it. Proposals are those of the messages in the window plus any still open, however old their message is.",
        "operationId": "GetConversation",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/conversations/{conversationId}/report": {
      "post": {
        "tags": [
          "Chat"
        ],
        "summary": "Report a reply from Bryti as offensive or wrong",
        "description": "Any member may press it, a Junior included (#349). The body is optional: a bare POST reports the thread, messageId narrows it to one reply, and reason plus a note of at most 500 characters say why (ADR-145). Safe to retry: a second report of the same message leaves one row and still answers 204. 400 when the note is too long.",
        "operationId": "ReportChatContent",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/ReportChatContentRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/conversations/{conversationId}/messages": {
      "post": {
        "tags": [
          "Chat"
        ],
        "summary": "Send a message to Bryti and wait for the whole reply",
        "description": "The non-streaming twin of the stream route, kept for clients without SSE support: same body, same rules, and costs credits like any turn. continuation asks Bryti to carry on after «Accept all» (#23); replacesMessageId edits the household's last own message, deleting everything after it (ADR-145). Send an Idempotency-Key header to make a retry safe.\n\n409 chat.edit_not_last / chat.edit_applied when the thread no longer allows that edit — reload the thread instead of retrying; 409 duplicate_ai_request when the Idempotency-Key was already accepted — refresh instead of retrying. 429 quota_exceeded when the household's monthly AI allowance is spent, or ai_paused when the platform's cost guard has paused AI this month (ADR-023, Retry-After set).",
        "operationId": "SendChatMessage",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "maxLength": 80,
              "pattern": "^[\\x21-\\x7E]+$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendChatMessageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendChatMessageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-rate-limit": "ai-per-household"
      }
    },
    "/api/v1/households/{householdId}/conversations/{conversationId}/messages/stream": {
      "post": {
        "tags": [
          "Chat"
        ],
        "summary": "Send a message to Bryti and stream the reply (SSE)",
        "description": "Same body and rules as the non-streaming route; costs credits. The response is text/event-stream: a ping first (and every 10 s of silence), then started with the aiRequestId the stop route needs, then any number of text_delta, tool_started, tool_completed, proposal and widget events. The stream ends with exactly one of completed (both messages and the turn's proposals), stopped (the household pressed Stop; whatever was reached is kept and nothing is charged, #1681) or error. When the turn failed part-way, error is followed by completed with a failed message.\n\nClosing the socket does not cancel the turn: it finishes in the background, the reply is persisted, and a push tells the phone (#200). 409 and 429 mean the same as on the non-streaming route and arrive as a plain problem before any event, never mid-stream.",
        "operationId": "StreamChatMessage",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "maxLength": 80,
              "pattern": "^[\\x21-\\x7E]+$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendChatMessageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-rate-limit": "ai-per-household"
      }
    },
    "/api/v1/households/{householdId}/conversations/{conversationId}/turns/{aiRequestId}/stop": {
      "post": {
        "tags": [
          "Chat"
        ],
        "summary": "Stop the reply Bryti is writing right now",
        "description": "aiRequestId is what the stream's started event named (#1681). Answers only once the turn has unwound and persisted what it reached, so the thread can be refetched the moment this returns. stopped: false means the turn was already over — not an error. A stopped turn is not charged. 404 when the turn is not this household's.",
        "operationId": "StopChatTurn",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "conversationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "aiRequestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StopChatTurnResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/chat-permission-mode": {
      "get": {
        "tags": [
          "Chat"
        ],
        "summary": "Read how eagerly Bryti applies proposed changes without asking",
        "operationId": "GetChatPermissionMode",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatPermissionModeResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "put": {
        "tags": [
          "Chat"
        ],
        "summary": "Set how eagerly Bryti applies proposed changes without asking",
        "description": "Adults only; a Junior member gets 403. AlwaysAsk, AutoApplyLowRisk or AutoApplyAll; every write still records a proposal whatever the mode.",
        "operationId": "SetChatPermissionMode",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateChatPermissionModeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatPermissionModeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/action-proposals/{proposalId}/apply": {
      "post": {
        "tags": [
          "Chat"
        ],
        "summary": "Apply a change Bryti proposed",
        "description": "Adults only; a Junior member gets 403 — a Junior may ask for anything in chat, but an adult applies it. A card that changes the household's own settings or language is the owner's, as PATCH households/{id} is: anyone else gets 403 with code owner_required and nothing changes (ADR-415 (#3423)). Runs the same handler the ordinary forms use, in one transaction: either the whole change lands, or the card comes back 200 with status Failed and its error and nothing changed. A proposal that plans the week puts the run in the planning queue and comes back Running with its jobId (GET plans/runs/{jobId} says how it goes); the card turns Applied, with its outcome, when the run lands, or Failed (ADR-337 (#3184)). It costs credits. 409 with code consent_required when the card would store an allergy, an intolerance, a diet tied to a condition or breastfeeding and the subject's explicit yes is not on file (GDPR art. 9(2)(a), #3077): the card stays pending, so it works once the yes is given. 409 without a code when another press took the card in the same moment: the change ran once, for that press (#3586).",
        "operationId": "ApplyActionProposal",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "proposalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionProposalResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "consent_required"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/action-proposals/{proposalId}/undo": {
      "post": {
        "tags": [
          "Chat"
        ],
        "summary": "Take back an applied change",
        "description": "Adults only; a Junior member gets 403. Only within 24 hours of applying, and only when the change has an inverse (undoable on the card) — 400 otherwise (#63). 409 when another press took the change back in the same moment (#3586).",
        "operationId": "UndoActionProposal",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "proposalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionProposalResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/action-proposals/{proposalId}/redo": {
      "post": {
        "tags": [
          "Chat"
        ],
        "summary": "Do an undone change again",
        "description": "Adults only; a Junior member gets 403. Only an undone change can be redone (400 otherwise). The action re-runs from its stored payload, so a coherence failure — a duplicate member, a date already covered — also comes back as 400 and the card stays undone. 409 with code consent_required when the change would store health data the subject has not said yes to (#3077); 409 without a code when another press did it again in the same moment (#3586).",
        "operationId": "RedoActionProposal",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "proposalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionProposalResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "consent_required"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/action-proposals/{proposalId}/reject": {
      "post": {
        "tags": [
          "Chat"
        ],
        "summary": "Reject a change Bryti proposed",
        "description": "Adults only; a Junior member gets 403.",
        "operationId": "RejectActionProposal",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "proposalId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActionProposalResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/usage": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "Get the household's AI credits for the current billing period",
        "description": "Any household member. Credits, never tokens or cost (SUBS.md §23–24): used, limit, remaining and bonus, the soft/allowance-spent/hard flags the apps warn on, and what one chat turn costs (#418). Reading rolls the period forward when it has ended but never spends anything.",
        "operationId": "GetHouseholdUsage",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/milestones": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "Get the household's first-week milestones and where the first week stands",
        "description": "Any household member. Each milestone (first plan, first list opened, first trip completed, first receipt read, first swap, first useful week) is written once, by the server, when the work happens. FirstWeek is the phase in the order plan, list, receipt, swap, and the step Bryti suggests next (ADR-391 (#3342)).",
        "operationId": "GetHouseholdMilestones",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HouseholdMilestonesResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/upgrade-prompts": {
      "get": {
        "tags": [
          "Usage"
        ],
        "summary": "List the upgrade moments that are active for the household right now",
        "description": "Any household member. The server decides (ADR-391 (#3342)): Introduction after the first plan and list on the free plan, QuotaHigh at 80 % of the period with five days left, SustainedHighUse for Familie at 80 % two periods running, never both of the last two. Moment 4 is the 429 quota_exceeded itself and is never listed. The clients draw what they get, never in a chat reply.",
        "operationId": "GetUpgradePrompts",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpgradePromptsResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Usage"
        ],
        "summary": "Record that an upgrade moment was shown, dismissed or opened",
        "description": "Any household member. Kept on the household, not in the browser, so «Ikke nå» holds on every device: Dismissed hides Introduction from Home for good, QuotaHigh for the period and SustainedHighUse for 90 days. OverQuota may be posted too, only to be counted. Idempotent per moment and window.",
        "operationId": "RecordUpgradePrompt",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecordUpgradePromptRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/intake": {
      "get": {
        "tags": [
          "Intake"
        ],
        "summary": "Read whether sign-up is open, invite-only or paused",
        "description": "Public — the sign-up screen asks before anyone has an account (ADR-025, #603). Cached for 60 seconds (Cache-Control: public, max-age=60). storeLinks carries the App Store and Google Play listings the owner has set (Landing__StoreLinks__Ios/Android); each is null until set (#3486).",
        "operationId": "GetIntakeStatus",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IntakeStatusResponse"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-rate-limit": "anonymous-per-ip"
      }
    },
    "/api/v1/intake/waitlist": {
      "post": {
        "tags": [
          "Intake"
        ],
        "summary": "Join the waitlist by e-mail, before having an account",
        "description": "Public (ADR-147). Always 202 whatever the list already knew about the address — a duplicate, a rejected address and a cooled-down resend all look the same, so the list never becomes a directory. A confirmation mail goes out for a new entry, and again for an unconfirmed one at most once a day; an unconfirmed entry past seven days is replaced. 400 only for the caller's own input (no «@», a note over 280 characters). Rate-limited per IP (intake-per-ip, 5 an hour by default): 429 with Retry-After.",
        "operationId": "JoinIntakeWaitlist",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JoinWaitlistRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "security": [ ],
        "x-rate-limit": "intake-per-ip"
      }
    },
    "/api/v1/intake/waitlist/me": {
      "post": {
        "tags": [
          "Intake"
        ],
        "summary": "Put the signed-in caller on the waitlist",
        "description": "Any signed-in user, no household needed — the «Opprett husholdning» screen while intake is paused (ADR-025). The account vouches for the address, so no confirmation mail. Safe to retry: one place per user, and an entry that joined by e-mail earlier becomes this user's.",
        "operationId": "JoinIntakeWaitlistAsUser",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistEntryResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/intake/waitlist/confirm": {
      "get": {
        "tags": [
          "Intake"
        ],
        "summary": "Confirm a waitlist address from the link in the mail",
        "description": "Public — the link opens in a browser that has never seen Bryti. Safe to retry: a second click is still confirmed. 404 (code waitlist_token_unknown) when no entry carries the token; 410 (code waitlist_token_expired) when the entry outlived its seven days unconfirmed — join again for a new link.",
        "operationId": "ConfirmIntakeWaitlist",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WaitlistConfirmationResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "410": {
            "description": "Gone",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "security": [ ],
        "x-problem-codes": [
          "waitlist_token_expired",
          "waitlist_token_unknown"
        ],
        "x-rate-limit": "anonymous-per-ip"
      }
    },
    "/api/v1/households/{householdId}/billing": {
      "get": {
        "tags": [
          "Billing"
        ],
        "summary": "Read the household's plan, billing status and the plans it could buy",
        "description": "Any member. PaymentsEnabled is false when no provider keys are configured — the apps then hide the upgrade buttons. CanManage means a provider-side customer exists to open the portal for.",
        "operationId": "GetBillingStatus",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingStatusResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/billing/checkout": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Start a hosted checkout for a plan and get the URL to send the caller to",
        "description": "Adults only; a Junior member gets 403. ReturnUrl must be an absolute http(s) URL; the provider sends the caller back to it with ?checkout=success or ?checkout=cancelled. 400 for an unknown plan or a plan that cannot be bought online. 409 (code already_subscribed) when the household already has a paid subscription — refetch the billing status and change plan from the portal instead (#3577). 503 (code billing_disabled) when online payments are switched off in this environment (ADR-026). 403 (code email_not_confirmed) when the caller's own e-mail address has not been confirmed — the receipts go there (ADR-290).",
        "operationId": "StartBillingCheckout",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartCheckoutRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RedirectResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "email_not_confirmed"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/billing/preview": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Preview what changing to a plan would charge now, give now and charge next",
        "description": "Adults only; a Junior member gets 403. Nothing is charged or changed. A bigger plan applies now and pays the difference for the days left of the period (ADR-392 (#3342)): the amount comes from Stripe's preview of the upcoming invoice when the household pays through Stripe, otherwise from the same proration done here with Estimated set. A smaller plan pays nothing now and applies from the next period. From the free plan the full month is paid now. 400 for an unknown plan or one that cannot be bought online; 503 (code billing_disabled) when online payments are switched off.",
        "operationId": "PreviewBillingChange",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingPreviewRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingPreviewResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/billing/portal": {
      "post": {
        "tags": [
          "Billing"
        ],
        "summary": "Open the provider's billing portal and get the URL to send the caller to",
        "description": "Adults only; a Junior member gets 403. For managing or cancelling the subscription. 400 when the household has not paid online yet; 503 (code billing_disabled) when online payments are switched off.",
        "operationId": "OpenBillingPortal",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenPortalRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RedirectResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/export": {
      "get": {
        "tags": [
          "Privacy"
        ],
        "summary": "Export everything the household has put into Bryti as one document",
        "description": "Any member (GDPR art. 20). Derived data the app can rebuild — candidate pools, price caches — and internal costs are left out. Personal to the caller: only their own conversations and push devices are included, and co-members appear by name and role only (#984, ADR-084).",
        "operationId": "ExportHouseholdData",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataExportResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/deletion-preview": {
      "get": {
        "tags": [
          "Privacy"
        ],
        "summary": "See what deleting the household would take with it, and whether it is allowed",
        "description": "Owner only. CanDelete is false with BlockerCode other_members while another login still belongs, or billing_subscription while a Stripe subscription is linked (#299).",
        "operationId": "PreviewHouseholdDeletion",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteHouseholdPreviewResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/me/deletion-preview": {
      "get": {
        "tags": [
          "Privacy"
        ],
        "summary": "See what deleting the account would do to each of the caller's households",
        "description": "Any signed-in user. A household nobody else uses is deleted with the account; a shared one just loses this member. CanDelete is false, with BlockerCode sole_owner_with_members, while the caller owns a household others still use. Blocker is the same thing in an English sentence, for a log — a screen says it from the code and Households (#2362).",
        "operationId": "PreviewAccountDeletion",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteAccountPreviewResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/consents": {
      "get": {
        "tags": [
          "Privacy"
        ],
        "summary": "List the household's explicit yesses, given and withdrawn",
        "description": "Any member. Withdrawn rows stay in the list with the moment they were taken back: the register answers «what was agreed, to which words, and until when» (GDPR art. 7(1)). householdMemberId null means the consent covers everyone at the table.",
        "operationId": "ListConsents",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ConsentResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Privacy"
        ],
        "summary": "Record an explicit yes to storing health data about one person",
        "description": "Adults only; a Junior member gets 403 — a child cannot consent for themselves, and the adult who presses it consents for a child in their care (art. 8). textVersion is the version of the text that was on the screen, so the stored yes can always be read back as the words the person saw; the register of versions is docs/GDPR.md §10. Giving a yes that already stands returns the standing one rather than a second row.",
        "operationId": "GiveConsent",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GiveConsentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConsentResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/consents/{consentId}": {
      "delete": {
        "tags": [
          "Privacy"
        ],
        "summary": "Take a yes back and clear the health data it allowed",
        "description": "Adults only; a Junior member gets 403. The row is kept and stamped withdrawn — deleting it would say the yes never happened — and in the same transaction the allergies, intolerances and diets recorded for the subject are deleted and the person's breastfeeding, pregnancy and hand-set nutrition targets go back to «nothing said» (art. 7(3)). A consent that is already withdrawn answers 204 and clears nothing again.",
        "operationId": "WithdrawConsent",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "consentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/me/devices": {
      "put": {
        "tags": [
          "Push"
        ],
        "summary": "Register this phone's push token for the signed-in login",
        "description": "Idempotent per token: a token already known is marked seen and handed to the caller's login — a reinstall, or a new login on the same phone — never duplicated (#198). platform is ios or android; provider defaults to expo. A device belongs to the person, not to a household.",
        "operationId": "RegisterPushDevice",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterPushDeviceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PushDeviceResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/me/devices/{token}": {
      "get": {
        "tags": [
          "Push"
        ],
        "summary": "Read a registered phone and its notification switches",
        "description": "404 when the token is not registered to the caller's login.",
        "operationId": "GetPushDevice",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PushDeviceResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "patch": {
        "tags": [
          "Push"
        ],
        "summary": "Set which notifications reach this phone",
        "description": "listChanges, householdNews, runDone and reminders. reminders left out keeps its current value, so an older app's three-switch PATCH does not turn Bryti's nudges off behind the person's back (#385). 404 when the token is not the caller's.",
        "operationId": "UpdatePushDevice",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePushDeviceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PushDeviceResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Push"
        ],
        "summary": "Forget this phone: nothing more is sent to it",
        "description": "Sign-out on the phone. 404 when the token is not the caller's.",
        "operationId": "ForgetPushDevice",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/candidates/generate": {
      "post": {
        "tags": [
          "Candidates"
        ],
        "summary": "Put a day's new dinner suggestions in the planning queue",
        "description": "Adults only; a Junior member gets 403. Answers 202 with the run's id (ADR-289): the model call is a background job, GET plans/runs/{jobId} says where it stands, and GET candidates for the day has the suggestions once it is done. Costs credits and replaces the day's current candidates, except one a plan entry still points at. A run that ends in candidate_pool_in_plan — somebody planned the week over these very candidates while they were being replaced (#533) — says so on its status; read the day again and ask once more.",
        "operationId": "GenerateMealCandidates",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateCandidatesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanRunAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "ai-per-household"
      }
    },
    "/api/v1/households/{householdId}/candidates": {
      "get": {
        "tags": [
          "Candidates"
        ],
        "summary": "List the candidates generated for one day",
        "description": "date is required. What is missing from the pantry is recomputed on every read — no model call, no write. price is each dish's estimated kroner per portion and class (#3226), from the household's receipts, the store catalogue or the table.",
        "operationId": "ListMealCandidates",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MealCandidateResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/candidates/range": {
      "get": {
        "tags": [
          "Candidates"
        ],
        "summary": "List the candidates for every day in a window of at most 92 days",
        "description": "One round-trip for a whole week (#1017); ordered by date, then by when each candidate was created. from and to are both required and the window may cover at most 92 days — a calendar read, not a page (#2977, ADR-292). A day's hand is a dozen dishes with their ingredients on each, so the window is what bounds the answer: three years of it was 2.4 MB.",
        "operationId": "ListMealCandidatesRange",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MealCandidateResponse"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/candidates/use-up": {
      "get": {
        "tags": [
          "Candidates"
        ],
        "summary": "Read the use-up section for one day: the swaps that would use what is running out, as the day's Swap panel shows them.",
        "description": "The bundle is the same as the week's (ADR-173): what remains after the planned use this week and next, and the fewest changes that would use it. This is the day's slice of it. Read-only; applying goes through the home use-up endpoints.",
        "operationId": "GetUseUpSectionForDay",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HomeUseUpBundleQuestion"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes": {
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "Read the household's own shelf a page at a time: its own and generated recipes, and every base recipe — or one of those sources alone",
        "description": "Any member. One page of rows — id, title, category, minutes, portions, tags, picture and the household's own marks (favorites, times cooked, excluded from planning). The ingredients, the steps, the named parts and the folded base recipes are one GET …/recipes/{id} away (#2960). ?q= matches the title and the ingredient names, case-insensitively; ?category= is a MealCategory and may be repeated — any of them (#3177); ?maxTotalMinutes= keeps the dishes at most that many minutes from start to table (#3177); ?favourites=true keeps only the dishes the household has marked; ?isBase= and ?isDinner= keep doughs or dinners only; ?source= is one RecipeSource and keeps that section alone — User is the book's «Bare egne», the household's own writing and its own base recipes (#3055); ?sort= is Title (default) or ActiveMinutes. ?limit= is 1–100, 50 by default. The order is the shelf's own — dinners before base recipes, the household's own before generated, then the sort — so the reader's sections stay whole across pages. `total` counts every match for the filter, and `nextCursor` — opaque — is handed back as ?cursor= for the next page; null on the last. A cursor cut under another sort, or one nobody handed out, is a 400. The shared starter library is not in this list since #2828: read it from GET …/recipes/library.",
        "operationId": "ListRecipes",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/MealCategory"
              }
            }
          },
          {
            "name": "favourites",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "isBase",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "isDinner",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/RecipeSource"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/RecipeListSort"
            }
          },
          {
            "name": "maxTotalMinutes",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedRecipeListItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Recipes"
        ],
        "summary": "Add a recipe of the household's own",
        "description": "Adults only; a Junior member gets 403 — writing the shared cookbook is household-shaping (#980). Created recipes are always Source = User.",
        "operationId": "CreateRecipe",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaveRecipeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/facets": {
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "Count what each of the book's filters would leave, over the whole book",
        "description": "Any member. The numbers the book's «Filtrer» sheet shows (#3177): `total` for the whole filter, and for each group — every category the book holds, the time-to-table steps (any, ≤ 20, ≤ 30, ≤ 45 minutes), the three sources and the favourites — how many dishes that choice would leave under the word typed and the other groups' choices. Takes the list's own ?q=, ?category= (repeatable), ?favourites= and ?maxTotalMinutes=, and ?source= as All (default), Own or Library — the household's shelf and the starter library together, the household's own writing, or the library alone.",
        "operationId": "GetRecipeFacets",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/MealCategory"
              }
            }
          },
          {
            "name": "favourites",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "maxTotalMinutes",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "source",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/RecipeBookSource"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeFacetsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/tags": {
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "List the tags the household has used on its own recipes",
        "description": "Any member. Distinct, trimmed, lowercased and sorted — the vocabulary the editor's autocomplete offers. The shared library's tags are not in it: they are Bryti's words, not this household's.",
        "operationId": "ListRecipeTags",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/library": {
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "Read the shared starter library a page at a time, searched and sorted on the server",
        "description": "Any member. Only the shared seed recipes (never the household's own). ?q= matches the title and the ingredient names, case-insensitively; ?category= is a MealCategory and may be repeated — any of them (#3177); ?maxTotalMinutes= keeps the dishes at most that many minutes from start to table (#3177); ?favourites=true keeps only the dishes this household has marked; ?sort= is Default (by title), Title or Quickest (active minutes, then title) — the enum's names, as ?category= takes the category's. ?limit= is 1–100, 50 by default. Each recipe carries the household's own marks like the list does, `total` counts every match for the filter, and `nextCursor` — opaque — is handed back as ?cursor= for the next page; null on the last. A cursor cut under another sort, or one nobody handed out, is a 400 (#2828).",
        "operationId": "SearchRecipeLibrary",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/MealCategory"
              }
            }
          },
          {
            "name": "favourites",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/RecipeLibrarySort"
            }
          },
          {
            "name": "maxTotalMinutes",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedRecipeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}": {
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "Read one recipe, with its linked base recipes folded in",
        "operationId": "GetRecipe",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "patch": {
        "tags": [
          "Recipes"
        ],
        "summary": "Edit one of the household's own recipes",
        "description": "Adults only; a Junior member gets 403. The whole content is replaced. Only Source = User recipes can be edited — a seed or generated recipe answers 400 (save a variant instead). Null StepSections, References and IsBase keep what the recipe has; an empty list clears them (ADR-154 pt. 10). Every edit is recorded in the history.",
        "operationId": "UpdateRecipe",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SaveRecipeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Recipes"
        ],
        "summary": "Delete one of the household's own or generated recipes",
        "description": "Adults only; a Junior member gets 403. Seed recipes cannot be deleted (400). 409 with code recipe_in_plan and the date when the recipe is a dinner still to come — swap that day out first (#1897, ADR-168). Days already past keep their history and do not block.",
        "operationId": "DeleteRecipe",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/history": {
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "List a page of a recipe's edit history, newest first",
        "description": "Any member. Cursor-paged (ADR-292): 50 revisions by default, 100 at most, and Total is every revision the recipe has — what «Vis alle 12» says. Empty for a recipe nobody has edited since history was recorded. 400 on a limit out of range or a cursor this endpoint did not hand out.",
        "operationId": "GetRecipeHistory",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedRecipeRevisionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/fit": {
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "Check whether a recipe suits the household or one evening's table",
        "description": "Without date the whole household's constraints apply; with date, the people eating that evening. Says what a variant would swap or leave out, whether one can be made at all, and whether the household already saved one with that title (#742, ADR-072).",
        "operationId": "GetRecipeFit",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeFitResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/tips": {
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "Read the kitchen tips that fit one recipe",
        "description": "Any member. At most three tips, best first, each judged to help with this dish as written: technique, preparation or rescue. Only Bryti's library recipes have any; the household's own and imported recipes get none. The text is Norwegian and carries no source (#3743).",
        "operationId": "GetRecipeTips",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeTipsResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/variants": {
      "post": {
        "tags": [
          "Recipes"
        ],
        "summary": "Save a variant of the recipe as the household's own",
        "description": "Adults only; a Junior member gets 403. Two kinds that never coincide: a swap the table needs (default — «uten melk», #742) or the tips the household marked as used in cook mode (FromTips, #696, ADR-076). Safe to retry: a variant with that title already saved is returned with AlreadyExisted = true rather than duplicated. With PlanOnDate the variant is also put on Date as that day's dinner. 400 when FromTips and PlanOnDate are combined, when the dish already suits the table, or when a conflict has no honest substitute.",
        "operationId": "CreateRecipeVariant",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRecipeVariantRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeVariantResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/favorite": {
      "put": {
        "tags": [
          "Recipes"
        ],
        "summary": "Mark or unmark a recipe as a favorite for one member or the whole household",
        "description": "Any member, Junior included. Null HouseholdMemberId means the whole household. Safe to retry: setting an existing mark or clearing a missing one is a no-op. 400 when the member is outside this household.",
        "operationId": "SetRecipeFavorite",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetRecipeFavoriteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/exclusion": {
      "put": {
        "tags": [
          "Recipes"
        ],
        "summary": "Tell the planner never to propose this recipe again, or undo that",
        "description": "Adults only; a Junior member gets 403. Per household — the recipe stays in the cookbook, marked, with «Angre» (#740, ADR-075). Safe to retry: idempotent both ways.",
        "operationId": "SetRecipeExclusion",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetRecipeExclusionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/references/{referenceId}/link": {
      "put": {
        "tags": [
          "Recipes"
        ],
        "summary": "Link a recipe reference to a base recipe, or unlink it",
        "description": "Adults only; a Junior member gets 403 — nothing links on a title match alone (ADR-154 pt. 7). Null LinkedRecipeId unlinks. Only on the household's own recipes; 400 when the target is outside the library, is the recipe itself, or already points back at it (ADR-154 pt. 2). Answers the host recipe as it now reads.",
        "operationId": "SetRecipeReferenceLink",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "referenceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetReferenceLinkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/references/{referenceId}/candidates": {
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "List a page of the recipes a reference could be linked to, by name",
        "description": "Adults only; a Junior member gets 403. Cursor-paged (ADR-292), ordered by title. Kind picks which of the sheet's three lists: Suggestions is the normalised-title match on the reference's own label («Er det denne?»), Bases is the household's base recipes, and All — the default — is everything it can see except the dish itself, for the search box. q matches the title, case-insensitively. 400 on a limit out of range or a cursor cut under another kind.",
        "operationId": "ListRecipeReferenceLinkCandidates",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "referenceId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ReferenceCandidateKind"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedReferenceLinkCandidateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/sections/extract": {
      "post": {
        "tags": [
          "Recipes"
        ],
        "summary": "Lift a named part of a recipe out into a base recipe of its own",
        "description": "Adults only; a Junior member gets 403. Only on the household's own recipes. The part becomes a base recipe and the host keeps a linked reference in its place, in one transaction (ADR-154 pt. 9). 400 when the part has no ingredients or no steps of its own; 404 when no part has that name.",
        "operationId": "ExtractRecipeSection",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractSectionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractSectionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/recipes/{recipeId}/image": {
      "post": {
        "tags": [
          "Recipes"
        ],
        "summary": "Put a photograph of the dish on one of the household's own recipes",
        "description": "Adults only; a Junior member gets 403. JSON body: a base64 JPEG whose long side is at most 2 000 px, 5 MB at most — the apps scale and convert HEIC and PNG before sending. 400 with code image_not_jpeg or image_too_large otherwise, and recipe_not_own for a starter-library or generated recipe (save a variant first). The camera's metadata is removed; the file is the household's and replaces any picture the recipe had (ADR-343 (#3181)).",
        "operationId": "SetRecipeImage",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetRecipeImageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Recipes"
        ],
        "summary": "Take the photograph off one of the household's own recipes",
        "description": "Adults only; a Junior member gets 403. Removes the picture whether the household took it or the import found it; the household's own file is deleted. 400 with code recipe_not_own for a recipe that is not the household's own.",
        "operationId": "DeleteRecipeImage",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "get": {
        "tags": [
          "Recipes"
        ],
        "summary": "Read the household's own photograph of a recipe",
        "description": "Any member. The address a recipe's ImageUrl names when the household put the picture there itself; served only to the household, never linked elsewhere. 404 when the recipe has no photograph of the household's own.",
        "operationId": "GetRecipeImage",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/wishes": {
      "get": {
        "tags": [
          "Wishes"
        ],
        "summary": "List the dishes the household has wished for, most votes first",
        "description": "Any member, Junior included. A wish falls off the list once its dish has been recorded as eaten on or after the day it was wished.",
        "operationId": "ListDishWishes",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DishWishResponse"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "post": {
        "tags": [
          "Wishes"
        ],
        "summary": "Wish for a dish, by recipe or by name, for oneself or for a child",
        "description": "Any member, Junior included. Wishing for a dish already on the list is a vote for it, so the call is safe to retry. 404 when RecipeId names no dish the household can see; 400 for an empty name or a member outside the household.",
        "operationId": "CreateDishWish",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDishWishRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DishWishResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/wishes/{wishId}/vote": {
      "put": {
        "tags": [
          "Wishes"
        ],
        "summary": "Give or take back a thumbs-up on a wished dish",
        "description": "Any member, Junior included; one vote per person per wish. Idempotent both ways. ForMemberId votes for a child in the household.",
        "operationId": "SetDishWishVote",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "wishId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetDishWishVoteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DishWishResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/wishes/{wishId}": {
      "delete": {
        "tags": [
          "Wishes"
        ],
        "summary": "Take a wish off the household's list",
        "description": "An adult, or the Junior who made the wish; 403 for another Junior's wish.",
        "operationId": "DeleteDishWish",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "wishId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/generate": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Put the week in the planning queue and answer with the run",
        "description": "Adults only; a Junior member gets 403. Answers 202 with the run's id (ADR-289): the week is built by a background job, GET plans/runs/{jobId} says where the run stands, and the phone is told by push when the week lands (#200). Costs credits unless the cookbook or the starter library answers every day — GET cost-estimate says the price first. A second press while the run is going joins it and gets the same run id back; 409 with code week_planning_in_progress only for two presses in the very same instant (ADR-073). What the run itself can end in — rules that cannot all be met, a provider that never answered — is on the run's status, not here.",
        "operationId": "GenerateWeeklyPlan",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GeneratePlanRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanRunAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "ai-per-household"
      }
    },
    "/api/v1/households/{householdId}/plans/runs/{jobId}": {
      "get": {
        "tags": [
          "Plans"
        ],
        "summary": "Say where a planning run stands, and why it ended if it ended badly",
        "description": "Any member — the week is the household's and so is the wait. status is queued, running, done or failed; queuePosition counts from one while the run waits, and counts the runs already in the model, because they are what it is waiting for. planId is the week the run settled, once it is done. failure carries the problem the run would have answered with while a request was still open — planning_conflict with its errors, for one (ADR-289).",
        "operationId": "GetPlanRun",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanRunStatusResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/cost-estimate": {
      "get": {
        "tags": [
          "Plans"
        ],
        "summary": "Estimate what planning the week would cost, with a day-by-day preflight",
        "description": "Reads only — nothing is generated or charged (#340). Answers the credits, what is left of the quota, whether the run would be admitted, and a preflight of what each day becomes if the run is pressed as it stands (#694, ADR-066). scope defaults to WholeWeek; allowGeneration overrides the household's cap on new dishes for this estimate only. lockedDates and excludedTitles are the sheet's «Låst» and «Ikke denne» (ADR-356 (#3244)), read exactly as the run's policy reads them; lockedDates absent means the plan's own locks. heldDishes («yyyy-MM-dd:Title») are the cookbook dishes the sheet already showed on days nobody touched, held there so re-pricing does not move them (ADR-366 (#3288)).",
        "operationId": "EstimatePlanCost",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "weekStart",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ReplanScope"
            }
          },
          {
            "name": "allowGeneration",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          },
          {
            "name": "lockedDates",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "date"
              }
            }
          },
          {
            "name": "excludedTitles",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "heldDishes",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanCostEstimateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/overview": {
      "get": {
        "tags": [
          "Plans"
        ],
        "summary": "List which days carry a dinner, week by week, for a window of at most 92 days",
        "description": "Month-map data: each week's status, each day's entry type and dish name, and whether the week has a shopping list and when the trip was finished. from and to are both required and from must not be after to (#445), and the window may cover at most 92 days — a calendar read, not a page (#2977, ADR-292). The widest any client asks for is a month grid's six weeks.",
        "operationId": "GetPlanOverview",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanOverviewResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/by-week/{weekStart}": {
      "get": {
        "tags": [
          "Plans"
        ],
        "summary": "Get the week's live plan",
        "description": "weekStart is the week's Monday. Superseded versions are never returned; 404 when the week has no plan.",
        "operationId": "GetWeeklyPlanByWeek",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "weekStart",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/by-week/{weekStart}/use-up": {
      "get": {
        "tags": [
          "Plans"
        ],
        "summary": "Read the use-up bundle for a week: what is running out after the planned use, and the fewest swaps that would use it.",
        "description": "weekStart is normalised to its Monday. Anchored at that Monday or today, whichever is later, while every deadline is measured from today (ADR-175): a week whose days are all past the food's life gets an empty bundle, and so does a week that is over. The bundle carries an id; apply or dismiss it through the home use-up endpoints. Read-only. A weekStart far outside the calendar a household lives in answers 400.",
        "operationId": "GetWeeklyUseUpBundle",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "weekStart",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HomeUseUpBundleQuestion"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/activate": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Make this plan the week's live plan",
        "description": "Adults only; a Junior member gets 403. Any other live plan for the same week is superseded first (#437). Safe to retry: activating the plan that is already active changes nothing and answers 200.",
        "operationId": "ActivateWeeklyPlan",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/restore": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Undo a replan: bring back the plan this one replaced",
        "description": "Adults only; a Junior member gets 403. The given plan is superseded and the week's previous plan is live again; the shopping list follows it back, but the credits the run spent are not refunded (#342). 409 with code plan_not_current when the week has been planned again since and this plan is no longer the week's — resync the page. 422 with code no_earlier_plan when this is the week's first plan.",
        "operationId": "RestorePreviousWeeklyPlan",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "no_earlier_plan",
          "plan_not_current"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/entries/{entryId}": {
      "patch": {
        "tags": [
          "Plans"
        ],
        "summary": "Replace one day's dinner with another candidate, or make it a leftovers day",
        "description": "Adults only; a Junior member gets 403. Send exactly one of mealCandidateId (a candidate generated for the same date) or entryType \"Leftovers\" (#1691, ADR-136). Regenerates the week's shopping list if one exists. A superseded plan answers 400 — refresh to the current week.",
        "operationId": "ReplacePlanEntry",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReplacePlanEntryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/entries/{entryId}/feedback": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Say what was wrong with a planned dinner and let Bryti act on it",
        "description": "Adults only; a Junior member gets 403. «Si fra om denne retten» (#740, ADR-075): depending on the reason Bryti swaps the day, replans the open days, offers a variant, excludes the dish, or only notes it when the day has passed or its outcome is recorded. Two of the reasons can end in a generation, so the call may cost credits and runs under the planner's time budget (502/504 as for planning the week). The answer carries the week and a typed result the client uses for «Angre».",
        "operationId": "SubmitPlanEntryFeedback",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanEntryFeedbackRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanEntryFeedbackResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "Bad Gateway",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "504": {
            "description": "Gateway Timeout",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/entries/{entryId}/move": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Move a dinner to another day in the same week, trading places if that day has one",
        "description": "Adults only; a Junior member gets 403. toDate must be a day of the same plan, and the day being moved must have a dinner (#111). Regenerates the week's shopping list if one exists.",
        "operationId": "MovePlanEntry",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MovePlanEntryRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}": {
      "delete": {
        "tags": [
          "Plans"
        ],
        "summary": "Delete the week's plan, putting the week back to «not planned»",
        "description": "Adults only; a Junior member gets 403. Deleting a live plan also removes the versions it superseded and the week's shopping list; deleting a superseded plan by id removes only that one (#882). The week's candidate pool survives.",
        "operationId": "DeleteWeeklyPlan",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/entries/{entryId}/dinner": {
      "delete": {
        "tags": [
          "Plans"
        ],
        "summary": "Take the dinner off a day and leave the day open",
        "description": "Adults only; a Junior member gets 403. Not eating out and not a swap — the day simply has nothing planned (#111). Regenerates the week's shopping list if one exists.",
        "operationId": "ClearPlanEntryDinner",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/plan-recipe": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Put a recipe from the library on a day",
        "description": "Adults only; a Junior member gets 403. No model is asked and nothing is charged: the recipe becomes the day's dinner, scaled to that day's table (#115). A week with no plan yet is opened by the first dish (#1681). A base recipe is a part, not a dinner, and is refused with 400 (ADR-154).",
        "operationId": "PlanRecipeOnDay",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlanRecipeOnDayRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/entries/{entryId}/lock": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Lock a day's dinner so a replan leaves it alone",
        "description": "Adults only; a Junior member gets 403.",
        "operationId": "LockPlanEntry",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/entries/{entryId}/unlock": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Unlock a day's dinner so a replan may change it again",
        "description": "Adults only; a Junior member gets 403.",
        "operationId": "UnlockPlanEntry",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/entries/{entryId}/tips/{index}/used": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Mark one of tonight's serving tips as used, or take the mark off",
        "description": "Any member, Juniors included — it says what happened in the kitchen and changes no plan (#696, ADR-076). index is the tip's position in the entry's stored array (ServingNoteResponse.TipIndex), not its place in the servingNotes list. Idempotent. Nothing is saved to the cookbook; the marks are what the finished screen offers to turn into a variant.",
        "operationId": "SetServingTipUsed",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "index",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetServingTipUsedRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/entries/{entryId}/freezer": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Say how many baby portions tonight's cooking put in the freezer",
        "description": "Any member, Juniors included — it says what happened in the kitchen (#3092). Portions is the total for this dinner, not an increment: tapping again sends two and «Angre» sends zero, so the call is idempotent. 400 when the dinner is not showing the freezer advice, or when portions is outside 0..20. Writes one pantry row on the food key baby-portion-frozen, in the freezer.",
        "operationId": "SetFrozenBabyPortions",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetFrozenBabyPortionsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeeklyPlanResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/home/today": {
      "get": {
        "tags": [
          "Home"
        ],
        "summary": "Read the day as Home shows it: dinner, timing, calendar, shopping, questions",
        "description": "date defaults to the household's local today. Any member, a Junior included; the questions say which of them need an adult to answer. Not a pure read: opening the day makes sure, idempotently, that the household has one conversation for this date with Bryti's morning message at the top, and the response names both (#1888, ADR-162). A date far outside the calendar a household lives in answers 400. Not free either: the day may ask the model for a dish that uses up what is about to go off, so the read is rate-limited per household — 120 a minute by default, and over the ceiling answers 429 with a Retry-After header.",
        "operationId": "GetHomeToday",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HomeTodayResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-rate-limit": "home-read-per-household"
      }
    },
    "/api/v1/households/{householdId}/home/today/questions/{questionId}/dismiss": {
      "post": {
        "tags": [
          "Home"
        ],
        "summary": "Say no to one of the day's questions",
        "description": "Remembered for the household on that date only — tomorrow the card may ask again. Any member, a Junior included: dismissing a question does not change the plan. The one exception is «use-up-bundle», which is adults only (ADR-203): a «nei takk» there silences the household's use-up bundle on every surface until tomorrow, and a Junior gets 403 junior_forbidden. Safe to repeat: a second «nei» rewrites the same row. A questionId the day does not ask answers 404, and a date more than a fortnight from today answers 400: «nei» is about a day the household is living. Checking that the day asks costs the same work as reading it, so the route shares home/today's per-household ceiling and answers 429 over it (#2122).",
        "operationId": "DismissHomeQuestion",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "questionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DismissHomeQuestionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-rate-limit": "home-read-per-household"
      }
    },
    "/api/v1/households/{householdId}/home/use-up/{bundleId}/apply": {
      "post": {
        "tags": [
          "Home"
        ],
        "summary": "Apply a use-up bundle: make the swaps it proposes and adjust the shopping list, then return today's home view.",
        "description": "Adults only. The bundle id comes from the week or day use-up read; a bundle the plan has since outgrown answers 409 (ADR-173). The shopping list is adjusted, never regenerated. Applying rebuilds the bundle and answers with the whole day, so the route shares home/today's per-household ceiling and answers 429 over it (#2122).",
        "operationId": "ApplyUseUpBundle",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "bundleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApplyUseUpBundleRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HomeTodayResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "x-problem-codes": [
          "use_up_bundle_stale"
        ],
        "x-rate-limit": "home-read-per-household"
      }
    },
    "/api/v1/households/{householdId}/home/use-up/{bundleId}/dismiss": {
      "post": {
        "tags": [
          "Home"
        ],
        "summary": "Dismiss a use-up bundle for this household so the morning message stops asking, and return today's home view.",
        "description": "Adults only (ADR-203): dismissing this card silences the household's use-up bundle on every surface until tomorrow. A bundle the plan or the pantry has since outgrown answers 409, and the client re-reads (ADR-173). Shares home/today's per-household ceiling and answers 429 over it (#2122).",
        "operationId": "DismissUseUpBundle",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "bundleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HomeTodayResponse"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-problem-codes": [
          "use_up_bundle_stale"
        ],
        "x-rate-limit": "home-read-per-household"
      }
    },
    "/api/v1/households/{householdId}/plan-entries/{entryId}/dinner-time": {
      "patch": {
        "tags": [
          "Home"
        ],
        "summary": "Move tonight's dinner time, tonight only or for named weekdays",
        "description": "Adults only; a Junior member gets 403. Addressed by plan entry, no plan id needed. With weekdays given, a DinnerTimeOnWeekday household rule is filed as well as moving tonight. Bryti writes one line into the day's thread, and the answer shows on Home as an undoable «Notert» note.",
        "operationId": "SetPlanEntryDinnerTime",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetDinnerTimeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanEntryResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plan-entries/{entryId}/dinner-time/undo": {
      "post": {
        "tags": [
          "Home"
        ],
        "summary": "Put tonight's dinner time back to the usual",
        "description": "Adults only; a Junior member gets 403. A weekday rule filed by the same answer is unfiled, or put back to the time it had.",
        "operationId": "UndoPlanEntryDinnerTime",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanEntryResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plan-entries/{entryId}/adaptations": {
      "post": {
        "tags": [
          "Home"
        ],
        "summary": "Put a pantry item into tonight's dish in place of an ingredient",
        "description": "Adults only; a Junior member gets 403. «Legg melka i suppa»: deterministic, no model turn, and never a new variant in the recipe library. The shopping list is adjusted, never regenerated; the response lists the items that stopped being needed. 400 when insteadOfFoodKey is not in the dish.",
        "operationId": "AddPlanEntryAdaptation",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePlanEntryAdaptationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanEntryAdaptationResult"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/adaptations/{adaptationId}": {
      "delete": {
        "tags": [
          "Home"
        ],
        "summary": "Undo an adaptation: the dish goes back to the recipe as written",
        "description": "Adults only; a Junior member gets 403. The shopping list lines the adaptation took off come back.",
        "operationId": "RemovePlanEntryAdaptation",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "adaptationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanEntryResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Read the pantry a page at a time: one row per food, as the household's evidence stands",
        "description": "Each row is a fold over the household's observations — quantity, confidence, «last seen», use-by window and the keys merged into it (#466). Names come back in the household's language. ?q= matches the row's display name and its food key, case-insensitively; ?place= is one StorageLocation; ?group= is one of the pantry's four sections — UseSoon, LikelyAvailable, Uncertain or Aged — and leaving it out gives the three the list shows, the archive excluded (#390). ?limit= is 1–100, 50 by default. The order is the pantry's own: the section first, so the reader's headings stay whole across pages, then the use priority, the name and the food key. `total` counts every row the filter matches, and `nextCursor` — opaque — is handed back as ?cursor= for the next page; null on the last. A cursor nobody handed out is a 400. The numbers over the list — the place tabs, the section headings, the triage deck — come from GET …/inventory/summary (#3000, ADR-292).",
        "operationId": "GetInventory",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "place",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/StorageLocation"
            }
          },
          {
            "name": "group",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/InventoryGroup"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedInventoryItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/summary": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Count the pantry without reading it: rows per place, per section, and the triage deck",
        "description": "Any member. `byGroup` and `total` are counted under the same ?q= and ?place= the rows were asked for, because a heading's number has to match the list under it; `byPlace` and `triage` are about the whole pantry on purpose — the tab strip has to show every place the pantry has, and the triage deck is the whole kitchen or the freezer's «vet ikke» rows are never asked about (#455). `byPlace` leaves the archived rows out, the way the tabs have always counted (#390); `total` counts them, so it is the sum of the four in `byGroup`.",
        "operationId": "GetInventorySummary",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "place",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/StorageLocation"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InventorySummaryResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/observations": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Record a pantry observation for one food (bought, seen, used up, gone…)",
        "description": "Any member. Answers the observation's own id — the handle for undoing the tick (#457) — and the food's row as it now stands; a «gone» tick answers an empty row. Free-form food keys are normalized server-side.",
        "operationId": "AddInventoryObservation",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddInventoryObservationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddObservationResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Empty the pantry evidence — all of it, or one storage location's",
        "description": "Adults only; a Junior member gets 403 (#981, ADR-083). Pass ?location=Fridge|Freezer|Pantry|Unknown to wipe one shelf; without it every observation goes. Answers how many observations were removed.",
        "operationId": "ClearInventoryObservations",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "location",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/StorageLocation"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClearInventoryObservationsResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/observations/{observationId}": {
      "delete": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Undo one pantry observation",
        "description": "Any member. The observation leaves the history, so the row is exactly what it was before it — «last seen» included (#457). 404 on a stale id is not an error: another device may have retracted it already.",
        "operationId": "RetractInventoryObservation",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "observationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/merge-suggestions": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Suggest pairs of pantry rows that look like the same food",
        "description": "Any member. Computed on every read, never stored: at most three pairs, leaving out the pairs the household has already said no to (#466). The pair of keys is the suggestion's identity — there is no id.",
        "operationId": "GetPantryMergeSuggestions",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PantryMergeSuggestionResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/merges": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Merge one pantry food key into another («yes, same food»)",
        "description": "Adults only; a Junior member gets 403 (#981, ADR-083). Safe to retry: the same pair twice is the same answer, and two devices racing land on one alias row. A yes overrides an earlier «different foods» answer. 400 when both keys are the same food; 409 when the merge would point the two rows at each other (A→B already stands and B→A is asked).",
        "operationId": "MergePantryKeys",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MergePantryKeysRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PantryMergeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "List a page of the household's pantry merges",
        "description": "Any member. Cursor-paged (ADR-292): ordered by the surviving row's key, then newest answer within it, so the page's cards stay whole across a page boundary. The «nei, ulike» answers are their own read — GetPantryMergeRejections. 400 on a limit out of range or a foreign cursor.",
        "operationId": "GetPantryMerges",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedPantryMergeListItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/merge-rejections": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Say two pantry rows are different foods, so the pair is not suggested again",
        "description": "Adults only; a Junior member gets 403. The pair is unordered, and the answer is idempotent — a second no is the same no.",
        "operationId": "RejectPantryMerge",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectPantryMergeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "List a page of the household's «different foods» answers",
        "description": "Any member. Cursor-paged (ADR-292), newest answer first. A no keeps a pair out of the suggestions until «Spør igjen» lifts it (#550). 400 on a limit out of range or a foreign cursor.",
        "operationId": "GetPantryMergeRejections",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedPantryMergeRejectionListItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/merges/{fromKey}": {
      "delete": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Split a merged pantry food key back out into its own row",
        "description": "Adults only; a Junior member gets 403. Also remembers a no for the pair, so it is not the very next suggestion (lift it with DELETE merge-rejections/{keyA}/{keyB}, #550). 404 when there is no merge on that key — another device may already have split it.",
        "operationId": "UnmergePantryKeys",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "fromKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/merge-rejections/{keyA}/{keyB}": {
      "delete": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Lift a «different foods» answer so the pair can be suggested again",
        "description": "Adults only; a Junior member gets 403. The pair is unordered — the server sorts the two keys (#550). 404 when no answer is stored for the pair, which is not an error: the pair is already in the asked-for state.",
        "operationId": "LiftPantryMergeRejection",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "keyA",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "keyB",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/unit-weights": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "List what one unit of a food weighs in this household's kitchen",
        "description": "Any member. «One cheese is 400 g here» (#551): the household's own answers per food and unit, in grams, which the pantry projection and the shopping list convert with. Bounded by the foods the household has corrected, so it does not page (ADR-292) — but foodKey narrows it to one food, which is how the item sheet reads it (#3003).",
        "operationId": "GetHouseholdUnitWeights",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "foodKey",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/UnitWeightResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/inventory/unit-weights/{foodKey}/{unit}": {
      "put": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Set what one unit of a food weighs in this kitchen, in grams",
        "description": "Adults only; a Junior member gets 403 (#981, ADR-083). One answer per food and unit: a second call restates the first, so it is safe to retry. Deliberately rebuilds no shopping list — ask for a regeneration when ready (#551). 400 when the grams are out of bounds.",
        "operationId": "SetHouseholdUnitWeight",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "foodKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "unit",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetUnitWeightRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnitWeightResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Forget the household's unit weight for a food",
        "description": "Adults only; a Junior member gets 403. The conversion refuses again afterwards — two honest lines rather than one invented one. 404 when there is no answer on that food and unit; another device may already have forgotten it.",
        "operationId": "ForgetHouseholdUnitWeight",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "foodKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "unit",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/shopping-list/generate": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Build, or rebuild, the shopping list for a planned week",
        "description": "Any member. Since ADR-352 the list is derived on every read that finds the week or the pantry changed, so this call is no longer needed to have a list: it derives the list again now, prices included, which is what «Oppdater prisene» uses, and older app builds still call it. Ticks and hand-added lines are kept. Prices are a garnish: when the price provider fails the list ships without suggestions.",
        "operationId": "GenerateShoppingList",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/shopping-list": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Read the shopping list for a planned week",
        "description": "Any member. The list is derived from the week's dinners and the pantry and follows every change to either (ADR-352), so it exists as soon as the plan does; 404 only when the plan is not this household's. The response carries a weak ETag over the list itself and `Cache-Control: private, no-cache`; an `If-None-Match` that still holds is answered 304 with no body, which is what makes the apps' fifteen-second poll free in a shop with bad coverage (#2984).",
        "operationId": "GetShoppingList",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListResponse"
                }
              }
            }
          },
          "304": {
            "description": "Not Modified",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/shopping-list/better-choices": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Suggest less processed products for the heavily processed lines on a week's list",
        "description": "Any member. For each line still to buy whose suggested product is NOVA 4 in Open Food Facts (or, without one, whose food mostly is), at most three NOVA 1-3 products: the household's own earlier buys first, then what its chains' catalogues list, then the same food before another in its Matvaretabellen group, then the lower step and price. Deterministic from data, no model call; lines without a choice are left out (#3248).",
        "operationId": "GetShoppingBetterChoices",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BetterChoicesResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/shopping/purchase-classification": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Count what the household's own receipts bought, by how processed the products are",
        "description": "Any member. Distinct receipt lines with a price in the last `days` (default 30, at most 365), each read as an Open Food Facts product when its words and food match one; counts per NOVA group, raw. The share is of the recognised lines only, and the apps say so in the same sentence (#2525, ADR-213). Own receipts only, never aggregated.",
        "operationId": "GetPurchaseClassification",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseClassificationResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/shopping-list/complete": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Finish the shopping trip and book what was bought into the pantry",
        "description": "Any member. Idempotent: only the first call writes the ticked lines into the pantry as purchases and answers a TripSummary (#448). ?markAllBought=true ticks every remaining line as part of the closing, so a reopen can take exactly those ticks back (#1121). The shopper gets one receipt-reminder push (#37).",
        "operationId": "CompleteShoppingList",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "markAllBought",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/shopping-list/reopen": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Reopen a finished shopping trip",
        "description": "Any member. Idempotent. Takes back the pantry evidence the closing wrote, and the ticks the closing itself made (#1121); the household's own ticks stand.",
        "operationId": "ReopenShoppingList",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/shopping-list/items": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Add a hand-typed line to the shopping list («we're out of milk»)",
        "description": "Any member. A name like «1 l melk» is read for amount and unit when the request gives none (#448); a food already on the list becomes one line (#1833). Hand-added lines survive a regenerated list, and are the only ones that can be removed by hand. The household gets one push (#198). 400 on an empty name, and on a quantity that is not greater than 0 and at most 100000.",
        "operationId": "AddShoppingItem",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddShoppingItemRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/shopping-list/recipe": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Add a recipe's ingredients to the plan's shopping list",
        "description": "Any member. «I'm making this at some point» — the lines are the household's own, tied to no day (#115). Optional ingredients are left out; a base recipe the dish points at contributes its lines, and a reference nobody has linked is reported in SkippedReferences rather than bought (ADR-154). 404 when the recipe is not the household's, or the plan has no list yet; 400 on portions outside 1–100.",
        "operationId": "AddRecipeToShoppingList",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddRecipeToShoppingListRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/shopping-list/recipe": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Add a recipe's ingredients to a week's shopping list, opening the week if needed",
        "description": "Any member. Addressed by week rather than plan id: WeekStart is any day in the wanted week, default the household's current one. A week nobody has planned gets an empty plan and a fresh list on the way through (#1681); a list already in progress is never regenerated. Otherwise as the per-plan route. 404 when the recipe is not found; 400 on portions outside 1–100.",
        "operationId": "AddRecipeToWeekShoppingList",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddRecipeToWeekShoppingListRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/shopping-items/{itemId}": {
      "delete": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Remove a hand-added line from the shopping list",
        "description": "Any member. Only lines the household typed in can go: a line the week's dinners need answers 404 rather than pretending — the next regeneration would put it back anyway.",
        "operationId": "RemoveShoppingItem",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "patch": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Tick, untick or change the status of one shopping-list line",
        "description": "Any member. Status Purchased with no PurchasedQuantity means everything was bought; a smaller amount keeps the remainder on the list. PurchasedUnit, when the amount is in another unit than the line's, is converted where the household's piece weight knows how and otherwise kept as said, the line left open (#3266). The first tick on a line queues one «ticked N items» push (#198).",
        "operationId": "UpdateShoppingItem",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateShoppingItemRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListItemResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/shopping-list/shopping-items/{itemId}/answer": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Answer «Har det» / «Tomt» on a «Sjekk hjemme» line",
        "description": "Adults only; a Junior member gets 403 (#981, ADR-083). Writes a pantry observation (ADR-177/230) — ConfirmedPresent for HaveIt, ConfirmedGone for Out, no amount — and adjusts the list in place (ADR-173 §6), so ids and ticks survive. «Har det» takes the line off the list (it is not a purchase, so never Checked); «Tomt» moves it to «Kjøp» under the same id. Answers the whole list. 400 when the line is not a «Sjekk hjemme» line; 404 when the plan, its list or the line is not this household's.",
        "operationId": "AnswerCheckAtHome",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnswerCheckAtHomeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/shopping-items/{itemId}/product": {
      "put": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Price a shopping-list line from another product («use this one»)",
        "description": "Any member — a swap is shopping, not setup (#1884, ADR-161). The product is one of the row's alternatives, or a product-search hit sent with its price; the previous product becomes an alternative, so the swap is reversible. Nothing is remembered — that is PUT product-preferences/{foodKey}. 400 when the chain is not one of the household's stores, or the product is new to the row and no price was given.",
        "operationId": "ChooseShoppingProduct",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChooseShoppingProductRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ShoppingListItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/shopping-items/{itemId}/product-search": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Search the household's stores' catalogue for products matching ?q=",
        "description": "Any member. ?q= needs between two and 64 characters (400 otherwise). Hits are grouped per store, cheapest first, at most 12 per store; SearchAvailable is false when no product catalogue is configured. Each call asks the catalogue, so it is rate-limited per household — 30 a minute by default (ADR-127, ADR-161): over the ceiling answers 429 with a Retry-After header.",
        "operationId": "SearchShoppingProducts",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "itemId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductSearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-problem-codes": [
          "product_search_length"
        ],
        "x-rate-limit": "product-search-per-household"
      }
    },
    "/api/v1/households/{householdId}/product-preferences": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "List the products the household has asked for by food",
        "description": "Any member. Newest choice first (#1884, ADR-161).",
        "operationId": "GetProductPreferences",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProductPreferenceResponse"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/product-preferences/{foodKey}": {
      "put": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Remember a product for a food («remember this choice»)",
        "description": "Adults only; a Junior member gets 403 (ADR-083). One choice per food: a second call restates it, so it is safe to retry. Matching rows on the open lists get the household-choice badge at once; the price follows at the next list build (#1884, ADR-161). 400 on a blank chain or product name, and on a food key that is not a word.",
        "operationId": "SetProductPreference",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "foodKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetProductPreferenceRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductPreferenceResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Forget the remembered product for a food",
        "description": "Adults only; a Junior member gets 403. Open lists lose the badge now; the line goes back to the cheapest match at its next build. 404 when nothing was remembered — another device may already have forgotten it.",
        "operationId": "ForgetProductPreference",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "foodKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/off/products/{barcode}": {
      "get": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "What a scanned barcode is: name, brand, pack size and Bryti's food key",
        "description": "Any member, Junior included — scanning is the pantry's daily upkeep (ADR-055). EAN-8, EAN-13 or UPC-A (answered as EAN-13); anything else is 400. «found: false» is a 200, not a 404: a Norwegian shelf has products Open Food Facts does not know, and that is the scanner's empty state (#2587). foodKey is null when the product is known but nobody has said what food it is.",
        "operationId": "LookupOffProduct",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "barcode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OffProductLookupResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/off/products/{barcode}/food-key": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Name the food a scanned product is, for everyone who scans it next",
        "description": "Any member. The name is normalised to a canonical food key and written as a manual barcode → food-key row — additive (ADR-200): an existing row is never moved, and the same answer twice is a no-op («written: false»). No household id is stored; the row is part of the ODbL derivative Bryti publishes (#2587).",
        "operationId": "SetOffProductFoodKey",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "barcode",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetOffFoodKeyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetOffFoodKeyResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/off/contributions": {
      "post": {
        "tags": [
          "Inventory & shopping"
        ],
        "summary": "Send a product Open Food Facts does not know to Open Food Facts",
        "description": "Adults only; a Junior gets 403 junior_forbidden (ADR-055). The barcode, name, brand and pack size leave Bryti and become public on Open Food Facts under Bryti's contributor account — the flow says so before the button (ADR-215). No picture (ADR-214). 202 with the product's page; 503 off_contribution_unavailable where no contributor account is configured (the client shows the product page instead); 502 when Open Food Facts refuses.",
        "operationId": "ContributeOffProduct",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OffContributionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OffContributionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "502": {
            "description": "Bad Gateway",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-problem-codes": [
          "off_contribution_failed",
          "off_contribution_unavailable"
        ],
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/imports/purchase-document": {
      "post": {
        "tags": [
          "Imports"
        ],
        "summary": "Upload a receipt, order or shelf photo and have Bryti read it into lines",
        "description": "Adults only; a Junior member gets 403 (#981, ADR-083). JSON body: pasted text, or a base64 PDF or image — 10 MB in all, a photo in at most four pages (#385). Purpose=StorageSnapshot for a photo of a fridge, freezer or pantry. Read before the call returns, and costs credits; a provider failure answers 200 with Status=Failed and the import stays retryable. Nothing reaches the pantry until confirm.\n\nSend an Idempotency-Key header: a retry with the same key answers 409 with the original operation's id instead of charging twice. 429 when the household's monthly AI quota is used up, or AI is paused for the month (ADR-023) — the latter with a Retry-After header.",
        "operationId": "UploadPurchaseDocument",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "maxLength": 80,
              "pattern": "^[\\x21-\\x7E]+$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadPurchaseDocumentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseImportResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-rate-limit": "ai-per-household"
      }
    },
    "/api/v1/households/{householdId}/imports/{importId}": {
      "get": {
        "tags": [
          "Imports"
        ],
        "summary": "Read a purchase import back: status, parsed lines and document facts",
        "description": "Adults only — the whole import flow is, its status included (ADR-083). Status is Pending, Parsed, Confirmed or Failed; each line says whether the review should ask for «best before» (#737, ADR-074).",
        "operationId": "GetPurchaseImport",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "importId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseImportResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/imports/recipe-photo": {
      "post": {
        "tags": [
          "Imports"
        ],
        "summary": "Import a recipe from one or more photos into the household's library",
        "description": "Adults only; a Junior member gets 403 (ADR-083). JSON body with base64 images — at most four pages of the same recipe, 10 MB in all (#174). Costs credits. One shot, no review: Status is Saved, AlreadyExists (the existing recipe rides back), Partial (what was read, for the editor to finish — nothing saved, ADR-152) or Failed with a Failure kind. ConversationId has the result told in that chat thread (ADR-141); HostRecipeId + ReferenceId save it as a base recipe linked into that host (ADR-154 pt. 7).\n\nSend an Idempotency-Key header: a retry with the same key answers 409 instead of charging twice. 429 when the AI quota is used up, or AI is paused for the month (ADR-023) — the latter with a Retry-After header.",
        "operationId": "ImportRecipePhoto",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "maxLength": 80,
              "pattern": "^[\\x21-\\x7E]+$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRecipePhotoRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeImportResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-rate-limit": "ai-per-household"
      }
    },
    "/api/v1/households/{householdId}/imports/recipe-url": {
      "post": {
        "tags": [
          "Imports"
        ],
        "summary": "Import a recipe from a web page into the household's library",
        "description": "Adults only; a Junior member gets 403 (ADR-083). The server fetches the page itself — public addresses only; an internal or malformed one is a 400 — and asks Bryti to read it, which costs credits. A page that cannot be fetched, sits behind a login wall or paywall, times out or is a file answers 200 with Status=Failed and a Failure kind, not an error (#1712). Otherwise as recipe-photo: Saved, AlreadyExists or Partial, with ConversationId and HostRecipeId + ReferenceId.\n\nSend an Idempotency-Key header: a retry with the same key answers 409 instead of charging twice. 429 when the AI quota is used up, or AI is paused for the month (ADR-023) — the latter with a Retry-After header.",
        "operationId": "ImportRecipeUrl",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "maxLength": 80,
              "pattern": "^[\\x21-\\x7E]+$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRecipeUrlRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeImportResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-rate-limit": "ai-per-household"
      }
    },
    "/api/v1/households/{householdId}/imports/recipe-text": {
      "post": {
        "tags": [
          "Imports"
        ],
        "summary": "Import a recipe from pasted text into the household's library",
        "description": "Adults only; a Junior member gets 403 (ADR-083). Up to 200 kB of text (#982); costs credits. Same outcomes and options as recipe-photo: Saved, AlreadyExists, Partial or Failed, with ConversationId and HostRecipeId + ReferenceId. A page the phone read with the member's own sign-in (#3818) may name its SourceUrl and ImageUrl: the source is kept on the recipe and the picture fetched as the recipe-url door fetches one; either must be an http(s) address or the call is a 400. An Idempotency-Key header is honoured here too — a retry with the same key answers 409. 429 when the AI quota is used up, or AI is paused for the month (ADR-023) — the latter with a Retry-After header.",
        "operationId": "ImportRecipeText",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "maxLength": 80,
              "pattern": "^[\\x21-\\x7E]+$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRecipeTextRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeImportResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              },
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          }
        },
        "x-rate-limit": "ai-per-household"
      }
    },
    "/api/v1/households/{householdId}/imports/recipe/{importId}": {
      "get": {
        "tags": [
          "Imports"
        ],
        "summary": "Read a recipe import back: how the reading went, and the recipe",
        "description": "Adults only — the whole import flow is, its status included (ADR-083). The three recipe-import routes normally answer with the finished reading, but since ADR-189 (#2283) the row is written and committed before the model is asked, so they may also answer Pending: the import exists, it is paid for, and it will finish even if the API restarts first. This is where a client waits for that — poll it until Status is Saved, AlreadyExists, Partial or Failed.\n\nTwo fields are only ever on the answer the reading itself produced: a Partial import's half recipe and the «er det denne?» link suggestions live in the API instance that read it, not on the row, so an import read back after a restart has its status and its recipe but not those (#307, US-1813).",
        "operationId": "GetRecipeImport",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "importId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipeImportResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/imports/recipe/page": {
      "get": {
        "tags": [
          "Imports"
        ],
        "summary": "Read the page question still open about a recipe, if there is one",
        "description": "Adults only, like the rest of the import flow (ADR-083). A photographed page with more than one recipe on it is asked about once the import has saved one of them (#3308); this is where the recipe page finds that question again after a reload or a closed app (#3322): the import to answer and the reading, by the saved recipe's id. 204 when nothing is left to ask — never asked, already answered, or not this household's.",
        "operationId": "GetRecipeImportPage",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "recipeId",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipePageQuestionResponse"
                }
              }
            }
          },
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/imports/recipe/{importId}/page": {
      "post": {
        "tags": [
          "Imports"
        ],
        "summary": "Answer what the recipes on one photographed page are to each other",
        "description": "Adults only, like the rest of the import flow (ADR-083). A recipe import whose answer carried Page read more than one recipe on the page and saved one reading of it (#3308). OneDish keeps them as one recipe with parts (ADR-154); Only with Index keeps that dish alone; All saves every dish as a recipe of its own, each told in the chat thread the import came from (ADR-141). The saved recipe is rewritten in place, never deleted. No credits: nothing is read again. 400 once the page has been answered, or for an index that is not a dish on it.",
        "operationId": "ChooseRecipeImportPage",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "importId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChooseRecipePageRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipePageChoiceResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/imports/{importId}/confirm": {
      "post": {
        "tags": [
          "Imports"
        ],
        "summary": "Confirm the reviewed lines and write them into the pantry",
        "description": "Adults only; a Junior member gets 403 (ADR-083). The only path from an import to the pantry: each kept line becomes a purchase observation — a sighting for a storage snapshot — dated by the receipt's own purchase time rather than today (#385), and each priced receipt line a price observation. StoreName, ChainCode and PurchasedAt override what was read. No credits. Not repeatable: 400 with no lines, and 400 on an import that is not in Parsed state, an already confirmed one included. With a ConversationId — the import was started from the chat's «+» — the same save writes one assistant turn in that thread saying how many things went in, with a pantry_import widget; an id that is not this household's is ignored, never a 403 (ADR-375 (#3305), ADR-141).",
        "operationId": "ConfirmPurchaseImport",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "importId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfirmImportRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PurchaseImportResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plan-entries/{entryId}/outcome": {
      "post": {
        "tags": [
          "Outcomes"
        ],
        "summary": "Record how a day's dinner went: cooked, changed, not cooked or ate elsewhere",
        "description": "A cooked dinner consumes its ingredients from the pantry as estimates, leftovers become pantry items, and the answer teaches a weak like or dislike for the dish. source \"CookMode\" records only that the dinner happened and leaves «hvordan gikk det?» to the week (#1836, ADR-158). 409 with code outcome_already_recorded when the day already has an outcome (#567) — refresh instead of retrying.",
        "operationId": "RecordMealOutcome",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecordOutcomeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MealOutcomeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "put": {
        "tags": [
          "Outcomes"
        ],
        "summary": "Answer the follow-up: change what a recorded outcome says",
        "description": "Replaces status, reason and notes; leftovers are added only if the outcome had none, and source never changes (#1836, ADR-158). 409 with code outcome_cannot_be_undone when the change would un-cook a cooked dinner — delete the outcome («Angre») instead.",
        "operationId": "UpdateMealOutcome",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecordOutcomeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MealOutcomeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      },
      "delete": {
        "tags": [
          "Outcomes"
        ],
        "summary": "Undo a recorded outcome and everything it caused",
        "description": "Removes the consumption estimates, the leftovers it put in the pantry and the preference signal it taught (#110).",
        "operationId": "DeleteMealOutcome",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plan-entries/{entryId}/outcome/confirm": {
      "post": {
        "tags": [
          "Outcomes"
        ],
        "summary": "Answer the follow-up with «as planned» and close the question",
        "description": "Stamps followedUpAt and changes nothing else (#1836, ADR-158). Finds the answer on the same evening in any version of the week, and records Cooked when none stands (#3263). Safe to retry: a second call answers 200 with the same outcome. 404 only when no plan holds the entry.",
        "operationId": "ConfirmMealOutcome",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "entryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MealOutcomeResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/outcomes": {
      "get": {
        "tags": [
          "Outcomes"
        ],
        "summary": "Read the household's recorded outcomes a page at a time, newest first",
        "description": "from and to filter on the day the outcome was recorded (UTC), not on the dinner's date; ?status= narrows to one kind of answer. ?limit= is 1–100, 50 by default. Each page hands back a nextCursor — give it back as ?cursor= for the next page; null on the last. A cursor nobody handed out is a 400. total is null: counting every dinner ever answered for would cost a second pass over rows nobody asked to see.",
        "operationId": "ListMealOutcomes",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/OutcomeStatus"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PagedMealOutcomeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    },
    "/api/v1/households/{householdId}/plans/{planId}/replan": {
      "post": {
        "tags": [
          "Plans"
        ],
        "summary": "Put the week back in the planning queue and answer with the run",
        "description": "Adults only; a Junior member gets 403. Answers 202 with the run's id (ADR-289): the week is planned again by a background job, and GET plans/runs/{jobId} says where it stands. scope defaults to OpenDays, which fills only the days with nothing on them and changes nothing already decided; WholeWeek re-solves everything that is not locked, answered for or in the past (#340). The optional body is the preflight's answer (#694, ADR-066).\n\nMay cost credits — GET plans/cost-estimate says the price first. The shopping list follows the week to the new plan, and «Angre» is POST plans/{planId}/restore. A press while a run is going joins that run; 409 (week_planning_in_progress) means the same as for planning the week.",
        "operationId": "ReplanWeek",
        "parameters": [
          {
            "name": "householdId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "planId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/ReplanScope"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/PlanPolicy"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanRunAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "headers": {
              "X-Trace-Id": {
                "$ref": "#/components/headers/X-Trace-Id"
              },
              "Server-Timing": {
                "$ref": "#/components/headers/Server-Timing"
              },
              "X-Diagnostics": {
                "$ref": "#/components/headers/X-Diagnostics"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "426": {
            "$ref": "#/components/responses/UpgradeRequired"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "x-rate-limit": "backstop"
      }
    }
  },
  "components": {
    "schemas": {
      "AcceptInviteRequest": {
        "required": [
          "code"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          }
        }
      },
      "ActionProposalResponse": {
        "required": [
          "id",
          "conversationId",
          "sourceMessageId",
          "actionType",
          "payload",
          "humanSummary",
          "status",
          "requiresConfirmation",
          "error",
          "createdAt",
          "appliedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "conversationId": {
            "type": "string",
            "format": "uuid"
          },
          "sourceMessageId": {
            "type": "string",
            "format": "uuid"
          },
          "actionType": {
            "type": "string"
          },
          "payload": {
            "$ref": "#/components/schemas/ChatAction"
          },
          "humanSummary": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/ProposalStatus"
          },
          "requiresConfirmation": {
            "type": "boolean"
          },
          "error": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "appliedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "undoable": {
            "type": "boolean",
            "default": false
          },
          "undoneAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "undoExpiresAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "jobId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "span": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanProposalSpan"
              }
            ]
          },
          "outcome": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanProposalOutcome"
              }
            ]
          },
          "askedAfterReading": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ChatForeignTextSource"
              }
            ]
          }
        }
      },
      "AddInventoryObservationRequest": {
        "required": [
          "foodKey",
          "displayName",
          "observationType",
          "quantity",
          "unit",
          "storageLocation",
          "confidence"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "displayName": {
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "observationType": {
            "$ref": "#/components/schemas/ObservationType"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "storageLocation": {
            "$ref": "#/components/schemas/StorageLocation"
          },
          "confidence": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "bestBefore": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "barcode": {
            "maxLength": 32,
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "AddObservationResponse": {
        "required": [
          "observationId",
          "item"
        ],
        "type": "object",
        "properties": {
          "observationId": {
            "type": "string",
            "format": "uuid"
          },
          "item": {
            "$ref": "#/components/schemas/InventoryItemResponse"
          }
        }
      },
      "AddRecipeToShoppingListRequest": {
        "required": [
          "recipeId"
        ],
        "type": "object",
        "properties": {
          "recipeId": {
            "type": "string",
            "format": "uuid"
          },
          "portions": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "AddRecipeToWeekShoppingListRequest": {
        "required": [
          "recipeId"
        ],
        "type": "object",
        "properties": {
          "recipeId": {
            "type": "string",
            "format": "uuid"
          },
          "portions": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "weekStart": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "AddressSuggestionResponse": {
        "required": [
          "text"
        ],
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          }
        }
      },
      "AddShoppingItemRequest": {
        "required": [
          "displayName"
        ],
        "type": "object",
        "properties": {
          "displayName": {
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "AnswerCheckAtHomeRequest": {
        "required": [
          "answer"
        ],
        "type": "object",
        "properties": {
          "answer": {
            "$ref": "#/components/schemas/CheckAtHomeAnswer"
          }
        }
      },
      "AppleNativeSignInRequest": {
        "required": [
          "identityToken"
        ],
        "type": "object",
        "properties": {
          "identityToken": {
            "type": "string"
          },
          "rawNonce": {
            "type": [
              "null",
              "string"
            ]
          },
          "givenName": {
            "type": [
              "null",
              "string"
            ]
          },
          "familyName": {
            "type": [
              "null",
              "string"
            ]
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ApplyUseUpBundleRequest": {
        "type": "object",
        "properties": {
          "changeIds": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          }
        }
      },
      "AppVersionFloorResponse": {
        "required": [
          "minimum",
          "storeUrl"
        ],
        "type": "object",
        "properties": {
          "minimum": {
            "type": [
              "null",
              "string"
            ]
          },
          "storeUrl": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "AppVersionResponse": {
        "required": [
          "ios",
          "android"
        ],
        "type": "object",
        "properties": {
          "ios": {
            "$ref": "#/components/schemas/AppVersionFloorResponse"
          },
          "android": {
            "$ref": "#/components/schemas/AppVersionFloorResponse"
          }
        }
      },
      "AuthProviderResponse": {
        "required": [
          "scheme",
          "displayName"
        ],
        "type": "object",
        "properties": {
          "scheme": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          }
        }
      },
      "BabyPortionFit": {
        "enum": [
          "NotApplicable",
          "Possible",
          "NotPossible"
        ]
      },
      "BasketEstimateResponse": {
        "required": [
          "stores",
          "optimizedTotal",
          "pricedItemCount",
          "unpricedItemCount"
        ],
        "type": "object",
        "properties": {
          "stores": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StoreBasketEstimateResponse"
            }
          },
          "optimizedTotal": {
            "type": "number",
            "format": "double"
          },
          "pricedItemCount": {
            "type": "integer",
            "format": "int32"
          },
          "unpricedItemCount": {
            "type": "integer",
            "format": "int32"
          },
          "hasEnoughPrices": {
            "type": "boolean",
            "default": false
          },
          "hasIndexEstimates": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "BetterChoiceBasis": {
        "enum": [
          "SuggestedProduct",
          "UsuallyHeavilyProcessed",
          null
        ]
      },
      "BetterChoiceResponse": {
        "required": [
          "productName",
          "brands",
          "novaGroup",
          "sameFood",
          "boughtBefore",
          "chainCode",
          "storeName",
          "price"
        ],
        "type": "object",
        "properties": {
          "productName": {
            "type": "string"
          },
          "brands": {
            "type": [
              "null",
              "string"
            ]
          },
          "novaGroup": {
            "type": "integer",
            "format": "int32"
          },
          "sameFood": {
            "type": "boolean"
          },
          "boughtBefore": {
            "type": "boolean"
          },
          "chainCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "storeName": {
            "type": [
              "null",
              "string"
            ]
          },
          "price": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "singleIngredient": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "BetterChoicesLineResponse": {
        "required": [
          "foodKey",
          "basis",
          "productName",
          "choices"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "basis": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/BetterChoiceBasis"
              }
            ]
          },
          "productName": {
            "type": [
              "null",
              "string"
            ]
          },
          "choices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BetterChoiceResponse"
            }
          },
          "novaGroup": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "novaReasons": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PurchaseNovaReasonResponse"
            }
          },
          "productSteps": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/ProductStepResponse"
            }
          }
        }
      },
      "BetterChoicesResponse": {
        "required": [
          "weeklyPlanId",
          "lines"
        ],
        "type": "object",
        "properties": {
          "weeklyPlanId": {
            "type": "string",
            "format": "uuid"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BetterChoicesLineResponse"
            }
          }
        }
      },
      "BillingPlanResponse": {
        "required": [
          "code",
          "name",
          "priceMonthMinor",
          "priceCurrency",
          "monthlyCredits",
          "purchasable",
          "current",
          "pending"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "priceMonthMinor": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "priceCurrency": {
            "type": "string"
          },
          "monthlyCredits": {
            "type": "integer",
            "format": "int32"
          },
          "purchasable": {
            "type": "boolean"
          },
          "current": {
            "type": "boolean"
          },
          "pending": {
            "type": "boolean"
          }
        }
      },
      "BillingPreviewKind": {
        "enum": [
          "NewSubscription",
          "Upgrade",
          "Downgrade",
          "Same"
        ]
      },
      "BillingPreviewRequest": {
        "required": [
          "planCode"
        ],
        "type": "object",
        "properties": {
          "planCode": {
            "type": "string"
          }
        }
      },
      "BillingPreviewResponse": {
        "required": [
          "currentPlanCode",
          "planCode",
          "kind",
          "dueNowMinor",
          "currency",
          "daysLeft",
          "periodDays",
          "creditsNow",
          "creditsUsed",
          "creditsLeftAfter",
          "nextPaymentAt",
          "nextPaymentMinor",
          "appliesAt",
          "estimated",
          "canManage"
        ],
        "type": "object",
        "properties": {
          "currentPlanCode": {
            "type": "string"
          },
          "planCode": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/BillingPreviewKind"
          },
          "dueNowMinor": {
            "type": "integer",
            "format": "int32"
          },
          "currency": {
            "type": "string"
          },
          "daysLeft": {
            "type": "integer",
            "format": "int32"
          },
          "periodDays": {
            "type": "integer",
            "format": "int32"
          },
          "creditsNow": {
            "type": "integer",
            "format": "int32"
          },
          "creditsUsed": {
            "type": "integer",
            "format": "int32"
          },
          "creditsLeftAfter": {
            "type": "integer",
            "format": "int32"
          },
          "nextPaymentAt": {
            "type": "string",
            "format": "date-time"
          },
          "nextPaymentMinor": {
            "type": "integer",
            "format": "int32"
          },
          "appliesAt": {
            "type": "string",
            "format": "date-time"
          },
          "estimated": {
            "type": "boolean"
          },
          "canManage": {
            "type": "boolean"
          }
        }
      },
      "BillingStatusResponse": {
        "required": [
          "paymentsEnabled",
          "planCode",
          "planName",
          "pendingPlanCode",
          "status",
          "canManage",
          "plans"
        ],
        "type": "object",
        "properties": {
          "paymentsEnabled": {
            "type": "boolean"
          },
          "planCode": {
            "type": "string"
          },
          "planName": {
            "type": "string"
          },
          "pendingPlanCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "type": "string"
          },
          "canManage": {
            "type": "boolean"
          },
          "plans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BillingPlanResponse"
            }
          }
        }
      },
      "BusyEveningResponse": {
        "required": [
          "date",
          "title",
          "startTime",
          "endTime"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "title": {
            "type": "string"
          },
          "startTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "endTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CalendarEventResponse": {
        "required": [
          "title",
          "date",
          "startTime",
          "endTime",
          "isAllDay",
          "location"
        ],
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "startTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "endTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "isAllDay": {
            "type": "boolean"
          },
          "location": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CalendarFeedEventsResponse": {
        "required": [
          "feedId",
          "feedName",
          "events",
          "error"
        ],
        "type": "object",
        "properties": {
          "feedId": {
            "type": "string",
            "format": "uuid"
          },
          "feedName": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CalendarEventResponse"
            }
          },
          "error": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CalendarFeedResponse": {
        "required": [
          "id",
          "name",
          "url",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "kind": {
            "type": "string",
            "default": "Ics"
          },
          "syncedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "eventCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "isThisDevice": {
            "type": "boolean",
            "default": false
          },
          "isMine": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "CandidateContent": {
        "required": [
          "title",
          "rationale",
          "ingredients",
          "steps",
          "activeMinutes",
          "totalMinutes",
          "portions",
          "tags",
          "leftoverPotential",
          "confidence",
          "assumptions",
          "safetyWarnings"
        ],
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "rationale": {
            "type": "string"
          },
          "ingredients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CandidateIngredient"
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "activeMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "totalMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "portions": {
            "type": "integer",
            "format": "int32"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "leftoverPotential": {
            "type": "string"
          },
          "confidence": {
            "type": "number",
            "format": "double"
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "safetyWarnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "nutrition": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CandidateNutrition"
              }
            ]
          },
          "timers": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CandidateStepTimer"
            }
          },
          "sections": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CandidateSection"
            }
          }
        }
      },
      "CandidateIngredient": {
        "required": [
          "foodKey",
          "displayName",
          "quantity",
          "unit",
          "isOptional",
          "missing"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit": {
            "type": "string"
          },
          "isOptional": {
            "type": "boolean"
          },
          "missing": {
            "type": "boolean"
          },
          "displayQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "displayUnit": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CandidateNutrition": {
        "required": [
          "kcalPerPortion",
          "proteinGrams",
          "fibreGrams",
          "addedSugarGrams",
          "saltGrams",
          "fruitVegPortions",
          "coverage"
        ],
        "type": "object",
        "properties": {
          "kcalPerPortion": {
            "type": "integer",
            "format": "int32"
          },
          "proteinGrams": {
            "type": "number",
            "format": "double"
          },
          "fibreGrams": {
            "type": "number",
            "format": "double"
          },
          "addedSugarGrams": {
            "type": "number",
            "format": "double"
          },
          "saltGrams": {
            "type": "number",
            "format": "double"
          },
          "fruitVegPortions": {
            "type": "number",
            "format": "double"
          },
          "coverage": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "CandidatePrice": {
        "required": [
          "class",
          "perPortionNok",
          "source",
          "coverage"
        ],
        "type": "object",
        "properties": {
          "class": {
            "$ref": "#/components/schemas/PriceClass"
          },
          "perPortionNok": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "source": {
            "$ref": "#/components/schemas/PriceSource"
          },
          "coverage": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "CandidateSection": {
        "required": [
          "name",
          "kind",
          "ingredients",
          "steps",
          "timers",
          "sourceRecipeId",
          "sourceRecipeTitle"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "kind": {
            "$ref": "#/components/schemas/RecipeSectionKind"
          },
          "ingredients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CandidateIngredient"
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "timers": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CandidateStepTimer"
            }
          },
          "sourceRecipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "sourceRecipeTitle": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CandidateStepTimer": {
        "required": [
          "step",
          "minutes",
          "name"
        ],
        "type": "object",
        "properties": {
          "step": {
            "type": "integer",
            "format": "int32"
          },
          "minutes": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "ChangeEmailRequest": {
        "required": [
          "newEmail"
        ],
        "type": "object",
        "properties": {
          "newEmail": {
            "type": "string"
          }
        }
      },
      "ChangeMembershipRoleRequest": {
        "required": [
          "role"
        ],
        "type": "object",
        "properties": {
          "role": {
            "$ref": "#/components/schemas/HouseholdRole"
          }
        }
      },
      "ChangePasswordRequest": {
        "required": [
          "currentPassword",
          "newPassword"
        ],
        "type": "object",
        "properties": {
          "currentPassword": {
            "type": [
              "null",
              "string"
            ]
          },
          "newPassword": {
            "type": "string"
          }
        }
      },
      "ChatAction": {
        "required": [
          "type"
        ],
        "type": "object",
        "anyOf": [
          {
            "$ref": "#/components/schemas/ChatActionAddHouseholdMemberAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionUpdateHouseholdMemberAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionAddDietaryConstraintAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionAddFoodPreferenceAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionUpsertCategoryFrequencyRuleAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionUpsertCategoryExcludedDaysRuleAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionUpsertCategoryOnDayRuleAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionUpsertMaximumCookingTimeRuleAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionUpsertPriceClassRuleAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionUpsertRepeatAfterDaysRuleAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionSetCategoryForDateAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionSetMaximumCookingTimeForDateAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionAddGuestsForDateAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionMarkEatingOutAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionMarkAwayAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionMarkMemberAbsentAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionAddInventoryObservationAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionMergePantryFoodsAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionSetHouseholdLanguageAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionSaveRecipeAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionActivatePlanAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionSetDinnerLockAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionReplaceDinnerAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionReplanOpenDaysAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionPlanWeekAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionRecordOutcomeAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionDeleteMealEventAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionDeleteMealRuleAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionDeleteFoodPreferenceAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionDeleteDietaryConstraintAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionDeleteRecipeAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionSetRecipeFavoriteAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionWishDishAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionBuildShoppingListAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionCheckShoppingItemAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionAddShoppingItemAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionUpdateHouseholdSettingsAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionImportRecipeFromUrlAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionSelectStoresAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionEditRecipeAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionSetDinnerTimeAction"
          },
          {
            "$ref": "#/components/schemas/ChatActionAdaptDinnerIngredientAction"
          }
        ],
        "discriminator": {
          "propertyName": "type",
          "mapping": {
            "AddHouseholdMember": "#/components/schemas/ChatActionAddHouseholdMemberAction",
            "UpdateHouseholdMember": "#/components/schemas/ChatActionUpdateHouseholdMemberAction",
            "AddDietaryConstraint": "#/components/schemas/ChatActionAddDietaryConstraintAction",
            "AddFoodPreference": "#/components/schemas/ChatActionAddFoodPreferenceAction",
            "UpsertCategoryFrequencyRule": "#/components/schemas/ChatActionUpsertCategoryFrequencyRuleAction",
            "UpsertCategoryExcludedDaysRule": "#/components/schemas/ChatActionUpsertCategoryExcludedDaysRuleAction",
            "UpsertCategoryOnDayRule": "#/components/schemas/ChatActionUpsertCategoryOnDayRuleAction",
            "UpsertMaximumCookingTimeRule": "#/components/schemas/ChatActionUpsertMaximumCookingTimeRuleAction",
            "UpsertPriceClassRule": "#/components/schemas/ChatActionUpsertPriceClassRuleAction",
            "UpsertRepeatAfterDaysRule": "#/components/schemas/ChatActionUpsertRepeatAfterDaysRuleAction",
            "SetCategoryForDate": "#/components/schemas/ChatActionSetCategoryForDateAction",
            "SetMaximumCookingTimeForDate": "#/components/schemas/ChatActionSetMaximumCookingTimeForDateAction",
            "AddGuestsForDate": "#/components/schemas/ChatActionAddGuestsForDateAction",
            "MarkEatingOut": "#/components/schemas/ChatActionMarkEatingOutAction",
            "MarkAway": "#/components/schemas/ChatActionMarkAwayAction",
            "MarkMemberAbsent": "#/components/schemas/ChatActionMarkMemberAbsentAction",
            "AddInventoryObservation": "#/components/schemas/ChatActionAddInventoryObservationAction",
            "MergePantryFoods": "#/components/schemas/ChatActionMergePantryFoodsAction",
            "SetHouseholdLanguage": "#/components/schemas/ChatActionSetHouseholdLanguageAction",
            "SaveRecipe": "#/components/schemas/ChatActionSaveRecipeAction",
            "ActivatePlan": "#/components/schemas/ChatActionActivatePlanAction",
            "SetDinnerLock": "#/components/schemas/ChatActionSetDinnerLockAction",
            "ReplaceDinner": "#/components/schemas/ChatActionReplaceDinnerAction",
            "ReplanOpenDays": "#/components/schemas/ChatActionReplanOpenDaysAction",
            "PlanWeek": "#/components/schemas/ChatActionPlanWeekAction",
            "RecordOutcome": "#/components/schemas/ChatActionRecordOutcomeAction",
            "DeleteMealEvent": "#/components/schemas/ChatActionDeleteMealEventAction",
            "DeleteMealRule": "#/components/schemas/ChatActionDeleteMealRuleAction",
            "DeleteFoodPreference": "#/components/schemas/ChatActionDeleteFoodPreferenceAction",
            "DeleteDietaryConstraint": "#/components/schemas/ChatActionDeleteDietaryConstraintAction",
            "DeleteRecipe": "#/components/schemas/ChatActionDeleteRecipeAction",
            "SetRecipeFavorite": "#/components/schemas/ChatActionSetRecipeFavoriteAction",
            "WishDish": "#/components/schemas/ChatActionWishDishAction",
            "BuildShoppingList": "#/components/schemas/ChatActionBuildShoppingListAction",
            "CheckShoppingItem": "#/components/schemas/ChatActionCheckShoppingItemAction",
            "AddShoppingItem": "#/components/schemas/ChatActionAddShoppingItemAction",
            "UpdateHouseholdSettings": "#/components/schemas/ChatActionUpdateHouseholdSettingsAction",
            "ImportRecipeFromUrl": "#/components/schemas/ChatActionImportRecipeFromUrlAction",
            "SelectStores": "#/components/schemas/ChatActionSelectStoresAction",
            "EditRecipe": "#/components/schemas/ChatActionEditRecipeAction",
            "SetDinnerTime": "#/components/schemas/ChatActionSetDinnerTimeAction",
            "AdaptDinnerIngredient": "#/components/schemas/ChatActionAdaptDinnerIngredientAction"
          }
        }
      },
      "ChatActionActivatePlanAction": {
        "required": [
          "weekStart"
        ],
        "properties": {
          "type": {
            "enum": [
              "ActivatePlan"
            ],
            "type": "string"
          },
          "weekStart": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "ChatActionAdaptDinnerIngredientAction": {
        "required": [
          "date",
          "useFood",
          "insteadOfFood"
        ],
        "properties": {
          "type": {
            "enum": [
              "AdaptDinnerIngredient"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "useFood": {
            "type": "string"
          },
          "insteadOfFood": {
            "type": "string"
          }
        }
      },
      "ChatActionAddDietaryConstraintAction": {
        "required": [
          "memberName",
          "kind",
          "foodOrCategory",
          "severity",
          "notes"
        ],
        "properties": {
          "type": {
            "enum": [
              "AddDietaryConstraint"
            ],
            "type": "string"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          },
          "kind": {
            "$ref": "#/components/schemas/DietaryConstraintKind"
          },
          "foodOrCategory": {
            "type": "string"
          },
          "severity": {
            "$ref": "#/components/schemas/ConstraintSeverity"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatActionAddFoodPreferenceAction": {
        "required": [
          "memberName",
          "subjectType",
          "subject",
          "sentiment"
        ],
        "properties": {
          "type": {
            "enum": [
              "AddFoodPreference"
            ],
            "type": "string"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          },
          "subjectType": {
            "$ref": "#/components/schemas/PreferenceSubjectType"
          },
          "subject": {
            "type": "string"
          },
          "sentiment": {
            "$ref": "#/components/schemas/PreferenceSentiment"
          }
        }
      },
      "ChatActionAddGuestsForDateAction": {
        "required": [
          "date",
          "adultCount",
          "childCount",
          "notes"
        ],
        "properties": {
          "type": {
            "enum": [
              "AddGuestsForDate"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "adultCount": {
            "type": "integer",
            "format": "int32"
          },
          "childCount": {
            "type": "integer",
            "format": "int32"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "endDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "guestNames": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "babyCount": {
            "type": "integer",
            "format": "int32",
            "default": 0
          }
        }
      },
      "ChatActionAddHouseholdMemberAction": {
        "required": [
          "name",
          "birthDate",
          "defaultDinnerParticipant"
        ],
        "properties": {
          "type": {
            "enum": [
              "AddHouseholdMember"
            ],
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "birthDate": {
            "type": "string",
            "format": "date"
          },
          "defaultDinnerParticipant": {
            "type": "boolean"
          },
          "isBreastfed": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "isGuest": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "portionFactor": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          }
        }
      },
      "ChatActionAddInventoryObservationAction": {
        "required": [
          "foodKey",
          "displayName",
          "observationType",
          "quantity",
          "unit",
          "storageLocation"
        ],
        "properties": {
          "type": {
            "enum": [
              "AddInventoryObservation"
            ],
            "type": "string"
          },
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "observationType": {
            "$ref": "#/components/schemas/ObservationType"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "storageLocation": {
            "$ref": "#/components/schemas/StorageLocation"
          }
        }
      },
      "ChatActionAddShoppingItemAction": {
        "required": [
          "weekStart",
          "displayName"
        ],
        "properties": {
          "type": {
            "enum": [
              "AddShoppingItem"
            ],
            "type": "string"
          },
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "displayName": {
            "type": "string"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatActionBuildShoppingListAction": {
        "required": [
          "weekStart"
        ],
        "properties": {
          "type": {
            "enum": [
              "BuildShoppingList"
            ],
            "type": "string"
          },
          "weekStart": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "ChatActionCheckShoppingItemAction": {
        "required": [
          "weekStart",
          "itemName",
          "purchased"
        ],
        "properties": {
          "type": {
            "enum": [
              "CheckShoppingItem"
            ],
            "type": "string"
          },
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "itemName": {
            "type": "string"
          },
          "purchased": {
            "type": "boolean"
          }
        }
      },
      "ChatActionDeleteDietaryConstraintAction": {
        "required": [
          "foodOrCategory"
        ],
        "properties": {
          "type": {
            "enum": [
              "DeleteDietaryConstraint"
            ],
            "type": "string"
          },
          "foodOrCategory": {
            "type": "string"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatActionDeleteFoodPreferenceAction": {
        "required": [
          "subject"
        ],
        "properties": {
          "type": {
            "enum": [
              "DeleteFoodPreference"
            ],
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatActionDeleteMealEventAction": {
        "required": [
          "date"
        ],
        "properties": {
          "type": {
            "enum": [
              "DeleteMealEvent"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "eventType": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/MealEventType"
              }
            ]
          }
        }
      },
      "ChatActionDeleteMealRuleAction": {
        "required": [
          "kind"
        ],
        "properties": {
          "type": {
            "enum": [
              "DeleteMealRule"
            ],
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/MealRuleKind"
          },
          "category": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/MealCategory"
              }
            ]
          },
          "date": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "ChatActionDeleteRecipeAction": {
        "required": [
          "title"
        ],
        "properties": {
          "type": {
            "enum": [
              "DeleteRecipe"
            ],
            "type": "string"
          },
          "title": {
            "type": "string"
          }
        }
      },
      "ChatActionEditRecipeAction": {
        "required": [
          "recipeTitle"
        ],
        "properties": {
          "type": {
            "enum": [
              "EditRecipe"
            ],
            "type": "string"
          },
          "recipeTitle": {
            "type": "string"
          },
          "newTitle": {
            "type": [
              "null",
              "string"
            ]
          },
          "category": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/MealCategory"
              }
            ]
          },
          "ingredients": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeIngredientDto"
            }
          },
          "steps": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "activeMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "totalMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "portions": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "ChatActionImportRecipeFromUrlAction": {
        "required": [
          "url"
        ],
        "properties": {
          "type": {
            "enum": [
              "ImportRecipeFromUrl"
            ],
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "ChatActionMarkAwayAction": {
        "required": [
          "date"
        ],
        "properties": {
          "type": {
            "enum": [
              "MarkAway"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatActionMarkEatingOutAction": {
        "required": [
          "date",
          "notes"
        ],
        "properties": {
          "type": {
            "enum": [
              "MarkEatingOut"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "endDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "ChatActionMarkMemberAbsentAction": {
        "required": [
          "memberName",
          "date"
        ],
        "properties": {
          "type": {
            "enum": [
              "MarkMemberAbsent"
            ],
            "type": "string"
          },
          "memberName": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "ChatActionMergePantryFoodsAction": {
        "required": [
          "fromFoodKey",
          "toFoodKey"
        ],
        "properties": {
          "type": {
            "enum": [
              "MergePantryFoods"
            ],
            "type": "string"
          },
          "fromFoodKey": {
            "type": "string"
          },
          "toFoodKey": {
            "type": "string"
          }
        }
      },
      "ChatActionPlanWeekAction": {
        "required": [
          "weekStart"
        ],
        "properties": {
          "type": {
            "enum": [
              "PlanWeek"
            ],
            "type": "string"
          },
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "policy": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanPolicy"
              }
            ]
          }
        }
      },
      "ChatActionRecordOutcomeAction": {
        "required": [
          "date",
          "status"
        ],
        "properties": {
          "type": {
            "enum": [
              "RecordOutcome"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "$ref": "#/components/schemas/OutcomeStatus"
          },
          "reason": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/OutcomeReason"
              }
            ]
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatActionReplaceDinnerAction": {
        "required": [
          "date",
          "dishTitle"
        ],
        "properties": {
          "type": {
            "enum": [
              "ReplaceDinner"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "dishTitle": {
            "type": "string"
          }
        }
      },
      "ChatActionReplanOpenDaysAction": {
        "required": [
          "weekStart"
        ],
        "properties": {
          "type": {
            "enum": [
              "ReplanOpenDays"
            ],
            "type": "string"
          },
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "policy": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanPolicy"
              }
            ]
          }
        }
      },
      "ChatActionSaveRecipeAction": {
        "required": [
          "title",
          "category",
          "ingredients",
          "steps",
          "tags",
          "activeMinutes",
          "totalMinutes",
          "portions"
        ],
        "properties": {
          "type": {
            "enum": [
              "SaveRecipe"
            ],
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "ingredients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeIngredientDto"
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "activeMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "totalMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "portions": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "ChatActionSelectStoresAction": {
        "required": [
          "storeNames"
        ],
        "properties": {
          "type": {
            "enum": [
              "SelectStores"
            ],
            "type": "string"
          },
          "storeNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatActionSetCategoryForDateAction": {
        "required": [
          "date",
          "category"
        ],
        "properties": {
          "type": {
            "enum": [
              "SetCategoryForDate"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "strength": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RuleStrength"
              }
            ]
          }
        }
      },
      "ChatActionSetDinnerLockAction": {
        "required": [
          "date",
          "locked"
        ],
        "properties": {
          "type": {
            "enum": [
              "SetDinnerLock"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "locked": {
            "type": "boolean"
          }
        }
      },
      "ChatActionSetDinnerTimeAction": {
        "required": [
          "time"
        ],
        "properties": {
          "type": {
            "enum": [
              "SetDinnerTime"
            ],
            "type": "string"
          },
          "time": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "date": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "weekdays": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/Weekday"
            }
          }
        }
      },
      "ChatActionSetHouseholdLanguageAction": {
        "required": [
          "languageCode"
        ],
        "properties": {
          "type": {
            "enum": [
              "SetHouseholdLanguage"
            ],
            "type": "string"
          },
          "languageCode": {
            "type": "string"
          }
        }
      },
      "ChatActionSetMaximumCookingTimeForDateAction": {
        "required": [
          "date",
          "minutes"
        ],
        "properties": {
          "type": {
            "enum": [
              "SetMaximumCookingTimeForDate"
            ],
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "minutes": {
            "type": "integer",
            "format": "int32"
          },
          "strength": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RuleStrength"
              }
            ]
          }
        }
      },
      "ChatActionSetRecipeFavoriteAction": {
        "required": [
          "recipeTitle",
          "favorite"
        ],
        "properties": {
          "type": {
            "enum": [
              "SetRecipeFavorite"
            ],
            "type": "string"
          },
          "recipeTitle": {
            "type": "string"
          },
          "favorite": {
            "type": "boolean"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatActionUpdateHouseholdMemberAction": {
        "required": [
          "memberName",
          "newName",
          "birthDate",
          "defaultDinnerParticipant",
          "isActive"
        ],
        "properties": {
          "type": {
            "enum": [
              "UpdateHouseholdMember"
            ],
            "type": "string"
          },
          "memberName": {
            "type": "string"
          },
          "newName": {
            "type": [
              "null",
              "string"
            ]
          },
          "birthDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "defaultDinnerParticipant": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "isActive": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "isBreastfed": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "isGuest": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "portionFactor": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          }
        }
      },
      "ChatActionUpdateHouseholdSettingsAction": {
        "properties": {
          "type": {
            "enum": [
              "UpdateHouseholdSettings"
            ],
            "type": "string"
          },
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "timeZoneId": {
            "type": [
              "null",
              "string"
            ]
          },
          "defaultDinnerTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "prefersLeftoverDinners": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "tailorServingToTastes": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "showSoftServingAdvice": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "maxNewDishesPerWeek": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "alwaysNewDishes": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "babyEatsSeparatelyWhenNeeded": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "pantryWeight": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatActionUpsertCategoryExcludedDaysRuleAction": {
        "required": [
          "category",
          "excludedWeekdays"
        ],
        "properties": {
          "type": {
            "enum": [
              "UpsertCategoryExcludedDaysRule"
            ],
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "excludedWeekdays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Weekday"
            }
          },
          "strength": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RuleStrength"
              }
            ]
          }
        }
      },
      "ChatActionUpsertCategoryFrequencyRuleAction": {
        "required": [
          "category",
          "minimumPerWeek",
          "maximumPerWeek"
        ],
        "properties": {
          "type": {
            "enum": [
              "UpsertCategoryFrequencyRule"
            ],
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "minimumPerWeek": {
            "type": "integer",
            "format": "int32"
          },
          "maximumPerWeek": {
            "type": "integer",
            "format": "int32"
          },
          "strength": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RuleStrength"
              }
            ]
          }
        }
      },
      "ChatActionUpsertCategoryOnDayRuleAction": {
        "required": [
          "category",
          "weekday"
        ],
        "properties": {
          "type": {
            "enum": [
              "UpsertCategoryOnDayRule"
            ],
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "weekday": {
            "$ref": "#/components/schemas/Weekday"
          },
          "strength": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RuleStrength"
              }
            ]
          }
        }
      },
      "ChatActionUpsertMaximumCookingTimeRuleAction": {
        "required": [
          "minutes"
        ],
        "properties": {
          "type": {
            "enum": [
              "UpsertMaximumCookingTimeRule"
            ],
            "type": "string"
          },
          "minutes": {
            "type": "integer",
            "format": "int32"
          },
          "weekdays": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/Weekday"
            }
          },
          "strength": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RuleStrength"
              }
            ]
          }
        }
      },
      "ChatActionUpsertPriceClassRuleAction": {
        "required": [
          "ceiling"
        ],
        "properties": {
          "type": {
            "enum": [
              "UpsertPriceClassRule"
            ],
            "type": "string"
          },
          "ceiling": {
            "$ref": "#/components/schemas/PriceCeiling"
          },
          "weekdays": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/Weekday"
            }
          },
          "strength": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RuleStrength"
              }
            ]
          }
        }
      },
      "ChatActionUpsertRepeatAfterDaysRuleAction": {
        "required": [
          "days"
        ],
        "properties": {
          "type": {
            "enum": [
              "UpsertRepeatAfterDaysRule"
            ],
            "type": "string"
          },
          "days": {
            "type": "integer",
            "format": "int32"
          },
          "strength": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RuleStrength"
              }
            ]
          }
        }
      },
      "ChatActionWishDishAction": {
        "required": [
          "title"
        ],
        "properties": {
          "type": {
            "enum": [
              "WishDish"
            ],
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ChatForeignTextSource": {
        "enum": [
          "Pantry",
          "Recipe",
          "Calendar",
          null
        ]
      },
      "ChatMessageResponse": {
        "required": [
          "id",
          "role",
          "content",
          "toolActivity",
          "widgets",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "role": {
            "$ref": "#/components/schemas/ChatRole"
          },
          "content": {
            "type": "string"
          },
          "toolActivity": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatToolActivityResponse"
            }
          },
          "widgets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatWidgetResponse"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "failed": {
            "type": "boolean",
            "default": false
          },
          "stopped": {
            "type": "boolean",
            "default": false
          },
          "systemNote": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ChatSystemNote"
              }
            ]
          }
        }
      },
      "ChatPermissionMode": {
        "enum": [
          "AlwaysAsk",
          "AutoApplyLowRisk",
          "AutoApplyAll"
        ]
      },
      "ChatPermissionModeResponse": {
        "required": [
          "permissionMode"
        ],
        "type": "object",
        "properties": {
          "permissionMode": {
            "$ref": "#/components/schemas/ChatPermissionMode"
          }
        }
      },
      "ChatReportReason": {
        "enum": [
          "WrongOrDangerous",
          "BreaksHouseRules",
          "Inappropriate",
          "Other",
          null
        ]
      },
      "ChatRole": {
        "enum": [
          "User",
          "Assistant",
          "System"
        ]
      },
      "ChatSystemNote": {
        "enum": [
          "ProposalsAccepted",
          "LanguageChanged",
          null
        ]
      },
      "ChatToolActivityResponse": {
        "required": [
          "toolName",
          "summary",
          "isError"
        ],
        "type": "object",
        "properties": {
          "toolName": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "isError": {
            "type": "boolean"
          }
        }
      },
      "ChatWidgetResponse": {
        "required": [
          "type",
          "payload"
        ],
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "payload": {
            "$ref": "#/components/schemas/JsonElement"
          }
        }
      },
      "CheckAtHomeAnswer": {
        "enum": [
          "HaveIt",
          "Out"
        ]
      },
      "ChooseRecipePageRequest": {
        "required": [
          "choice"
        ],
        "type": "object",
        "properties": {
          "choice": {
            "$ref": "#/components/schemas/RecipePageChoice"
          },
          "index": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "ChooseShoppingProductRequest": {
        "required": [
          "chainCode",
          "productName"
        ],
        "type": "object",
        "properties": {
          "chainCode": {
            "type": "string"
          },
          "productName": {
            "type": "string"
          },
          "price": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          }
        }
      },
      "ClearInventoryObservationsResponse": {
        "required": [
          "removedObservations"
        ],
        "type": "object",
        "properties": {
          "removedObservations": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "CompleteOnboardingRequest": {
        "type": "object",
        "properties": {
          "skippedSteps": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/OnboardingStep"
            }
          },
          "defaultDinnerTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CompleteOnboardingResponse": {
        "required": [
          "onboardingCompletedAt",
          "alreadyCompleted",
          "conversationId"
        ],
        "type": "object",
        "properties": {
          "onboardingCompletedAt": {
            "type": "string",
            "format": "date-time"
          },
          "alreadyCompleted": {
            "type": "boolean"
          },
          "conversationId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "ConfirmEmailChangeRequest": {
        "required": [
          "userId",
          "newEmail",
          "token"
        ],
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "format": "uuid"
          },
          "newEmail": {
            "type": "string"
          },
          "token": {
            "type": "string"
          }
        }
      },
      "ConfirmEmailRequest": {
        "required": [
          "userId",
          "token"
        ],
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "format": "uuid"
          },
          "token": {
            "type": "string"
          }
        }
      },
      "ConfirmImportLine": {
        "required": [
          "foodKey",
          "displayName",
          "quantity",
          "unit",
          "storageLocation"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "storageLocation": {
            "$ref": "#/components/schemas/StorageLocation"
          },
          "linePrice": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unitPrice": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unitPriceUnit": {
            "type": [
              "null",
              "string"
            ]
          },
          "bestBefore": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "rawText": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ConfirmImportRequest": {
        "required": [
          "lines"
        ],
        "type": "object",
        "properties": {
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfirmImportLine"
            }
          },
          "storeName": {
            "type": [
              "null",
              "string"
            ]
          },
          "chainCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "purchasedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "conversationId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "ConsentKind": {
        "enum": [
          "HealthData"
        ]
      },
      "ConsentResponse": {
        "required": [
          "id",
          "householdId",
          "householdMemberId",
          "kind",
          "textVersion",
          "givenAt",
          "withdrawnAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "householdMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "kind": {
            "$ref": "#/components/schemas/ConsentKind"
          },
          "textVersion": {
            "type": "string"
          },
          "givenAt": {
            "type": "string",
            "format": "date-time"
          },
          "withdrawnAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "ConstraintSeverity": {
        "enum": [
          "Hard",
          "Soft"
        ]
      },
      "ConversationResponse": {
        "required": [
          "id",
          "householdId",
          "title",
          "createdAt",
          "messages",
          "proposals"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatMessageResponse"
            }
          },
          "proposals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionProposalResponse"
            }
          },
          "hasEarlier": {
            "type": "boolean",
            "default": false
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ConversationSummaryResponse": {
        "required": [
          "id",
          "title",
          "createdAt",
          "lastMessageAt",
          "lastMessagePreview"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastMessageAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastMessagePreview": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateCalendarFeedRequest": {
        "required": [
          "name",
          "url"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "CreateConversationRequest": {
        "required": [
          "title"
        ],
        "type": "object",
        "properties": {
          "title": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateDietaryConstraintRequest": {
        "required": [
          "householdMemberId",
          "kind",
          "foodOrCategory",
          "severity",
          "notes"
        ],
        "type": "object",
        "properties": {
          "householdMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "kind": {
            "$ref": "#/components/schemas/DietaryConstraintKind"
          },
          "foodOrCategory": {
            "type": "string"
          },
          "severity": {
            "$ref": "#/components/schemas/ConstraintSeverity"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateDishWishRequest": {
        "required": [
          "recipeId",
          "title"
        ],
        "type": "object",
        "properties": {
          "recipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "forMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "CreateFoodPreferenceRequest": {
        "required": [
          "householdMemberId",
          "subjectType",
          "subject",
          "sentiment"
        ],
        "type": "object",
        "properties": {
          "householdMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "subjectType": {
            "$ref": "#/components/schemas/PreferenceSubjectType"
          },
          "subject": {
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "sentiment": {
            "$ref": "#/components/schemas/PreferenceSentiment"
          }
        }
      },
      "CreateHouseholdMemberRequest": {
        "required": [
          "name",
          "birthDate"
        ],
        "type": "object",
        "properties": {
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "birthDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "defaultDinnerParticipant": {
            "type": "boolean",
            "default": true
          },
          "isBreastfed": {
            "type": "boolean",
            "default": false
          },
          "isGuest": {
            "type": "boolean",
            "default": false
          },
          "portionFactor": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "isPregnant": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "safetyAdviceOnly": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "CreateHouseholdRequest": {
        "required": [
          "name",
          "timeZoneId",
          "defaultDinnerTime"
        ],
        "type": "object",
        "properties": {
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "timeZoneId": {
            "type": "string"
          },
          "defaultDinnerTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "preferredLanguage": {
            "type": [
              "null",
              "string"
            ]
          },
          "inviteCode": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateInviteRequest": {
        "required": [
          "role"
        ],
        "type": "object",
        "properties": {
          "role": {
            "$ref": "#/components/schemas/HouseholdRole"
          },
          "memberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "CreateMealEventRequest": {
        "required": [
          "date",
          "eventType",
          "servingTime",
          "availableCookingMinutes",
          "notes",
          "guests",
          "memberAttendance"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "eventType": {
            "$ref": "#/components/schemas/MealEventType"
          },
          "servingTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "availableCookingMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "guests": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/GuestGroupRequest"
            }
          },
          "memberAttendance": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/MemberAttendanceRequest"
            }
          }
        }
      },
      "CreateMealEventRunRequest": {
        "required": [
          "from",
          "through",
          "eventType",
          "servingTime",
          "availableCookingMinutes",
          "notes",
          "guests",
          "memberAttendance"
        ],
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date"
          },
          "through": {
            "type": "string",
            "format": "date"
          },
          "eventType": {
            "$ref": "#/components/schemas/MealEventType"
          },
          "servingTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "availableCookingMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "guests": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/GuestGroupRequest"
            }
          },
          "memberAttendance": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/MemberAttendanceRequest"
            }
          }
        }
      },
      "CreateMealRuleRequest": {
        "required": [
          "payload",
          "strength",
          "scope",
          "startDate",
          "endDate"
        ],
        "type": "object",
        "properties": {
          "payload": {
            "$ref": "#/components/schemas/MealRulePayloadDto"
          },
          "strength": {
            "$ref": "#/components/schemas/RuleStrength"
          },
          "scope": {
            "$ref": "#/components/schemas/RuleScope"
          },
          "startDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "endDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "CreatePlanEntryAdaptationRequest": {
        "required": [
          "useFoodKey",
          "insteadOfFoodKey"
        ],
        "type": "object",
        "properties": {
          "useFoodKey": {
            "type": "string"
          },
          "insteadOfFoodKey": {
            "type": "string"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "note": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "CreateRecipeVariantRequest": {
        "type": "object",
        "properties": {
          "date": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "planOnDate": {
            "type": "boolean",
            "default": false
          },
          "fromTips": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/VariantFromTipsRequest"
              }
            ]
          }
        }
      },
      "CreditPot": {
        "enum": [
          "Welcome",
          "Monthly",
          "None"
        ],
        "default": "Monthly"
      },
      "DataExportResponse": {
        "required": [
          "exportedAt",
          "account",
          "household",
          "people",
          "access",
          "allergies",
          "tastes",
          "rules",
          "events",
          "calendars",
          "recipes",
          "favourites",
          "exclusions",
          "wishes",
          "plans",
          "inventory",
          "shoppingLists",
          "stores",
          "productChoices",
          "conversations",
          "outcomes",
          "imports",
          "priceObservations",
          "subscription",
          "devices",
          "consents"
        ],
        "type": "object",
        "properties": {
          "exportedAt": {
            "type": "string",
            "format": "date-time"
          },
          "account": {
            "$ref": "#/components/schemas/ExportAccount"
          },
          "household": {
            "$ref": "#/components/schemas/ExportHousehold"
          },
          "people": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportPerson"
            }
          },
          "access": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportAccess"
            }
          },
          "allergies": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportNamed"
            }
          },
          "tastes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportNamed"
            }
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportNamed"
            }
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportEvent"
            }
          },
          "calendars": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportNamed"
            }
          },
          "recipes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportRecipe"
            }
          },
          "favourites": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportNamed"
            }
          },
          "exclusions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportNamed"
            }
          },
          "wishes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportNamed"
            }
          },
          "plans": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportPlan"
            }
          },
          "inventory": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportInventoryItem"
            }
          },
          "shoppingLists": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportShoppingList"
            }
          },
          "stores": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportNamed"
            }
          },
          "productChoices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportProductChoice"
            }
          },
          "conversations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportConversation"
            }
          },
          "outcomes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportOutcome"
            }
          },
          "imports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportImport"
            }
          },
          "priceObservations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportPriceObservation"
            }
          },
          "subscription": {
            "$ref": "#/components/schemas/ExportSubscription"
          },
          "devices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportDevice"
            }
          },
          "consents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportConsent"
            }
          }
        }
      },
      "DayOfWeek": {
        "enum": [
          "Sunday",
          "Monday",
          "Tuesday",
          "Wednesday",
          "Thursday",
          "Friday",
          "Saturday",
          null
        ]
      },
      "DefaultRulesResponse": {
        "required": [
          "active",
          "source",
          "rules"
        ],
        "type": "object",
        "properties": {
          "active": {
            "type": "boolean"
          },
          "source": {
            "type": "string"
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MealRuleResponse"
            }
          }
        }
      },
      "DeleteAccountPreviewResponse": {
        "required": [
          "households",
          "canDelete",
          "blocker"
        ],
        "type": "object",
        "properties": {
          "households": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeleteHouseholdImpact"
            }
          },
          "canDelete": {
            "type": "boolean"
          },
          "blocker": {
            "type": [
              "null",
              "string"
            ]
          },
          "blockerCode": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "DeleteHouseholdImpact": {
        "required": [
          "householdId",
          "name",
          "role",
          "otherMembers",
          "willBeDeleted"
        ],
        "type": "object",
        "properties": {
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "otherMembers": {
            "type": "integer",
            "format": "int32"
          },
          "willBeDeleted": {
            "type": "boolean"
          }
        }
      },
      "DeleteHouseholdPreviewResponse": {
        "required": [
          "householdId",
          "name",
          "otherMembers",
          "hasBillingSubscription",
          "canDelete",
          "blockerCode"
        ],
        "type": "object",
        "properties": {
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "otherMembers": {
            "type": "integer",
            "format": "int32"
          },
          "hasBillingSubscription": {
            "type": "boolean"
          },
          "canDelete": {
            "type": "boolean"
          },
          "blockerCode": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "DeviceCalendarEvent": {
        "required": [
          "title",
          "date",
          "startTime",
          "endTime",
          "isAllDay",
          "location"
        ],
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "startTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "endTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "isAllDay": {
            "type": "boolean"
          },
          "location": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "DietaryConstraintKind": {
        "enum": [
          "Allergy",
          "Intolerance",
          "DietaryPattern",
          "Avoidance"
        ]
      },
      "DietaryConstraintResponse": {
        "required": [
          "id",
          "householdId",
          "householdMemberId",
          "kind",
          "foodOrCategory",
          "severity",
          "notes",
          "createdAt",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "householdMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "kind": {
            "$ref": "#/components/schemas/DietaryConstraintKind"
          },
          "foodOrCategory": {
            "type": "string"
          },
          "severity": {
            "$ref": "#/components/schemas/ConstraintSeverity"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DinnerCalendarLinkResponse": {
        "required": [
          "url",
          "webcalUrl"
        ],
        "type": "object",
        "properties": {
          "url": {
            "type": [
              "null",
              "string"
            ]
          },
          "webcalUrl": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "DinnerPriceBasketPublic": {
        "required": [
          "slug",
          "persona",
          "portions",
          "recipeSetVersion",
          "latest",
          "series",
          "exampleWeek",
          "cheapestWeek"
        ],
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "persona": {
            "type": "string"
          },
          "portions": {
            "type": "integer",
            "format": "int32"
          },
          "recipeSetVersion": {
            "type": "string"
          },
          "latest": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/DinnerPriceLatestPublic"
              }
            ]
          },
          "series": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DinnerPriceSeriesPoint"
            }
          },
          "exampleWeek": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/DinnerPriceWeekPublic"
              }
            ]
          },
          "cheapestWeek": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/DinnerPriceWeekPublic"
              }
            ]
          }
        }
      },
      "DinnerPriceBreak": {
        "required": [
          "date",
          "kind",
          "note"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "note": {
            "type": "string"
          }
        }
      },
      "DinnerPriceConfirmResponse": {
        "required": [
          "confirmed"
        ],
        "type": "object",
        "properties": {
          "confirmed": {
            "type": "boolean"
          }
        }
      },
      "DinnerPriceExcludedRecipePublic": {
        "required": [
          "recipeId",
          "title",
          "reason",
          "item",
          "itemEn"
        ],
        "type": "object",
        "properties": {
          "recipeId": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          },
          "item": {
            "type": "string"
          },
          "itemEn": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "DinnerPriceItemPublic": {
        "required": [
          "foodKey",
          "label",
          "labelEn",
          "form",
          "unit",
          "derivedUnit",
          "singleChain"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "labelEn": {
            "type": [
              "null",
              "string"
            ]
          },
          "form": {
            "type": [
              "null",
              "string"
            ]
          },
          "unit": {
            "type": "string"
          },
          "derivedUnit": {
            "type": "boolean"
          },
          "singleChain": {
            "type": "boolean"
          }
        }
      },
      "DinnerPriceLatestPublic": {
        "required": [
          "month",
          "measuredAt",
          "median",
          "p10",
          "p90",
          "perPortion",
          "itemsPriced",
          "itemsTotal",
          "coverage",
          "chainCount",
          "changeFromPrevious",
          "changeFromYearAgo",
          "weeksPriced",
          "weeksTotal",
          "singleChainItems",
          "derivedUnitItems",
          "measurements"
        ],
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "measuredAt": {
            "type": "string",
            "format": "date-time"
          },
          "median": {
            "type": "integer",
            "format": "int32"
          },
          "p10": {
            "type": "integer",
            "format": "int32"
          },
          "p90": {
            "type": "integer",
            "format": "int32"
          },
          "perPortion": {
            "type": "integer",
            "format": "int32"
          },
          "itemsPriced": {
            "type": "integer",
            "format": "int32"
          },
          "itemsTotal": {
            "type": "integer",
            "format": "int32"
          },
          "coverage": {
            "type": "number",
            "format": "double"
          },
          "chainCount": {
            "type": "integer",
            "format": "int32"
          },
          "changeFromPrevious": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "changeFromYearAgo": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "weeksPriced": {
            "type": "integer",
            "format": "int32"
          },
          "weeksTotal": {
            "type": "integer",
            "format": "int32"
          },
          "singleChainItems": {
            "type": "integer",
            "format": "int32"
          },
          "derivedUnitItems": {
            "type": "integer",
            "format": "int32"
          },
          "measurements": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "DinnerPriceMainItem": {
        "required": [
          "label",
          "labelEn",
          "form"
        ],
        "type": "object",
        "properties": {
          "label": {
            "type": "string"
          },
          "labelEn": {
            "type": [
              "null",
              "string"
            ]
          },
          "form": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "DinnerPriceSeriesPoint": {
        "required": [
          "month",
          "median"
        ],
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "median": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "DinnerPricesPublic": {
        "required": [
          "generatedAt",
          "source",
          "license",
          "recipeSetVersion",
          "sampleSize",
          "chainsRead",
          "missingChains",
          "firstMonth",
          "latestMonth",
          "nextMonth",
          "baskets",
          "topTen",
          "breaks",
          "nextPublishDate",
          "items"
        ],
        "type": "object",
        "properties": {
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string"
          },
          "license": {
            "type": "string"
          },
          "recipeSetVersion": {
            "type": [
              "null",
              "string"
            ]
          },
          "sampleSize": {
            "type": "integer",
            "format": "int32"
          },
          "chainsRead": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "missingChains": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "firstMonth": {
            "type": [
              "null",
              "string"
            ]
          },
          "latestMonth": {
            "type": [
              "null",
              "string"
            ]
          },
          "nextMonth": {
            "type": "string"
          },
          "baskets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DinnerPriceBasketPublic"
            }
          },
          "topTen": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DinnerPriceTopTenPublic"
            }
          },
          "breaks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DinnerPriceBreak"
            }
          },
          "nextPublishDate": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DinnerPriceItemPublic"
            }
          },
          "weighting": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/DinnerPriceWeightingPublic"
              }
            ]
          },
          "excludedRecipes": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/DinnerPriceExcludedRecipePublic"
            }
          }
        }
      },
      "DinnerPriceSubscribeRequest": {
        "required": [
          "email",
          "language"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "language": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "DinnerPriceTopTenPublic": {
        "required": [
          "rank",
          "recipeId",
          "title",
          "perPortion",
          "ssbGroup",
          "mainItem"
        ],
        "type": "object",
        "properties": {
          "rank": {
            "type": "integer",
            "format": "int32"
          },
          "recipeId": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "perPortion": {
            "type": "integer",
            "format": "int32"
          },
          "ssbGroup": {
            "type": "string"
          },
          "mainItem": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/DinnerPriceMainItem"
              }
            ]
          }
        }
      },
      "DinnerPriceUnsubscribeResponse": {
        "required": [
          "unsubscribed"
        ],
        "type": "object",
        "properties": {
          "unsubscribed": {
            "type": "boolean"
          }
        }
      },
      "DinnerPriceWeekPublic": {
        "required": [
          "month",
          "weekPrice",
          "recipes"
        ],
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "weekPrice": {
            "type": "integer",
            "format": "int32"
          },
          "recipes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DinnerPriceWeekRecipe"
            }
          }
        }
      },
      "DinnerPriceWeekRecipe": {
        "required": [
          "day",
          "recipeId",
          "title",
          "ssbGroup",
          "price",
          "perPortion",
          "mainItem"
        ],
        "type": "object",
        "properties": {
          "day": {
            "type": "integer",
            "format": "int32"
          },
          "recipeId": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "ssbGroup": {
            "type": "string"
          },
          "price": {
            "type": "integer",
            "format": "int32"
          },
          "perPortion": {
            "type": "integer",
            "format": "int32"
          },
          "mainItem": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/DinnerPriceMainItem"
              }
            ]
          }
        }
      },
      "DinnerPriceWeightingPublic": {
        "required": [
          "kind",
          "version",
          "source",
          "sourceEn",
          "capShareOfWeeks"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "version": {
            "type": [
              "null",
              "string"
            ]
          },
          "source": {
            "type": [
              "null",
              "string"
            ]
          },
          "sourceEn": {
            "type": [
              "null",
              "string"
            ]
          },
          "capShareOfWeeks": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          }
        }
      },
      "DinnerRole": {
        "enum": [
          "Dinner",
          "BreadFood",
          "Unknown",
          "Leftovers",
          null
        ]
      },
      "DinnerTimeSource": {
        "enum": [
          "HouseholdDefault",
          "WeekdayRule",
          "DateEvent",
          "PlanEntry"
        ]
      },
      "DishPhotoBox": {
        "required": [
          "page",
          "x",
          "y",
          "width",
          "height"
        ],
        "type": "object",
        "properties": {
          "page": {
            "type": "integer",
            "format": "int32"
          },
          "x": {
            "type": "number",
            "format": "double"
          },
          "y": {
            "type": "number",
            "format": "double"
          },
          "width": {
            "type": "number",
            "format": "double"
          },
          "height": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "DishWishResponse": {
        "required": [
          "id",
          "recipeId",
          "title",
          "wishedByName",
          "createdAt",
          "votes",
          "voterNames",
          "votedByMe",
          "canRemove"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "recipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "wishedByName": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "votes": {
            "type": "integer",
            "format": "int32"
          },
          "voterNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "votedByMe": {
            "type": "boolean"
          },
          "canRemove": {
            "type": "boolean"
          }
        }
      },
      "DismissHomeQuestionResponse": {
        "required": [
          "questionId",
          "date"
        ],
        "type": "object",
        "properties": {
          "questionId": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "EvidenceKind": {
        "enum": [
          "Sighting",
          "Purchase"
        ],
        "default": "Sighting"
      },
      "ExchangeAuthCodeRequest": {
        "required": [
          "code",
          "codeVerifier"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "codeVerifier": {
            "type": "string"
          }
        }
      },
      "ExportAccess": {
        "required": [
          "userId",
          "displayName",
          "role"
        ],
        "type": "object",
        "properties": {
          "userId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "displayName": {
            "type": "string"
          },
          "role": {
            "type": "string"
          }
        }
      },
      "ExportAccount": {
        "required": [
          "id",
          "email",
          "displayName"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "displayName": {
            "type": "string"
          }
        }
      },
      "ExportConsent": {
        "required": [
          "kind",
          "who",
          "givenBy",
          "textVersion",
          "givenAt",
          "withdrawnAt"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "who": {
            "type": "string"
          },
          "givenBy": {
            "type": "string"
          },
          "textVersion": {
            "type": "string"
          },
          "givenAt": {
            "type": "string",
            "format": "date-time"
          },
          "withdrawnAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "ExportConversation": {
        "required": [
          "createdAt",
          "title",
          "messages"
        ],
        "type": "object",
        "properties": {
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportMessage"
            }
          }
        }
      },
      "ExportDevice": {
        "required": [
          "platform",
          "provider",
          "tokenSuffix",
          "listChanges",
          "householdNews",
          "runDone",
          "registeredAt",
          "lastSeenAt"
        ],
        "type": "object",
        "properties": {
          "platform": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "tokenSuffix": {
            "type": "string"
          },
          "listChanges": {
            "type": "boolean"
          },
          "householdNews": {
            "type": "boolean"
          },
          "runDone": {
            "type": "boolean"
          },
          "registeredAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastSeenAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExportEvent": {
        "required": [
          "date",
          "endDate",
          "kind",
          "note",
          "guestAdults",
          "guestChildren",
          "guestAvoids"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "endDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "kind": {
            "type": "string"
          },
          "note": {
            "type": [
              "null",
              "string"
            ]
          },
          "guestAdults": {
            "type": "integer",
            "format": "int32"
          },
          "guestChildren": {
            "type": "integer",
            "format": "int32"
          },
          "guestAvoids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ExportHousehold": {
        "required": [
          "id",
          "name",
          "timeZoneId",
          "defaultDinnerTime",
          "preferredLanguage",
          "country",
          "address",
          "loyaltyPrograms",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "timeZoneId": {
            "type": "string"
          },
          "defaultDinnerTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "preferredLanguage": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "loyaltyPrograms": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExportImport": {
        "required": [
          "kind",
          "status",
          "source",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "source": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExportInventoryItem": {
        "required": [
          "foodKey",
          "displayName",
          "quantity",
          "unit",
          "storageLocation",
          "observationType",
          "observedAt"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "storageLocation": {
            "type": "string"
          },
          "observationType": {
            "type": "string"
          },
          "observedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExportMessage": {
        "required": [
          "role",
          "content",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "role": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExportNamed": {
        "required": [
          "description",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "createdAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "ExportOutcome": {
        "required": [
          "date",
          "status",
          "note"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "type": "string"
          },
          "note": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ExportPerson": {
        "required": [
          "id",
          "name",
          "birthDate",
          "isActive",
          "eatsDinnerAtHome"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "birthDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "isActive": {
            "type": "boolean"
          },
          "eatsDinnerAtHome": {
            "type": "boolean"
          },
          "isBreastfed": {
            "type": "boolean",
            "default": false
          },
          "isPregnant": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "ExportPlan": {
        "required": [
          "weekStart",
          "status",
          "days"
        ],
        "type": "object",
        "properties": {
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "type": "string"
          },
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportPlanDay"
            }
          }
        }
      },
      "ExportPlanDay": {
        "required": [
          "date",
          "kind",
          "title",
          "locked"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "kind": {
            "type": "string"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "locked": {
            "type": "boolean"
          }
        }
      },
      "ExportPriceObservation": {
        "required": [
          "foodKey",
          "productName",
          "rawText",
          "store",
          "location",
          "linePrice",
          "unitPrice",
          "unitPriceUnit",
          "latitude",
          "longitude",
          "observedAt"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "productName": {
            "type": "string"
          },
          "rawText": {
            "type": [
              "null",
              "string"
            ]
          },
          "store": {
            "type": [
              "null",
              "string"
            ]
          },
          "location": {
            "type": [
              "null",
              "string"
            ]
          },
          "linePrice": {
            "type": "number",
            "format": "double"
          },
          "unitPrice": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unitPriceUnit": {
            "type": [
              "null",
              "string"
            ]
          },
          "latitude": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "longitude": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "observedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExportProductChoice": {
        "required": [
          "foodKey",
          "displayName",
          "chainCode",
          "productName",
          "chosenAt"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "chainCode": {
            "type": "string"
          },
          "productName": {
            "type": "string"
          },
          "chosenAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ExportRecipe": {
        "required": [
          "id",
          "title",
          "category",
          "source",
          "activeMinutes",
          "totalMinutes",
          "basePortions",
          "tags",
          "ingredients",
          "steps"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "activeMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "totalMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "basePortions": {
            "type": "integer",
            "format": "int32"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ingredients": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sourceUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "isBase": {
            "type": "boolean",
            "default": false
          },
          "sections": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "references": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "photo": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ExportShoppingItem": {
        "required": [
          "displayName",
          "quantity",
          "unit",
          "status",
          "purchased"
        ],
        "type": "object",
        "properties": {
          "displayName": {
            "type": "string"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "type": "string"
          },
          "purchased": {
            "type": "boolean"
          }
        }
      },
      "ExportShoppingList": {
        "required": [
          "weekStart",
          "createdAt",
          "items"
        ],
        "type": "object",
        "properties": {
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ExportShoppingItem"
            }
          }
        }
      },
      "ExportSubscription": {
        "required": [
          "planCode",
          "planName",
          "periodStartsAt",
          "periodEndsAt",
          "creditsUsed",
          "creditLimit"
        ],
        "type": "object",
        "properties": {
          "planCode": {
            "type": "string"
          },
          "planName": {
            "type": "string"
          },
          "periodStartsAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "periodEndsAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "creditsUsed": {
            "type": "integer",
            "format": "int32"
          },
          "creditLimit": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "ExtractSectionRequest": {
        "required": [
          "section"
        ],
        "type": "object",
        "properties": {
          "section": {
            "type": "string"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ExtractSectionResponse": {
        "required": [
          "host",
          "base"
        ],
        "type": "object",
        "properties": {
          "host": {
            "$ref": "#/components/schemas/RecipeResponse"
          },
          "base": {
            "$ref": "#/components/schemas/RecipeResponse"
          }
        }
      },
      "FeedbackApiFailure": {
        "required": [
          "at",
          "method",
          "path",
          "status",
          "code",
          "traceId",
          "durationMs"
        ],
        "type": "object",
        "properties": {
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "status": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "code": {
            "type": [
              "null",
              "string"
            ]
          },
          "traceId": {
            "type": [
              "null",
              "string"
            ]
          },
          "durationMs": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "FeedbackClientInfo": {
        "required": [
          "platform",
          "environment",
          "appVersion",
          "build",
          "sha",
          "device"
        ],
        "type": "object",
        "properties": {
          "platform": {
            "type": "string"
          },
          "environment": {
            "type": "string"
          },
          "appVersion": {
            "type": [
              "null",
              "string"
            ]
          },
          "build": {
            "type": [
              "null",
              "string"
            ]
          },
          "sha": {
            "type": [
              "null",
              "string"
            ]
          },
          "device": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "FeedbackSubmittedResponse": {
        "required": [
          "id",
          "reference"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "reference": {
            "type": "string"
          }
        }
      },
      "FirstWeekPhase": {
        "enum": [
          "Empty",
          "Planned",
          "Shopped",
          "ReceiptRead",
          "Swapped",
          "Done"
        ]
      },
      "FirstWeekResponse": {
        "required": [
          "phase",
          "next",
          "done"
        ],
        "type": "object",
        "properties": {
          "phase": {
            "$ref": "#/components/schemas/FirstWeekPhase"
          },
          "next": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/FirstWeekStep"
              }
            ]
          },
          "done": {
            "type": "boolean"
          }
        }
      },
      "FirstWeekStep": {
        "enum": [
          "Plan",
          "OpenList",
          "Receipt",
          "Swap",
          null
        ]
      },
      "FoodPreferenceResponse": {
        "required": [
          "id",
          "householdId",
          "householdMemberId",
          "subjectType",
          "subject",
          "sentiment",
          "source",
          "confidence",
          "createdAt",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "householdMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "subjectType": {
            "$ref": "#/components/schemas/PreferenceSubjectType"
          },
          "subject": {
            "type": "string"
          },
          "sentiment": {
            "$ref": "#/components/schemas/PreferenceSentiment"
          },
          "source": {
            "$ref": "#/components/schemas/PreferenceSource"
          },
          "confidence": {
            "type": "number",
            "format": "double"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ForgotPasswordRequest": {
        "required": [
          "email"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          }
        }
      },
      "GenerateCandidatesRequest": {
        "required": [
          "date",
          "category"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "category": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/MealCategory"
              }
            ]
          }
        }
      },
      "GeneratePlanRequest": {
        "required": [
          "weekStart"
        ],
        "type": "object",
        "properties": {
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "policy": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanPolicy"
              }
            ]
          }
        }
      },
      "GiveConsentRequest": {
        "required": [
          "kind",
          "textVersion"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/ConsentKind"
          },
          "textVersion": {
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "householdMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "GuestGroupRequest": {
        "required": [
          "guestType",
          "guestCount",
          "dietaryNotes"
        ],
        "type": "object",
        "properties": {
          "guestType": {
            "$ref": "#/components/schemas/GuestType"
          },
          "guestCount": {
            "type": "integer",
            "format": "int32"
          },
          "dietaryNotes": {
            "type": [
              "null",
              "string"
            ]
          },
          "avoids": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          }
        }
      },
      "GuestType": {
        "enum": [
          "Adult",
          "Child",
          "Baby",
          null
        ]
      },
      "HealthResponse": {
        "required": [
          "status",
          "version",
          "build"
        ],
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "build": {
            "type": "string"
          }
        }
      },
      "HolidayKey": {
        "enum": [
          "Fastelavn",
          "Easter",
          "ConstitutionDay",
          "Midsummer",
          "SchoolStart",
          "Christmas"
        ]
      },
      "HomeDinnerTimeQuestion": {
        "required": [
          "current",
          "suggested",
          "choices",
          "tooTight",
          "eventTitle",
          "eventStart",
          "personName",
          "totalMinutes",
          "latestStart"
        ],
        "type": "object",
        "properties": {
          "current": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "suggested": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "choices": {
            "type": "array",
            "items": {
              "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
              "type": "string"
            }
          },
          "tooTight": {
            "type": "array",
            "items": {
              "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
              "type": "string"
            }
          },
          "eventTitle": {
            "type": "string"
          },
          "eventStart": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "personName": {
            "type": [
              "null",
              "string"
            ]
          },
          "totalMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "latestStart": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          }
        }
      },
      "HomeEventResponse": {
        "required": [
          "title",
          "startTime",
          "endTime",
          "isAllDay",
          "personName"
        ],
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "startTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "endTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "isAllDay": {
            "type": "boolean"
          },
          "personName": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "HomeFirstWeekQuestion": {
        "required": [
          "phase",
          "next",
          "swapDay"
        ],
        "type": "object",
        "properties": {
          "phase": {
            "$ref": "#/components/schemas/FirstWeekPhase"
          },
          "next": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/FirstWeekStep"
              }
            ]
          },
          "swapDay": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "HomeHolidayQuestion": {
        "required": [
          "key",
          "date"
        ],
        "type": "object",
        "properties": {
          "key": {
            "$ref": "#/components/schemas/HolidayKey"
          },
          "date": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "HomeNoteKind": {
        "enum": [
          "DinnerTimeMoved",
          "DinnerTimeMovedWeekly",
          "RecipeAdapted",
          "OutcomeRecorded",
          "UseUpApplied"
        ]
      },
      "HomeNoteResponse": {
        "required": [
          "id",
          "kind",
          "answeredAt",
          "undoable",
          "undoExpiresAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/HomeNoteKind"
          },
          "answeredAt": {
            "type": "string",
            "format": "date-time"
          },
          "undoable": {
            "type": "boolean"
          },
          "undoExpiresAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "fromTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "toTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "fromFoodDisplayName": {
            "type": [
              "null",
              "string"
            ]
          },
          "toFoodDisplayName": {
            "type": [
              "null",
              "string"
            ]
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "changeCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "usedDisplayNames": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "text": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "HomePantryStaleQuestion": {
        "required": [
          "daysSinceLastObservation"
        ],
        "type": "object",
        "properties": {
          "daysSinceLastObservation": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "HomeQuestionKind": {
        "enum": [
          "Onboarding",
          "Outcome",
          "DinnerTime",
          "UseUp",
          "UseUpBundle",
          "PantryStale",
          "FirstWeek",
          "Holiday"
        ]
      },
      "HomeQuestionResponse": {
        "required": [
          "id",
          "kind",
          "requiresAdult"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/HomeQuestionKind"
          },
          "requiresAdult": {
            "type": "boolean"
          },
          "dinnerTime": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HomeDinnerTimeQuestion"
              }
            ]
          },
          "useUp": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HomeUseUpQuestion"
              }
            ]
          },
          "planEntryId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "useUpBundle": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HomeUseUpBundleQuestion"
              }
            ]
          },
          "pantryStale": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HomePantryStaleQuestion"
              }
            ]
          },
          "firstWeek": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HomeFirstWeekQuestion"
              }
            ]
          },
          "holiday": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HomeHolidayQuestion"
              }
            ]
          }
        }
      },
      "HomeShoppingSummaryResponse": {
        "required": [
          "planId",
          "shoppingListId",
          "itemsToBuy",
          "bestStoreName",
          "bestStoreHasEverything",
          "estimatedTotal",
          "stale"
        ],
        "type": "object",
        "properties": {
          "planId": {
            "type": "string",
            "format": "uuid"
          },
          "shoppingListId": {
            "type": "string",
            "format": "uuid"
          },
          "itemsToBuy": {
            "type": "integer",
            "format": "int32"
          },
          "bestStoreName": {
            "type": [
              "null",
              "string"
            ]
          },
          "bestStoreHasEverything": {
            "type": "boolean"
          },
          "estimatedTotal": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "stale": {
            "type": "boolean"
          },
          "itemsAtHome": {
            "type": "integer",
            "format": "int32",
            "default": 0
          }
        }
      },
      "HomeTimingResponse": {
        "required": [
          "gapMinutes",
          "tightBelowMinutes",
          "suggestedDinnerTime",
          "latestStart",
          "activeMinutes",
          "totalMinutes",
          "nextEventTitle",
          "nextEventStart",
          "nextEventPersonName"
        ],
        "type": "object",
        "properties": {
          "gapMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "tightBelowMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "suggestedDinnerTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "latestStart": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "activeMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "totalMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "nextEventTitle": {
            "type": [
              "null",
              "string"
            ]
          },
          "nextEventStart": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "nextEventPersonName": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "HomeTodayResponse": {
        "required": [
          "date",
          "conversationId",
          "morningMessageId",
          "dinnerTime",
          "dinnerTimeSource",
          "planId",
          "tonight",
          "events",
          "timing",
          "shopping",
          "questions",
          "notes"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "conversationId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "morningMessageId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "dinnerTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "dinnerTimeSource": {
            "$ref": "#/components/schemas/DinnerTimeSource"
          },
          "planId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "tonight": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanEntryResponse"
              }
            ]
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HomeEventResponse"
            }
          },
          "timing": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HomeTimingResponse"
              }
            ]
          },
          "shopping": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HomeShoppingSummaryResponse"
              }
            ]
          },
          "questions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HomeQuestionResponse"
            }
          },
          "notes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HomeNoteResponse"
            }
          }
        }
      },
      "HomeUseUpBundleQuestion": {
        "required": [
          "bundleId",
          "changes",
          "coveredCount",
          "urgentCount",
          "notDinner"
        ],
        "type": "object",
        "properties": {
          "bundleId": {
            "type": "string"
          },
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HomeUseUpChange"
            }
          },
          "coveredCount": {
            "type": "integer",
            "format": "int32"
          },
          "urgentCount": {
            "type": "integer",
            "format": "int32"
          },
          "notDinner": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HomeUseUpFood"
            }
          },
          "nextWeek": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HomeUseUpNextWeek"
              }
            ]
          }
        }
      },
      "HomeUseUpChange": {
        "required": [
          "id",
          "day",
          "kind",
          "recipeId",
          "title",
          "uses",
          "handsBack",
          "activeMinutes",
          "shoppingDelta"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "day": {
            "type": "string",
            "format": "date"
          },
          "kind": {
            "$ref": "#/components/schemas/HomeUseUpChangeKind"
          },
          "recipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "uses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HomeUseUpUse"
            }
          },
          "handsBack": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HomeUseUpUse"
            }
          },
          "activeMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "shoppingDelta": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "HomeUseUpChangeKind": {
        "enum": [
          "SwapDish",
          "AdaptIngredient",
          "FillOpenDay"
        ]
      },
      "HomeUseUpFood": {
        "required": [
          "foodKey",
          "displayName"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "reason": {
            "$ref": "#/components/schemas/HomeUseUpFoodReason"
          }
        }
      },
      "HomeUseUpFoodReason": {
        "enum": [
          "BreadFood",
          "NoRecipe",
          "Leftovers",
          "NoChangeInTime"
        ],
        "default": "BreadFood"
      },
      "HomeUseUpNextWeek": {
        "required": [
          "changeCount",
          "foodNames"
        ],
        "type": "object",
        "properties": {
          "changeCount": {
            "type": "integer",
            "format": "int32"
          },
          "foodNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "HomeUseUpQuestion": {
        "required": [
          "itemFoodKey",
          "itemDisplayName",
          "bestBefore",
          "replacesFoodKey",
          "replacesDisplayName",
          "quantity",
          "unit",
          "note"
        ],
        "type": "object",
        "properties": {
          "itemFoodKey": {
            "type": "string"
          },
          "itemDisplayName": {
            "type": "string"
          },
          "bestBefore": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "replacesFoodKey": {
            "type": "string"
          },
          "replacesDisplayName": {
            "type": "string"
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit": {
            "type": "string"
          },
          "note": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "HomeUseUpUse": {
        "required": [
          "foodKey",
          "displayName",
          "quantity",
          "unit"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "usesMostOfIt": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "HostLinkSuggestion": {
        "required": [
          "hostRecipeId",
          "hostTitle",
          "referenceId",
          "label",
          "sourceHint"
        ],
        "type": "object",
        "properties": {
          "hostRecipeId": {
            "type": "string",
            "format": "uuid"
          },
          "hostTitle": {
            "type": "string"
          },
          "referenceId": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "sourceHint": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "HouseholdChoiceResponse": {
        "required": [
          "productName",
          "chainCode",
          "storeName",
          "applied"
        ],
        "type": "object",
        "properties": {
          "productName": {
            "type": "string"
          },
          "chainCode": {
            "type": "string"
          },
          "storeName": {
            "type": [
              "null",
              "string"
            ]
          },
          "applied": {
            "type": "boolean"
          }
        }
      },
      "HouseholdMemberResponse": {
        "required": [
          "id",
          "householdId",
          "name",
          "birthDate",
          "ageYears",
          "ageMonths",
          "defaultDinnerParticipant",
          "isActive",
          "isBreastfed",
          "createdAt",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "birthDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "ageYears": {
            "type": "integer",
            "format": "int32"
          },
          "ageMonths": {
            "type": "integer",
            "format": "int32"
          },
          "defaultDinnerParticipant": {
            "type": "boolean"
          },
          "isActive": {
            "type": "boolean"
          },
          "isBreastfed": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "isGuest": {
            "type": "boolean",
            "default": false
          },
          "portionFactor": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "nutritionTargets": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/NutritionTargetsResponse"
              }
            ]
          },
          "nutritionOverrides": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/NutritionOverridesDto"
              }
            ]
          },
          "isPregnant": {
            "type": "boolean",
            "default": false
          },
          "safetyAdviceOnly": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "HouseholdMembershipResponse": {
        "required": [
          "userId",
          "displayName",
          "email",
          "role",
          "memberId"
        ],
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "format": "uuid"
          },
          "displayName": {
            "type": "string"
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "role": {
            "$ref": "#/components/schemas/HouseholdRole"
          },
          "memberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "HouseholdMilestoneKind": {
        "enum": [
          "FirstPlan",
          "FirstListOpened",
          "FirstTripCompleted",
          "FirstReceiptRead",
          "FirstSwap",
          "FirstUsefulWeek"
        ]
      },
      "HouseholdMilestoneResponse": {
        "required": [
          "kind",
          "reachedAt",
          "source"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/HouseholdMilestoneKind"
          },
          "reachedAt": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string"
          }
        }
      },
      "HouseholdMilestonesResponse": {
        "required": [
          "milestones",
          "firstWeek"
        ],
        "type": "object",
        "properties": {
          "milestones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HouseholdMilestoneResponse"
            }
          },
          "firstWeek": {
            "$ref": "#/components/schemas/FirstWeekResponse"
          }
        }
      },
      "HouseholdResponse": {
        "required": [
          "id",
          "name",
          "timeZoneId",
          "defaultDinnerTime",
          "preferredLanguage",
          "country",
          "preferOwnRecipes",
          "prefersLeftoverDinners",
          "measurementSystem",
          "dryGoodsMeasure",
          "address",
          "loyaltyPrograms",
          "createdAt",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "timeZoneId": {
            "type": "string"
          },
          "defaultDinnerTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "preferredLanguage": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "preferOwnRecipes": {
            "type": "boolean"
          },
          "prefersLeftoverDinners": {
            "type": "boolean"
          },
          "measurementSystem": {
            "type": "string"
          },
          "dryGoodsMeasure": {
            "type": "string"
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "loyaltyPrograms": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "shareReceiptPrices": {
            "type": "boolean",
            "default": true
          },
          "onboardingCompletedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "shareUnitWeights": {
            "type": "boolean",
            "default": false
          },
          "tailorServingToTastes": {
            "type": "boolean",
            "default": true
          },
          "showSoftServingAdvice": {
            "type": "boolean",
            "default": true
          },
          "babyEatsSeparatelyWhenNeeded": {
            "type": "boolean",
            "default": false
          },
          "maxNewDishesPerWeek": {
            "type": "integer",
            "format": "int32",
            "default": 7
          },
          "alwaysNewDishes": {
            "type": "boolean",
            "default": false
          },
          "pantryWeight": {
            "type": "string",
            "default": "Usual"
          },
          "useStarterLibrary": {
            "type": "boolean",
            "default": true
          },
          "showChildTasks": {
            "type": "boolean",
            "default": false
          },
          "madeAgainLift": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "HouseholdRole": {
        "enum": [
          "Owner",
          "Adult",
          "Junior"
        ]
      },
      "HouseholdStoreResponse": {
        "required": [
          "id",
          "externalStoreId",
          "chainCode",
          "name",
          "address"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "externalStoreId": {
            "type": [
              "null",
              "string"
            ]
          },
          "chainCode": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ImportDocumentKind": {
        "enum": [
          "Text",
          "Pdf",
          "Image"
        ]
      },
      "ImportPurpose": {
        "enum": [
          "Purchase",
          "StorageSnapshot"
        ]
      },
      "ImportRecipePhotoRequest": {
        "required": [
          "fileName",
          "contentType",
          "base64Data"
        ],
        "type": "object",
        "properties": {
          "fileName": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "base64Data": {
            "type": "string"
          },
          "additionalPages": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipePhotoPage"
            }
          },
          "conversationId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "hostRecipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "referenceId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "ImportRecipeTextRequest": {
        "required": [
          "text"
        ],
        "type": "object",
        "properties": {
          "text": {
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "conversationId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "hostRecipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "referenceId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "sourceUrl": {
            "maxLength": 2048,
            "type": [
              "null",
              "string"
            ]
          },
          "imageUrl": {
            "maxLength": 2048,
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ImportRecipeUrlRequest": {
        "required": [
          "url"
        ],
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "conversationId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "hostRecipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "referenceId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "ImportStatus": {
        "enum": [
          "Pending",
          "Parsed",
          "Confirmed",
          "Failed"
        ]
      },
      "IntakeStatusResponse": {
        "required": [
          "mode",
          "storeLinks"
        ],
        "type": "object",
        "properties": {
          "mode": {
            "type": "string"
          },
          "storeLinks": {
            "$ref": "#/components/schemas/StoreLinksResponse"
          }
        }
      },
      "InventoryGroup": {
        "enum": [
          "UseSoon",
          "LikelyAvailable",
          "Uncertain",
          "Aged",
          null
        ]
      },
      "InventoryGroupCounts": {
        "required": [
          "useSoon",
          "likelyAvailable",
          "uncertain",
          "aged"
        ],
        "type": "object",
        "properties": {
          "useSoon": {
            "type": "integer",
            "format": "int32"
          },
          "likelyAvailable": {
            "type": "integer",
            "format": "int32"
          },
          "uncertain": {
            "type": "integer",
            "format": "int32"
          },
          "aged": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "InventoryItemResponse": {
        "required": [
          "foodKey",
          "displayName",
          "estimatedQuantity",
          "unit",
          "confidence",
          "storageLocation",
          "usePriority",
          "lastEvidenceAt"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "estimatedQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "confidence": {
            "type": "number",
            "format": "double"
          },
          "storageLocation": {
            "$ref": "#/components/schemas/StorageLocation"
          },
          "usePriority": {
            "$ref": "#/components/schemas/UsePriority"
          },
          "lastEvidenceAt": {
            "type": "string",
            "format": "date-time"
          },
          "aisle": {
            "$ref": "#/components/schemas/ShoppingAisle"
          },
          "useWithinDays": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "mergedFromKeys": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "bestBefore": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "asksForDate": {
            "type": "boolean",
            "default": false
          },
          "lastEvidenceKind": {
            "$ref": "#/components/schemas/EvidenceKind"
          },
          "plannedQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "remainingQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "dinnerRole": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/DinnerRole"
              }
            ]
          },
          "lotCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "oldestLotQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "oldestLotUnit": {
            "type": [
              "null",
              "string"
            ]
          },
          "lots": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/InventoryLotResponse"
            }
          },
          "plannedFor": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/InventoryPlannedUseResponse"
            }
          }
        }
      },
      "InventoryLotResponse": {
        "required": [
          "quantity",
          "unit",
          "acquiredAt",
          "bestBefore"
        ],
        "type": "object",
        "properties": {
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "acquiredAt": {
            "type": "string",
            "format": "date-time"
          },
          "bestBefore": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "InventoryPlaceCounts": {
        "required": [
          "fridge",
          "pantry",
          "freezer",
          "unknown"
        ],
        "type": "object",
        "properties": {
          "fridge": {
            "type": "integer",
            "format": "int32"
          },
          "pantry": {
            "type": "integer",
            "format": "int32"
          },
          "freezer": {
            "type": "integer",
            "format": "int32"
          },
          "unknown": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "InventoryPlannedUseResponse": {
        "required": [
          "title",
          "date"
        ],
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "InventorySummaryResponse": {
        "required": [
          "byPlace",
          "byGroup",
          "triage",
          "total",
          "matchesByPlace"
        ],
        "type": "object",
        "properties": {
          "byPlace": {
            "$ref": "#/components/schemas/InventoryPlaceCounts"
          },
          "byGroup": {
            "$ref": "#/components/schemas/InventoryGroupCounts"
          },
          "triage": {
            "type": "integer",
            "format": "int32"
          },
          "total": {
            "type": "integer",
            "format": "int32"
          },
          "matchesByPlace": {
            "$ref": "#/components/schemas/InventoryPlaceCounts"
          }
        }
      },
      "InviteCreatedResponse": {
        "required": [
          "id",
          "code",
          "role",
          "expiresAt",
          "memberId"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "code": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/HouseholdRole"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "memberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "InviteResponse": {
        "required": [
          "id",
          "role",
          "expiresAt",
          "createdAt",
          "memberId",
          "memberName"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "role": {
            "$ref": "#/components/schemas/HouseholdRole"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "memberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "JoinWaitlistRequest": {
        "required": [
          "email",
          "note"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "note": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "JsonElement": { },
      "LeftoverRequest": {
        "required": [
          "description",
          "portionCount",
          "quantity",
          "unit",
          "storageLocation"
        ],
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "portionCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "storageLocation": {
            "$ref": "#/components/schemas/StorageLocation"
          }
        }
      },
      "LeftoverResponse": {
        "required": [
          "id",
          "description",
          "portionCount",
          "quantity",
          "unit",
          "storageLocation"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "description": {
            "type": "string"
          },
          "portionCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "storageLocation": {
            "$ref": "#/components/schemas/StorageLocation"
          }
        }
      },
      "LinkMembershipMemberRequest": {
        "required": [
          "memberId"
        ],
        "type": "object",
        "properties": {
          "memberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "LoginRequest": {
        "required": [
          "email",
          "password"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "password": {
            "type": "string"
          }
        }
      },
      "LoyaltyProgramResponse": {
        "required": [
          "code",
          "displayName"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          }
        }
      },
      "MealCandidateResponse": {
        "required": [
          "id",
          "householdId",
          "targetDate",
          "category",
          "content",
          "scores",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "targetDate": {
            "type": "string",
            "format": "date"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "content": {
            "$ref": "#/components/schemas/CandidateContent"
          },
          "scores": {
            "type": "object",
            "additionalProperties": {
              "type": "integer",
              "format": "int32"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "recipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "price": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/CandidatePrice"
              }
            ]
          }
        }
      },
      "MealCategory": {
        "enum": [
          "Fish",
          "Meat",
          "Poultry",
          "Vegetarian",
          "Vegan",
          "Soup",
          "Pasta",
          "Taco",
          "Leftovers",
          "Other"
        ]
      },
      "MealEventParticipantResponse": {
        "required": [
          "id",
          "householdMemberId",
          "isPresent",
          "guestType",
          "guestCount",
          "dietaryNotes",
          "avoids"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "isPresent": {
            "type": "boolean"
          },
          "guestType": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/GuestType"
              }
            ]
          },
          "guestCount": {
            "type": "integer",
            "format": "int32"
          },
          "dietaryNotes": {
            "type": [
              "null",
              "string"
            ]
          },
          "avoids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "MealEventResponse": {
        "required": [
          "id",
          "householdId",
          "date",
          "eventType",
          "servingTime",
          "availableCookingMinutes",
          "notes",
          "participants",
          "createdAt",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "eventType": {
            "$ref": "#/components/schemas/MealEventType"
          },
          "servingTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "availableCookingMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "participants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MealEventParticipantResponse"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MealEventType": {
        "enum": [
          "Guests",
          "EatingOut",
          "PersonAbsent",
          "PersonPresent",
          "Activity",
          "Celebration",
          "Travel",
          "Custom"
        ]
      },
      "MealOutcomeResponse": {
        "required": [
          "id",
          "planEntryId",
          "status",
          "reason",
          "notes",
          "leftovers",
          "recordedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "planEntryId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/OutcomeStatus"
          },
          "reason": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/OutcomeReason"
              }
            ]
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "leftovers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LeftoverResponse"
            }
          },
          "recordedAt": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "$ref": "#/components/schemas/OutcomeSource"
          },
          "followedUpAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "MealRuleKind": {
        "enum": [
          "CategoryFrequency",
          "CategoryExcludedDays",
          "CategoryOnDay",
          "MaximumActiveCookingTime",
          "DinnerTimeOnWeekday",
          "PriceClassOnDays",
          "RepeatAfterDays"
        ]
      },
      "MealRulePayloadDto": {
        "required": [
          "kind"
        ],
        "type": "object",
        "anyOf": [
          {
            "$ref": "#/components/schemas/MealRulePayloadDtoCategoryFrequencyRulePayload"
          },
          {
            "$ref": "#/components/schemas/MealRulePayloadDtoCategoryExcludedDaysRulePayload"
          },
          {
            "$ref": "#/components/schemas/MealRulePayloadDtoCategoryOnDayRulePayload"
          },
          {
            "$ref": "#/components/schemas/MealRulePayloadDtoMaximumActiveCookingTimeRulePayload"
          },
          {
            "$ref": "#/components/schemas/MealRulePayloadDtoDinnerTimeOnWeekdayRulePayload"
          },
          {
            "$ref": "#/components/schemas/MealRulePayloadDtoPriceClassOnDaysRulePayload"
          },
          {
            "$ref": "#/components/schemas/MealRulePayloadDtoRepeatAfterDaysRulePayload"
          }
        ],
        "discriminator": {
          "propertyName": "kind",
          "mapping": {
            "CategoryFrequency": "#/components/schemas/MealRulePayloadDtoCategoryFrequencyRulePayload",
            "CategoryExcludedDays": "#/components/schemas/MealRulePayloadDtoCategoryExcludedDaysRulePayload",
            "CategoryOnDay": "#/components/schemas/MealRulePayloadDtoCategoryOnDayRulePayload",
            "MaximumActiveCookingTime": "#/components/schemas/MealRulePayloadDtoMaximumActiveCookingTimeRulePayload",
            "DinnerTimeOnWeekday": "#/components/schemas/MealRulePayloadDtoDinnerTimeOnWeekdayRulePayload",
            "PriceClassOnDays": "#/components/schemas/MealRulePayloadDtoPriceClassOnDaysRulePayload",
            "RepeatAfterDays": "#/components/schemas/MealRulePayloadDtoRepeatAfterDaysRulePayload"
          }
        }
      },
      "MealRulePayloadDtoCategoryExcludedDaysRulePayload": {
        "required": [
          "category",
          "excludedWeekdays"
        ],
        "properties": {
          "kind": {
            "enum": [
              "CategoryExcludedDays"
            ],
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "excludedWeekdays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Weekday"
            }
          }
        }
      },
      "MealRulePayloadDtoCategoryFrequencyRulePayload": {
        "required": [
          "category",
          "minimumPerWeek",
          "maximumPerWeek"
        ],
        "properties": {
          "kind": {
            "enum": [
              "CategoryFrequency"
            ],
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "minimumPerWeek": {
            "type": "integer",
            "format": "int32"
          },
          "maximumPerWeek": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "MealRulePayloadDtoCategoryOnDayRulePayload": {
        "required": [
          "category",
          "weekday",
          "specificDate"
        ],
        "properties": {
          "kind": {
            "enum": [
              "CategoryOnDay"
            ],
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "weekday": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/Weekday"
              }
            ]
          },
          "specificDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "MealRulePayloadDtoDinnerTimeOnWeekdayRulePayload": {
        "required": [
          "time",
          "weekdays"
        ],
        "properties": {
          "kind": {
            "enum": [
              "DinnerTimeOnWeekday"
            ],
            "type": "string"
          },
          "time": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "weekdays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Weekday"
            }
          }
        }
      },
      "MealRulePayloadDtoMaximumActiveCookingTimeRulePayload": {
        "required": [
          "minutes",
          "weekdays",
          "specificDate"
        ],
        "properties": {
          "kind": {
            "enum": [
              "MaximumActiveCookingTime"
            ],
            "type": "string"
          },
          "minutes": {
            "type": "integer",
            "format": "int32"
          },
          "weekdays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Weekday"
            }
          },
          "specificDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "MealRulePayloadDtoPriceClassOnDaysRulePayload": {
        "required": [
          "ceiling",
          "weekdays"
        ],
        "properties": {
          "kind": {
            "enum": [
              "PriceClassOnDays"
            ],
            "type": "string"
          },
          "ceiling": {
            "$ref": "#/components/schemas/PriceCeiling"
          },
          "weekdays": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Weekday"
            }
          }
        }
      },
      "MealRulePayloadDtoRepeatAfterDaysRulePayload": {
        "required": [
          "days"
        ],
        "properties": {
          "kind": {
            "enum": [
              "RepeatAfterDays"
            ],
            "type": "string"
          },
          "days": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "MealRuleResponse": {
        "required": [
          "id",
          "householdId",
          "kind",
          "strength",
          "scope",
          "startDate",
          "endDate",
          "source",
          "payload",
          "createdAt",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "$ref": "#/components/schemas/MealRuleKind"
          },
          "strength": {
            "$ref": "#/components/schemas/RuleStrength"
          },
          "scope": {
            "$ref": "#/components/schemas/RuleScope"
          },
          "startDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "endDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "source": {
            "$ref": "#/components/schemas/RuleSource"
          },
          "payload": {
            "$ref": "#/components/schemas/MealRulePayloadDto"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemberAttendanceRequest": {
        "required": [
          "householdMemberId",
          "isPresent",
          "dietaryNotes"
        ],
        "type": "object",
        "properties": {
          "householdMemberId": {
            "type": "string",
            "format": "uuid"
          },
          "isPresent": {
            "type": "boolean"
          },
          "dietaryNotes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "MembershipResponse": {
        "required": [
          "householdId",
          "householdName",
          "role",
          "memberId"
        ],
        "type": "object",
        "properties": {
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "householdName": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/HouseholdRole"
          },
          "memberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "MeResponse": {
        "required": [
          "user",
          "memberships",
          "isPlatformAdmin"
        ],
        "type": "object",
        "properties": {
          "user": {
            "$ref": "#/components/schemas/UserResponse"
          },
          "memberships": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MembershipResponse"
            }
          },
          "isPlatformAdmin": {
            "type": "boolean"
          },
          "emailConfirmed": {
            "type": "boolean",
            "default": true
          },
          "hasPassword": {
            "type": "boolean",
            "default": true
          },
          "serverNow": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "MergePantryKeysRequest": {
        "required": [
          "fromKey",
          "toKey"
        ],
        "type": "object",
        "properties": {
          "fromKey": {
            "type": "string"
          },
          "toKey": {
            "type": "string"
          },
          "source": {
            "$ref": "#/components/schemas/PantryMergeSource"
          }
        }
      },
      "MovePlanEntryRequest": {
        "required": [
          "toDate"
        ],
        "type": "object",
        "properties": {
          "toDate": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "NearbyStoreResponse": {
        "required": [
          "externalStoreId",
          "chainCode",
          "name",
          "address",
          "distanceMeters"
        ],
        "type": "object",
        "properties": {
          "externalStoreId": {
            "type": [
              "null",
              "string"
            ]
          },
          "chainCode": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "distanceMeters": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          }
        }
      },
      "NearbyStoresResponse": {
        "required": [
          "addressFound",
          "stores"
        ],
        "type": "object",
        "properties": {
          "addressFound": {
            "type": "boolean"
          },
          "stores": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NearbyStoreResponse"
            }
          }
        }
      },
      "NovaAdditiveClass": {
        "enum": [
          "Colour",
          "Emulsifier",
          "FlavourEnhancer",
          "Glazing",
          "Sweetener",
          "ModifiedStarch",
          null
        ]
      },
      "NovaReasonKind": {
        "enum": [
          "Additive",
          "Ingredient"
        ]
      },
      "NutritionOverridesDto": {
        "type": "object",
        "properties": {
          "energyKcal": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "proteinGrams": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "fibreGrams": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "addedSugarMaxGrams": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "saltMaxGrams": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "fruitVegPortions": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "NutritionTargetsResponse": {
        "required": [
          "energyKcal",
          "proteinGrams",
          "fibreGrams",
          "addedSugarMaxGrams",
          "saltMaxGrams",
          "fruitVegPortions"
        ],
        "type": "object",
        "properties": {
          "energyKcal": {
            "type": "integer",
            "format": "int32"
          },
          "proteinGrams": {
            "type": "integer",
            "format": "int32"
          },
          "fibreGrams": {
            "type": "integer",
            "format": "int32"
          },
          "addedSugarMaxGrams": {
            "type": "integer",
            "format": "int32"
          },
          "saltMaxGrams": {
            "type": "number",
            "format": "double"
          },
          "fruitVegPortions": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "ObservationType": {
        "enum": [
          "Purchased",
          "ConfirmedPresent",
          "EstimatedConsumed",
          "ManuallyAdjusted",
          "Frozen",
          "Discarded",
          "LeftoverCreated",
          "ConfirmedGone"
        ]
      },
      "OffContributionRequest": {
        "required": [
          "barcode",
          "productName",
          "brands",
          "quantity"
        ],
        "type": "object",
        "properties": {
          "barcode": {
            "type": "string"
          },
          "productName": {
            "type": "string"
          },
          "brands": {
            "type": [
              "null",
              "string"
            ]
          },
          "quantity": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "OffContributionResponse": {
        "required": [
          "barcode",
          "productUrl"
        ],
        "type": "object",
        "properties": {
          "barcode": {
            "type": "string"
          },
          "productUrl": {
            "type": "string"
          }
        }
      },
      "OffProductLookupResponse": {
        "required": [
          "found",
          "barcode",
          "productName",
          "brands",
          "quantity",
          "foodKey",
          "novaGroup",
          "nutriScore",
          "allergensTags"
        ],
        "type": "object",
        "properties": {
          "found": {
            "type": "boolean"
          },
          "barcode": {
            "type": "string"
          },
          "productName": {
            "type": [
              "null",
              "string"
            ]
          },
          "brands": {
            "type": [
              "null",
              "string"
            ]
          },
          "quantity": {
            "type": [
              "null",
              "string"
            ]
          },
          "foodKey": {
            "type": [
              "null",
              "string"
            ]
          },
          "novaGroup": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "nutriScore": {
            "type": [
              "null",
              "string"
            ]
          },
          "allergensTags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sugarsPer100g": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "saltPer100g": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          }
        }
      },
      "OnboardingStep": {
        "enum": [
          "Pantry",
          "People",
          "Allergies",
          "Rules"
        ]
      },
      "OpenPortalRequest": {
        "required": [
          "returnUrl"
        ],
        "type": "object",
        "properties": {
          "returnUrl": {
            "type": "string"
          }
        }
      },
      "OutcomeReason": {
        "enum": [
          "UnexpectedEvent",
          "MissingIngredient",
          "NotEnoughTime",
          "TooTired",
          "NotAppealing",
          "Illness",
          "PlansChanged",
          "Other",
          "DidNotLike",
          null
        ]
      },
      "OutcomeSource": {
        "enum": [
          "Form",
          "CookMode"
        ],
        "default": "Form"
      },
      "OutcomeStatus": {
        "enum": [
          "Cooked",
          "CookedWithChanges",
          "NotCooked",
          "AteElsewhere"
        ]
      },
      "PackageSuggestionResponse": {
        "required": [
          "count",
          "unit",
          "gramsPerPackage"
        ],
        "type": "object",
        "properties": {
          "count": {
            "type": "integer",
            "format": "int32"
          },
          "unit": {
            "type": "string"
          },
          "gramsPerPackage": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "PagedConversationSummaryResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConversationSummaryResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PagedInventoryItemResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InventoryItemResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PagedMealEventResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MealEventResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PagedMealOutcomeResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MealOutcomeResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PagedPantryMergeListItemResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PantryMergeListItemResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PagedPantryMergeRejectionListItemResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PantryMergeRejectionListItemResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PagedRecipeListItemResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeListItemResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PagedRecipeResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PagedRecipeRevisionResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeRevisionResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PagedReferenceLinkCandidateResponse": {
        "required": [
          "items",
          "nextCursor",
          "total"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReferenceLinkCandidateResponse"
            }
          },
          "nextCursor": {
            "type": [
              "null",
              "string"
            ]
          },
          "total": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PantryMergeListItemResponse": {
        "required": [
          "fromKey",
          "fromDisplayName",
          "toKey",
          "toDisplayName",
          "mergedAt"
        ],
        "type": "object",
        "properties": {
          "fromKey": {
            "type": "string"
          },
          "fromDisplayName": {
            "type": "string"
          },
          "toKey": {
            "type": "string"
          },
          "toDisplayName": {
            "type": "string"
          },
          "mergedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PantryMergeRejectionListItemResponse": {
        "required": [
          "keyA",
          "keyADisplayName",
          "keyB",
          "keyBDisplayName",
          "decidedAt"
        ],
        "type": "object",
        "properties": {
          "keyA": {
            "type": "string"
          },
          "keyADisplayName": {
            "type": "string"
          },
          "keyB": {
            "type": "string"
          },
          "keyBDisplayName": {
            "type": "string"
          },
          "decidedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PantryMergeResponse": {
        "required": [
          "fromKey",
          "toKey",
          "item"
        ],
        "type": "object",
        "properties": {
          "fromKey": {
            "type": "string"
          },
          "toKey": {
            "type": "string"
          },
          "item": {
            "$ref": "#/components/schemas/InventoryItemResponse"
          }
        }
      },
      "PantryMergeSignal": {
        "enum": [
          "ContainedTokens",
          "SharedStem"
        ]
      },
      "PantryMergeSource": {
        "enum": [
          "Suggestion",
          "Manual",
          "Chat"
        ],
        "default": "Suggestion"
      },
      "PantryMergeSuggestionResponse": {
        "required": [
          "from",
          "to",
          "signal"
        ],
        "type": "object",
        "properties": {
          "from": {
            "$ref": "#/components/schemas/InventoryItemResponse"
          },
          "to": {
            "$ref": "#/components/schemas/InventoryItemResponse"
          },
          "signal": {
            "$ref": "#/components/schemas/PantryMergeSignal"
          }
        }
      },
      "ParsedLeftoverDinners": {
        "required": [
          "summary",
          "enabled",
          "askedEveryDays"
        ],
        "type": "object",
        "properties": {
          "summary": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "askedEveryDays": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "ParsedPurchaseLine": {
        "required": [
          "rawText",
          "foodKey",
          "displayName",
          "quantity",
          "unit",
          "storageLocation",
          "confidence",
          "needsAttention"
        ],
        "type": "object",
        "properties": {
          "rawText": {
            "type": "string"
          },
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "quantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "storageLocation": {
            "$ref": "#/components/schemas/StorageLocation"
          },
          "confidence": {
            "type": "number",
            "format": "double"
          },
          "needsAttention": {
            "type": "boolean"
          },
          "linePrice": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unitPrice": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unitPriceUnit": {
            "type": [
              "null",
              "string"
            ]
          },
          "asksForDate": {
            "type": "boolean",
            "default": false
          },
          "bestBefore": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "packCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "ParsedRuleProposal": {
        "required": [
          "summary",
          "request"
        ],
        "type": "object",
        "properties": {
          "summary": {
            "type": "string"
          },
          "request": {
            "$ref": "#/components/schemas/CreateMealRuleRequest"
          }
        }
      },
      "ParseRulesRequest": {
        "required": [
          "text"
        ],
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          }
        }
      },
      "ParseRulesResponse": {
        "required": [
          "proposals",
          "leftoverDinners",
          "unheard"
        ],
        "type": "object",
        "properties": {
          "proposals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParsedRuleProposal"
            }
          },
          "leftoverDinners": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ParsedLeftoverDinners"
              }
            ]
          },
          "unheard": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "PartialRecipeResponse": {
        "required": [
          "title",
          "ingredients",
          "steps",
          "missing"
        ],
        "type": "object",
        "properties": {
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "ingredients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeIngredientDto"
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "missing": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeImportMissingPart"
            }
          },
          "translatedFrom": {
            "type": [
              "null",
              "string"
            ]
          },
          "stepSections": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeStepSectionDto"
            }
          },
          "references": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeReferenceDto"
            }
          }
        }
      },
      "PlanCostEstimateResponse": {
        "required": [
          "credits",
          "remaining",
          "affordable",
          "daysToGenerate",
          "usesStarterLibrary",
          "resetsAt"
        ],
        "type": "object",
        "properties": {
          "credits": {
            "type": "integer",
            "format": "int32"
          },
          "remaining": {
            "type": "integer",
            "format": "int32"
          },
          "affordable": {
            "type": "boolean"
          },
          "daysToGenerate": {
            "type": "integer",
            "format": "int32"
          },
          "usesStarterLibrary": {
            "type": "boolean"
          },
          "resetsAt": {
            "type": "string",
            "format": "date-time"
          },
          "suggestMoreCredits": {
            "type": "integer",
            "format": "int32",
            "default": 0
          },
          "preflight": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanPreflightResponse"
              }
            ]
          },
          "affordableNewDishes": {
            "type": "integer",
            "format": "int32",
            "default": 0
          },
          "shortfall": {
            "type": "integer",
            "format": "int32",
            "default": 0
          }
        }
      },
      "PlanDeviationKind": {
        "enum": [
          "BentSoft",
          "NearMiss",
          "LeftOpen",
          "HandPicked"
        ]
      },
      "PlanDeviationOptionResponse": {
        "required": [
          "code"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "date": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "category": {
            "type": [
              "null",
              "string"
            ]
          },
          "ruleId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "PlanDeviationResponse": {
        "required": [
          "kind",
          "rule",
          "ruleId",
          "date",
          "category",
          "target",
          "actual",
          "minutes",
          "reason",
          "options"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/PlanDeviationKind"
          },
          "rule": {
            "$ref": "#/components/schemas/PlanDeviationRule"
          },
          "ruleId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "date": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "category": {
            "type": [
              "null",
              "string"
            ]
          },
          "target": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "actual": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "minutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "reason": {
            "type": "string"
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanDeviationOptionResponse"
            }
          },
          "ingredient": {
            "type": [
              "null",
              "string"
            ]
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          },
          "constraintKind": {
            "type": [
              "null",
              "string"
            ]
          },
          "lockedByHousehold": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "capSpent": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "priceCeiling": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PriceCeiling"
              }
            ]
          }
        }
      },
      "PlanDeviationRule": {
        "enum": [
          "CategoryFrequency",
          "MaximumActiveCookingTime",
          "CalendarCap",
          "CategoryOnDay",
          "CategoryExcludedDays",
          "HardConstraint",
          "BabySafePortion",
          "DietaryPreference",
          "PriceClassOnDays",
          "RepeatAfterDays"
        ]
      },
      "PlanEntryAdaptationResponse": {
        "required": [
          "id",
          "fromFoodKey",
          "fromDisplayName",
          "toFoodKey",
          "toDisplayName",
          "quantity",
          "unit",
          "note"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "fromFoodKey": {
            "type": "string"
          },
          "fromDisplayName": {
            "type": "string"
          },
          "toFoodKey": {
            "type": "string"
          },
          "toDisplayName": {
            "type": "string"
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit": {
            "type": "string"
          },
          "note": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "PlanEntryAdaptationResult": {
        "required": [
          "entry",
          "removedShoppingItemIds",
          "shoppingListChanged"
        ],
        "type": "object",
        "properties": {
          "entry": {
            "$ref": "#/components/schemas/PlanEntryResponse"
          },
          "removedShoppingItemIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "shoppingListChanged": {
            "type": "boolean"
          }
        }
      },
      "PlanEntryFeedbackAction": {
        "enum": [
          "Swapped",
          "Replanned",
          "VariantOffered",
          "Excluded",
          "Noted",
          "NothingToSwapTo"
        ]
      },
      "PlanEntryFeedbackReason": {
        "enum": [
          "RuleBroken",
          "TooMuchOfCategory",
          "ConstraintNotHonoured",
          "TooMuchWork",
          "KidsWontEat",
          "NeverAgain"
        ]
      },
      "PlanEntryFeedbackRequest": {
        "required": [
          "reason"
        ],
        "type": "object",
        "properties": {
          "reason": {
            "$ref": "#/components/schemas/PlanEntryFeedbackReason"
          },
          "text": {
            "type": [
              "null",
              "string"
            ]
          },
          "memberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "PlanEntryFeedbackResponse": {
        "required": [
          "plan",
          "result"
        ],
        "type": "object",
        "properties": {
          "plan": {
            "$ref": "#/components/schemas/WeeklyPlanResponse"
          },
          "result": {
            "$ref": "#/components/schemas/PlanEntryFeedbackResultResponse"
          }
        }
      },
      "PlanEntryFeedbackResultResponse": {
        "required": [
          "reason",
          "action"
        ],
        "type": "object",
        "properties": {
          "reason": {
            "$ref": "#/components/schemas/PlanEntryFeedbackReason"
          },
          "action": {
            "$ref": "#/components/schemas/PlanEntryFeedbackAction"
          },
          "broke": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanEntryFeedbackRuleResponse"
              }
            ]
          },
          "category": {
            "type": [
              "null",
              "string"
            ]
          },
          "categoryCountBefore": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "categoryCountAfter": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "memberNames": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "previousCandidateId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "newCandidateId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "newTitle": {
            "type": [
              "null",
              "string"
            ]
          },
          "newActiveMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "preferenceIds": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "recipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "variantTitle": {
            "type": [
              "null",
              "string"
            ]
          },
          "existingVariantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "PlanEntryFeedbackRuleResponse": {
        "required": [
          "rule"
        ],
        "type": "object",
        "properties": {
          "rule": {
            "$ref": "#/components/schemas/PlanDeviationRule"
          },
          "ruleId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "category": {
            "type": [
              "null",
              "string"
            ]
          },
          "minutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "weekday": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/DayOfWeek"
              }
            ]
          },
          "target": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "actual": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "ingredient": {
            "type": [
              "null",
              "string"
            ]
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          },
          "constraintKind": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "PlanEntryResponse": {
        "required": [
          "id",
          "date",
          "entryType",
          "isLocked",
          "portions",
          "candidate"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "entryType": {
            "$ref": "#/components/schemas/PlanEntryType"
          },
          "isLocked": {
            "type": "boolean"
          },
          "portions": {
            "type": "integer",
            "format": "int32"
          },
          "candidate": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/MealCandidateResponse"
              }
            ]
          },
          "outcome": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/OutcomeStatus"
              }
            ]
          },
          "outcomeReason": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/OutcomeReason"
              }
            ]
          },
          "outcomeFollowedUpAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "leftoverFromDates": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "leftoverPortions": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "lastModifiedByUserId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "lastModifiedByName": {
            "type": [
              "null",
              "string"
            ]
          },
          "lastModifiedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "servingNotes": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/ServingNoteResponse"
            }
          },
          "servingSummaries": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/ServingSummaryResponse"
            }
          },
          "usedTipIndexes": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "integer",
              "format": "int32"
            }
          },
          "servingTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "adaptations": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PlanEntryAdaptationResponse"
            }
          },
          "frozenBabyPortions": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "newFromRun": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "PlanEntryType": {
        "enum": [
          "CookAtHome",
          "EatingOut",
          "NoDinnerPlan",
          "Leftovers"
        ]
      },
      "PlanHeldDish": {
        "required": [
          "date",
          "title"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "title": {
            "type": "string"
          }
        }
      },
      "PlanningConflictDayResponse": {
        "required": [
          "date",
          "reason"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "PlanningConflictResponse": {
        "required": [
          "code",
          "message",
          "relatedDates",
          "suggestedResolution"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "relatedDates": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "suggestedResolution": {
            "type": "string"
          },
          "category": {
            "type": [
              "null",
              "string"
            ]
          },
          "requiredCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "availableCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "ruleMaximum": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "servedCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "dayReasons": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PlanningConflictDayResponse"
            }
          }
        }
      },
      "PlanOverviewDayResponse": {
        "required": [
          "date",
          "hasDinner",
          "entryType"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "hasDinner": {
            "type": "boolean"
          },
          "entryType": {
            "$ref": "#/components/schemas/PlanEntryType"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "PlanOverviewResponse": {
        "required": [
          "weeks"
        ],
        "type": "object",
        "properties": {
          "weeks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanOverviewWeekResponse"
            }
          }
        }
      },
      "PlanOverviewWeekResponse": {
        "required": [
          "weekStart",
          "status",
          "days"
        ],
        "type": "object",
        "properties": {
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "$ref": "#/components/schemas/PlanStatus"
          },
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanOverviewDayResponse"
            }
          },
          "hasShoppingList": {
            "type": "boolean",
            "default": false
          },
          "shoppedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "changedSinceShopped": {
            "type": "integer",
            "format": "int32",
            "default": 0
          }
        }
      },
      "PlanPickedDish": {
        "required": [
          "date"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "candidateId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "recipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "PlanPolicy": {
        "type": "object",
        "properties": {
          "allowGeneration": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "allowNearMisses": {
            "type": "boolean",
            "default": false
          },
          "categoryCaps": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RunCategoryCap"
            }
          },
          "reopenDates": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "lockedDates": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "excludedTitles": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "pickedDishes": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PlanPickedDish"
            }
          },
          "heldDishes": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PlanHeldDish"
            }
          }
        }
      },
      "PlanPreflightBlockedRule": {
        "required": [
          "rule"
        ],
        "type": "object",
        "properties": {
          "rule": {
            "$ref": "#/components/schemas/PlanDeviationRule"
          },
          "category": {
            "type": [
              "null",
              "string"
            ]
          },
          "minutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "priceCeiling": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PriceCeiling"
              }
            ]
          }
        }
      },
      "PlanPreflightDay": {
        "required": [
          "date",
          "state"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "state": {
            "$ref": "#/components/schemas/PlanPreflightDayState"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "category": {
            "type": [
              "null",
              "string"
            ]
          },
          "bend": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanDeviationResponse"
              }
            ]
          },
          "reason": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanPreflightReason"
              }
            ]
          },
          "kept": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "usesUp": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PlanPreflightUseUp"
            }
          }
        }
      },
      "PlanPreflightDayState": {
        "enum": [
          "Library",
          "Generate",
          "NearMiss",
          "StaysOpen",
          "Locked",
          "Answered",
          "Past",
          "EatingOut",
          "Leftovers",
          "Replaced"
        ]
      },
      "PlanPreflightReason": {
        "required": [
          "kind"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/PlanPreflightReasonKind"
          },
          "used": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "cap": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "proposals": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "rules": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PlanPreflightBlockedRule"
            }
          }
        }
      },
      "PlanPreflightReasonKind": {
        "enum": [
          "CapReached",
          "NoCandidateFits",
          "RuleBlocked"
        ]
      },
      "PlanPreflightResponse": {
        "required": [
          "days",
          "nearMissOffer",
          "generationCap",
          "householdCap",
          "alwaysNewDishes"
        ],
        "type": "object",
        "properties": {
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanPreflightDay"
            }
          },
          "nearMissOffer": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanDeviationResponse"
            }
          },
          "generationCap": {
            "type": "integer",
            "format": "int32"
          },
          "householdCap": {
            "type": "integer",
            "format": "int32"
          },
          "alwaysNewDishes": {
            "type": "boolean"
          },
          "nextWeekOnly": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "PlanPreflightUseUp": {
        "required": [
          "foodKey",
          "displayName",
          "bestBefore"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "bestBefore": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "PlanProposalOutcome": {
        "required": [
          "weekStart",
          "from",
          "to",
          "days",
          "ruleCategories"
        ],
        "type": "object",
        "properties": {
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanProposalOutcomeDay"
            }
          },
          "ruleCategories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MealCategory"
            }
          },
          "fromStarterLibrary": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "PlanProposalOutcomeDay": {
        "required": [
          "date",
          "entryType",
          "category"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "entryType": {
            "$ref": "#/components/schemas/PlanEntryType"
          },
          "category": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/MealCategory"
              }
            ]
          }
        }
      },
      "PlanProposalReplaced": {
        "required": [
          "date",
          "entryType",
          "title"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "entryType": {
            "$ref": "#/components/schemas/PlanEntryType"
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "PlanProposalSpan": {
        "required": [
          "weekStart",
          "from",
          "to",
          "replaces"
        ],
        "type": "object",
        "properties": {
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "replaces": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanProposalReplaced"
            }
          },
          "credits": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "preflight": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanPreflightResponse"
              }
            ]
          }
        }
      },
      "PlanRecipeOnDayRequest": {
        "required": [
          "recipeId",
          "date"
        ],
        "type": "object",
        "properties": {
          "recipeId": {
            "type": "string",
            "format": "uuid"
          },
          "date": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "PlanRunAccepted": {
        "required": [
          "jobId",
          "weekStart"
        ],
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "weekStart": {
            "type": "string",
            "format": "date"
          }
        }
      },
      "PlanRunFailureResponse": {
        "required": [
          "status",
          "code"
        ],
        "type": "object",
        "properties": {
          "status": {
            "type": "integer",
            "format": "int32"
          },
          "code": {
            "type": [
              "null",
              "string"
            ]
          },
          "errors": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PlanningConflictResponse"
            }
          },
          "resetsAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "scope": {
            "type": [
              "null",
              "string"
            ]
          },
          "shortfall": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "PlanRunState": {
        "enum": [
          "Queued",
          "Running",
          "Done",
          "Failed"
        ]
      },
      "PlanRunStatusResponse": {
        "required": [
          "jobId",
          "weekStart",
          "status",
          "queuePosition",
          "planId",
          "startedAt"
        ],
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "weekStart": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "$ref": "#/components/schemas/PlanRunState"
          },
          "queuePosition": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "planId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "startedAt": {
            "type": "string",
            "format": "date-time"
          },
          "failure": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanRunFailureResponse"
              }
            ]
          }
        }
      },
      "PlanStatus": {
        "enum": [
          "Draft",
          "Active",
          "Superseded"
        ]
      },
      "PreferenceFit": {
        "enum": [
          "NotAssessed",
          "Follows",
          "NoCandidateFollows",
          "AllergensUnknown"
        ],
        "default": "NotAssessed"
      },
      "PreferenceSentiment": {
        "enum": [
          "Like",
          "Dislike",
          "StrongDislike"
        ]
      },
      "PreferenceSource": {
        "enum": [
          "UserDeclared",
          "Learned"
        ]
      },
      "PreferenceSubjectType": {
        "enum": [
          "Ingredient",
          "Category",
          "Dish",
          "Texture",
          "Preparation",
          "Taste"
        ]
      },
      "PriceCeiling": {
        "enum": [
          "Cheap",
          "CheapOrMid"
        ]
      },
      "PriceCertainty": {
        "enum": [
          "Confirmed",
          "Estimated",
          "Uncertain",
          null
        ]
      },
      "PriceClass": {
        "enum": [
          "Cheap",
          "Mid",
          "Expensive"
        ]
      },
      "PriceSource": {
        "enum": [
          "Observed",
          "Catalogue",
          "Table",
          "Unknown"
        ]
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "instance": {
            "type": [
              "null",
              "string"
            ]
          },
          "code": {
            "type": [
              "null",
              "string"
            ],
            "description": "What was refused, as a stable machine-readable name — the one part of a problem body meant to be branched on. `detail` is English written for a log; the apps look the reader's sentence up by this instead. Which codes an operation can answer with is on the operation as `x-problem-codes`; the ones the middleware and the exception handlers can write on any operation are on the document under the same key. Absent on a refusal that says nothing the status did not."
          }
        }
      },
      "ProductPreferenceResponse": {
        "required": [
          "foodKey",
          "displayName",
          "chainCode",
          "storeName",
          "productName",
          "chosenAt"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "chainCode": {
            "type": "string"
          },
          "storeName": {
            "type": [
              "null",
              "string"
            ]
          },
          "productName": {
            "type": "string"
          },
          "chosenAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProductSearchHitResponse": {
        "required": [
          "productName",
          "price",
          "unitPrice",
          "unitPriceUnit"
        ],
        "type": "object",
        "properties": {
          "productName": {
            "type": "string"
          },
          "price": {
            "type": "number",
            "format": "double"
          },
          "unitPrice": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unitPriceUnit": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ProductSearchResponse": {
        "required": [
          "searchAvailable",
          "stores"
        ],
        "type": "object",
        "properties": {
          "searchAvailable": {
            "type": "boolean"
          },
          "stores": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSearchStoreResponse"
            }
          }
        }
      },
      "ProductSearchStoreResponse": {
        "required": [
          "chainCode",
          "storeName",
          "products"
        ],
        "type": "object",
        "properties": {
          "chainCode": {
            "type": "string"
          },
          "storeName": {
            "type": "string"
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSearchHitResponse"
            }
          }
        }
      },
      "ProductStepResponse": {
        "required": [
          "productName",
          "novaGroup"
        ],
        "type": "object",
        "properties": {
          "productName": {
            "type": "string"
          },
          "novaGroup": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "ProposalStatus": {
        "enum": [
          "Proposed",
          "Applied",
          "Rejected",
          "Failed",
          "Running"
        ]
      },
      "PurchaseClassificationExampleResponse": {
        "required": [
          "productName",
          "novaGroup",
          "nutriScore"
        ],
        "type": "object",
        "properties": {
          "productName": {
            "type": "string"
          },
          "novaGroup": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "nutriScore": {
            "type": [
              "null",
              "string"
            ]
          },
          "sugarsPer100g": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "saltPer100g": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "offProductName": {
            "type": [
              "null",
              "string"
            ]
          },
          "brands": {
            "type": [
              "null",
              "string"
            ]
          },
          "quantity": {
            "type": [
              "null",
              "string"
            ]
          },
          "novaReasons": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PurchaseNovaReasonResponse"
            }
          }
        }
      },
      "PurchaseClassificationResponse": {
        "required": [
          "days",
          "linesTotal",
          "linesRecognised",
          "heavilyProcessed",
          "processed",
          "lightlyProcessed",
          "processingUnknown",
          "examples"
        ],
        "type": "object",
        "properties": {
          "days": {
            "type": "integer",
            "format": "int32"
          },
          "linesTotal": {
            "type": "integer",
            "format": "int32"
          },
          "linesRecognised": {
            "type": "integer",
            "format": "int32"
          },
          "heavilyProcessed": {
            "type": "integer",
            "format": "int32"
          },
          "processed": {
            "type": "integer",
            "format": "int32"
          },
          "lightlyProcessed": {
            "type": "integer",
            "format": "int32"
          },
          "processingUnknown": {
            "type": "integer",
            "format": "int32"
          },
          "examples": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PurchaseClassificationExampleResponse"
            }
          }
        }
      },
      "PurchaseDocumentPage": {
        "required": [
          "fileName",
          "contentType",
          "base64Data"
        ],
        "type": "object",
        "properties": {
          "fileName": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "base64Data": {
            "type": "string"
          }
        }
      },
      "PurchaseImportResponse": {
        "required": [
          "id",
          "householdId",
          "fileName",
          "documentKind",
          "purpose",
          "snapshotLocation",
          "status",
          "lines",
          "error",
          "createdAt",
          "confirmedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "fileName": {
            "type": "string"
          },
          "documentKind": {
            "$ref": "#/components/schemas/ImportDocumentKind"
          },
          "purpose": {
            "$ref": "#/components/schemas/ImportPurpose"
          },
          "snapshotLocation": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/StorageLocation"
              }
            ]
          },
          "status": {
            "$ref": "#/components/schemas/ImportStatus"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ParsedPurchaseLine"
            }
          },
          "error": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "confirmedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "storeName": {
            "type": [
              "null",
              "string"
            ]
          },
          "chainCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "purchasedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "total": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          }
        }
      },
      "PurchaseNovaReasonResponse": {
        "required": [
          "kind",
          "code",
          "additiveClass",
          "text"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/NovaReasonKind"
          },
          "code": {
            "type": [
              "null",
              "string"
            ]
          },
          "additiveClass": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/NovaAdditiveClass"
              }
            ]
          },
          "text": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "PushDeviceResponse": {
        "required": [
          "token",
          "platform",
          "provider",
          "listChanges",
          "householdNews",
          "runDone",
          "reminders",
          "lastSeenAt"
        ],
        "type": "object",
        "properties": {
          "token": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "listChanges": {
            "type": "boolean"
          },
          "householdNews": {
            "type": "boolean"
          },
          "runDone": {
            "type": "boolean"
          },
          "reminders": {
            "type": "boolean"
          },
          "lastSeenAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PutHouseholdStoresRequest": {
        "required": [
          "stores"
        ],
        "type": "object",
        "properties": {
          "stores": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StoreSelection"
            }
          }
        }
      },
      "RecipeBookSource": {
        "enum": [
          "All",
          "Own",
          "Library",
          null
        ]
      },
      "RecipeCategoryFacet": {
        "required": [
          "category",
          "count"
        ],
        "type": "object",
        "properties": {
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "count": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "RecipeChange": {
        "required": [
          "field",
          "detail"
        ],
        "type": "object",
        "properties": {
          "field": {
            "$ref": "#/components/schemas/RecipeChangeField"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "RecipeChangeField": {
        "enum": [
          "Title",
          "Category",
          "Ingredients",
          "Steps",
          "Tags",
          "Times",
          "Portions",
          "Sections",
          "References",
          "Base",
          "Dinner"
        ]
      },
      "RecipeFacetsResponse": {
        "required": [
          "total",
          "categories",
          "times",
          "sources",
          "favourites"
        ],
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "format": "int32"
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeCategoryFacet"
            }
          },
          "times": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeTimeFacet"
            }
          },
          "sources": {
            "$ref": "#/components/schemas/RecipeSourceFacets"
          },
          "favourites": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "RecipeFitConflictResponse": {
        "required": [
          "foodKey",
          "displayName",
          "isOptional",
          "constraint",
          "constraintKind",
          "memberNames",
          "substituteFoodKey",
          "substituteDisplayName",
          "removed",
          "note"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "isOptional": {
            "type": "boolean"
          },
          "constraint": {
            "type": "string"
          },
          "constraintKind": {
            "type": "string"
          },
          "memberNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "substituteFoodKey": {
            "type": [
              "null",
              "string"
            ]
          },
          "substituteDisplayName": {
            "type": [
              "null",
              "string"
            ]
          },
          "removed": {
            "type": "boolean"
          },
          "note": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "RecipeFitResponse": {
        "required": [
          "conflicts",
          "canMakeVariant",
          "variantTitle",
          "existingVariantId"
        ],
        "type": "object",
        "properties": {
          "conflicts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeFitConflictResponse"
            }
          },
          "canMakeVariant": {
            "type": "boolean"
          },
          "variantTitle": {
            "type": [
              "null",
              "string"
            ]
          },
          "existingVariantId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "softMatches": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeFitSoftMatchResponse"
            }
          }
        }
      },
      "RecipeFitSoftMatchResponse": {
        "required": [
          "foodKey",
          "displayName",
          "constraint",
          "memberNames"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "constraint": {
            "type": "string"
          },
          "memberNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "RecipeImportFailure": {
        "enum": [
          "FetchFailed",
          "PageBlocked",
          "Timeout",
          "NotAPage",
          "NoRecipeFound",
          "PhotoUnreadable",
          "PhotoNoRecipe",
          "ReadFailed",
          "SaveFailed",
          "ProviderUnavailable",
          "Other",
          null
        ]
      },
      "RecipeImportMissingPart": {
        "enum": [
          "Title",
          "Ingredients",
          "Steps"
        ]
      },
      "RecipeImportResponse": {
        "required": [
          "id",
          "status",
          "recipe",
          "error",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/RecipeImportStatus"
          },
          "recipe": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RecipeResponse"
              }
            ]
          },
          "error": {
            "type": [
              "null",
              "string"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "translatedFrom": {
            "type": [
              "null",
              "string"
            ]
          },
          "failure": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RecipeImportFailure"
              }
            ]
          },
          "partial": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PartialRecipeResponse"
              }
            ]
          },
          "linkSuggestions": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/ReferenceLinkSuggestion"
            }
          },
          "hostSuggestions": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/HostLinkSuggestion"
            }
          },
          "linkedHostRecipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "dishPhoto": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/DishPhotoBox"
              }
            ]
          },
          "page": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/RecipePageReading"
              }
            ]
          }
        }
      },
      "RecipeImportStatus": {
        "enum": [
          "Saved",
          "AlreadyExists",
          "Failed",
          "Partial",
          "Pending"
        ]
      },
      "RecipeIngredientDto": {
        "required": [
          "foodKey",
          "displayName",
          "quantity",
          "unit",
          "isOptional"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "quantity": {
            "type": "number",
            "format": "double"
          },
          "unit": {
            "type": "string"
          },
          "isOptional": {
            "type": "boolean"
          },
          "functionTags": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "displayQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "displayUnit": {
            "type": [
              "null",
              "string"
            ]
          },
          "section": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "RecipeLibrarySort": {
        "enum": [
          "Default",
          "Title",
          "Quickest",
          null
        ]
      },
      "RecipeLinkResponse": {
        "required": [
          "id",
          "title"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          }
        }
      },
      "RecipeListItemResponse": {
        "required": [
          "id",
          "title",
          "category",
          "source",
          "tags",
          "activeMinutes",
          "totalMinutes",
          "basePortions",
          "isFamilyFavorite",
          "favoriteMemberIds"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "source": {
            "$ref": "#/components/schemas/RecipeSource"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "activeMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "totalMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "basePortions": {
            "type": "integer",
            "format": "int32"
          },
          "isFamilyFavorite": {
            "type": "boolean"
          },
          "favoriteMemberIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "sourceUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "sourceName": {
            "type": [
              "null",
              "string"
            ]
          },
          "license": {
            "type": [
              "null",
              "string"
            ]
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "timesCooked": {
            "type": "integer",
            "format": "int32",
            "default": 0
          },
          "lastCookedOn": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "excludedFromPlanning": {
            "type": "boolean",
            "default": false
          },
          "isBase": {
            "type": "boolean",
            "default": false
          },
          "effectiveActiveMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "effectiveTotalMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "isDinner": {
            "type": "boolean",
            "default": true
          },
          "attribution": {
            "type": [
              "null",
              "string"
            ]
          },
          "isCore": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "RecipeListSort": {
        "enum": [
          "Title",
          "ActiveMinutes",
          null
        ]
      },
      "RecipePageChoice": {
        "enum": [
          "OneDish",
          "Only",
          "All"
        ]
      },
      "RecipePageChoiceResponse": {
        "required": [
          "recipes"
        ],
        "type": "object",
        "properties": {
          "recipes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeResponse"
            }
          }
        }
      },
      "RecipePageQuestionResponse": {
        "required": [
          "importId",
          "page"
        ],
        "type": "object",
        "properties": {
          "importId": {
            "type": "string",
            "format": "uuid"
          },
          "page": {
            "$ref": "#/components/schemas/RecipePageReading"
          }
        }
      },
      "RecipePageReading": {
        "required": [
          "relation",
          "reason",
          "titles",
          "oneDishTitle",
          "savedAsOneDish"
        ],
        "type": "object",
        "properties": {
          "relation": {
            "$ref": "#/components/schemas/RecipePageRelation"
          },
          "reason": {
            "type": [
              "null",
              "string"
            ]
          },
          "titles": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "oneDishTitle": {
            "type": "string"
          },
          "savedAsOneDish": {
            "type": "boolean"
          }
        }
      },
      "RecipePageRelation": {
        "enum": [
          "PartsOfOne",
          "Separate",
          "Unsure"
        ]
      },
      "RecipePhotoPage": {
        "required": [
          "fileName",
          "contentType",
          "base64Data"
        ],
        "type": "object",
        "properties": {
          "fileName": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "base64Data": {
            "type": "string"
          }
        }
      },
      "RecipeReferenceDto": {
        "required": [
          "id",
          "label",
          "sourceHint",
          "section",
          "position",
          "linkedRecipeId"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "sourceHint": {
            "type": [
              "null",
              "string"
            ]
          },
          "section": {
            "type": [
              "null",
              "string"
            ]
          },
          "position": {
            "type": "integer",
            "format": "int32"
          },
          "linkedRecipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "linkedRecipeTitle": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "RecipeResponse": {
        "required": [
          "id",
          "title",
          "category",
          "source",
          "ingredients",
          "steps",
          "tags",
          "activeMinutes",
          "totalMinutes",
          "basePortions",
          "isFamilyFavorite",
          "favoriteMemberIds"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "source": {
            "$ref": "#/components/schemas/RecipeSource"
          },
          "ingredients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeIngredientDto"
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "activeMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "totalMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "basePortions": {
            "type": "integer",
            "format": "int32"
          },
          "isFamilyFavorite": {
            "type": "boolean"
          },
          "favoriteMemberIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "sourceUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "sourceName": {
            "type": [
              "null",
              "string"
            ]
          },
          "license": {
            "type": [
              "null",
              "string"
            ]
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "timesCooked": {
            "type": "integer",
            "format": "int32",
            "default": 0
          },
          "lastCookedOn": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "timers": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CandidateStepTimer"
            }
          },
          "excludedFromPlanning": {
            "type": "boolean",
            "default": false
          },
          "isBase": {
            "type": "boolean",
            "default": false
          },
          "stepSections": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeStepSectionDto"
            }
          },
          "references": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeReferenceDto"
            }
          },
          "sections": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeSectionResponse"
            }
          },
          "effectiveActiveMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "effectiveTotalMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "isDinner": {
            "type": "boolean",
            "default": true
          },
          "attribution": {
            "type": [
              "null",
              "string"
            ]
          },
          "translatedFrom": {
            "type": [
              "null",
              "string"
            ]
          },
          "imageLicense": {
            "type": [
              "null",
              "string"
            ]
          },
          "imageAuthor": {
            "type": [
              "null",
              "string"
            ]
          },
          "imageSourceUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "isCore": {
            "type": "boolean",
            "default": false
          },
          "usedBy": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeLinkResponse"
            }
          }
        }
      },
      "RecipeRevisionResponse": {
        "required": [
          "version",
          "changedAt",
          "changes"
        ],
        "type": "object",
        "properties": {
          "version": {
            "type": "integer",
            "format": "int32"
          },
          "changedAt": {
            "type": "string",
            "format": "date-time"
          },
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeChange"
            }
          }
        }
      },
      "RecipeSectionKind": {
        "enum": [
          "Own",
          "Linked",
          "Unresolved"
        ]
      },
      "RecipeSectionResponse": {
        "required": [
          "name",
          "kind",
          "ingredients",
          "steps",
          "timers"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "kind": {
            "$ref": "#/components/schemas/RecipeSectionKind"
          },
          "ingredients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeIngredientDto"
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "timers": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CandidateStepTimer"
            }
          },
          "sourceRecipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "sourceRecipeTitle": {
            "type": [
              "null",
              "string"
            ]
          },
          "referenceId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "sourceHint": {
            "type": [
              "null",
              "string"
            ]
          },
          "sourceActiveMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "sourceTotalMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "RecipeSource": {
        "enum": [
          "Seed",
          "Generated",
          "User"
        ]
      },
      "RecipeSourceFacets": {
        "required": [
          "all",
          "own",
          "library"
        ],
        "type": "object",
        "properties": {
          "all": {
            "type": "integer",
            "format": "int32"
          },
          "own": {
            "type": "integer",
            "format": "int32"
          },
          "library": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "RecipeStepSectionDto": {
        "required": [
          "name",
          "fromStep"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "fromStep": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "RecipeTimeFacet": {
        "required": [
          "maxTotalMinutes",
          "count"
        ],
        "type": "object",
        "properties": {
          "maxTotalMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "count": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "RecipeTipResponse": {
        "required": [
          "id",
          "text",
          "kind"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          }
        }
      },
      "RecipeTipsResponse": {
        "required": [
          "tips"
        ],
        "type": "object",
        "properties": {
          "tips": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeTipResponse"
            }
          }
        }
      },
      "RecipeVariantResponse": {
        "required": [
          "recipe",
          "alreadyExisted",
          "plan"
        ],
        "type": "object",
        "properties": {
          "recipe": {
            "$ref": "#/components/schemas/RecipeResponse"
          },
          "alreadyExisted": {
            "type": "boolean"
          },
          "plan": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/WeeklyPlanResponse"
              }
            ]
          }
        }
      },
      "RecordOutcomeRequest": {
        "required": [
          "status",
          "reason",
          "notes",
          "leftovers"
        ],
        "type": "object",
        "properties": {
          "status": {
            "$ref": "#/components/schemas/OutcomeStatus"
          },
          "reason": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/OutcomeReason"
              }
            ]
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "leftovers": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/LeftoverRequest"
            }
          },
          "source": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/OutcomeSource"
              }
            ]
          },
          "frozenBabyPortions": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          }
        }
      },
      "RecordUpgradePromptRequest": {
        "required": [
          "moment",
          "action"
        ],
        "type": "object",
        "properties": {
          "moment": {
            "$ref": "#/components/schemas/UpgradeMoment"
          },
          "action": {
            "$ref": "#/components/schemas/UpgradePromptAction"
          }
        }
      },
      "RedirectResponse": {
        "required": [
          "url"
        ],
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          }
        }
      },
      "ReferenceCandidateKind": {
        "enum": [
          "Suggestions",
          "Bases",
          "All",
          null
        ]
      },
      "ReferenceLinkCandidateResponse": {
        "required": [
          "recipeId",
          "title",
          "isBase",
          "titleMatches"
        ],
        "type": "object",
        "properties": {
          "recipeId": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "isBase": {
            "type": "boolean"
          },
          "titleMatches": {
            "type": "boolean"
          }
        }
      },
      "ReferenceLinkSuggestion": {
        "required": [
          "referenceId",
          "label",
          "recipeId",
          "recipeTitle",
          "isBase"
        ],
        "type": "object",
        "properties": {
          "referenceId": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "recipeId": {
            "type": "string",
            "format": "uuid"
          },
          "recipeTitle": {
            "type": "string"
          },
          "isBase": {
            "type": "boolean"
          }
        }
      },
      "ReferralCodeResponse": {
        "required": [
          "code",
          "maxUses",
          "uses",
          "expiresAt"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "maxUses": {
            "type": "integer",
            "format": "int32"
          },
          "uses": {
            "type": "integer",
            "format": "int32"
          },
          "expiresAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          }
        }
      },
      "RegisterPushDeviceRequest": {
        "required": [
          "token",
          "platform",
          "provider"
        ],
        "type": "object",
        "properties": {
          "token": {
            "maxLength": 200,
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "provider": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "RegisterRequest": {
        "required": [
          "email",
          "password",
          "displayName"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "password": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          }
        }
      },
      "RejectPantryMergeRequest": {
        "required": [
          "keyA",
          "keyB"
        ],
        "type": "object",
        "properties": {
          "keyA": {
            "type": "string"
          },
          "keyB": {
            "type": "string"
          }
        }
      },
      "ReplacePlanEntryRequest": {
        "type": "object",
        "properties": {
          "mealCandidateId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "entryType": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PlanEntryType"
              }
            ]
          }
        }
      },
      "ReplanScope": {
        "enum": [
          "OpenDays",
          "WholeWeek",
          null
        ]
      },
      "ReportChatContentRequest": {
        "type": "object",
        "properties": {
          "messageId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "reason": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ChatReportReason"
              }
            ]
          },
          "note": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ResetPasswordRequest": {
        "required": [
          "email",
          "token",
          "newPassword"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "token": {
            "type": "string"
          },
          "newPassword": {
            "type": "string"
          }
        }
      },
      "ReverseGeocodeResponse": {
        "required": [
          "address"
        ],
        "type": "object",
        "properties": {
          "address": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "RuleScope": {
        "enum": [
          "Permanent",
          "DateRange",
          "SpecificDate"
        ]
      },
      "RuleSource": {
        "enum": [
          "UserDeclared",
          "SystemDefault",
          "Learned"
        ]
      },
      "RuleStrength": {
        "enum": [
          "Hard",
          "Soft"
        ]
      },
      "RunCategoryCap": {
        "required": [
          "category",
          "maxPerWeek"
        ],
        "type": "object",
        "properties": {
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "maxPerWeek": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "SaveRecipeRequest": {
        "required": [
          "title",
          "category",
          "ingredients",
          "steps",
          "tags",
          "activeMinutes",
          "totalMinutes",
          "basePortions"
        ],
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/MealCategory"
          },
          "ingredients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipeIngredientDto"
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "activeMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "totalMinutes": {
            "type": "integer",
            "format": "int32"
          },
          "basePortions": {
            "type": "integer",
            "format": "int32"
          },
          "timers": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/CandidateStepTimer"
            }
          },
          "stepSections": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeStepSectionDto"
            }
          },
          "references": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/RecipeReferenceDto"
            }
          },
          "isBase": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "isDinner": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "SendChatMessageRequest": {
        "required": [
          "content"
        ],
        "type": "object",
        "properties": {
          "content": {
            "type": "string"
          },
          "continuation": {
            "type": "boolean",
            "default": false
          },
          "replacesMessageId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "SendChatMessageResponse": {
        "required": [
          "userMessage",
          "assistantMessage",
          "proposals"
        ],
        "type": "object",
        "properties": {
          "userMessage": {
            "$ref": "#/components/schemas/ChatMessageResponse"
          },
          "assistantMessage": {
            "$ref": "#/components/schemas/ChatMessageResponse"
          },
          "proposals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ActionProposalResponse"
            }
          }
        }
      },
      "ServingAudienceKind": {
        "enum": [
          "Pot",
          "Member",
          "Baby",
          "Guests"
        ]
      },
      "ServingNoteResponse": {
        "required": [
          "kind",
          "source",
          "code",
          "text",
          "tags"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/ServingAudienceKind"
          },
          "source": {
            "$ref": "#/components/schemas/ServingNoteSource"
          },
          "code": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "memberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          },
          "stepIndex": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "tipIndex": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "tipFoods": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "rowText": {
            "type": [
              "null",
              "string"
            ]
          },
          "lappKind": {
            "type": [
              "null",
              "string"
            ]
          },
          "action": {
            "type": [
              "null",
              "string"
            ]
          },
          "why": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ServingNoteSource": {
        "enum": [
          "Engine",
          "Model"
        ]
      },
      "ServingSummaryResponse": {
        "required": [
          "kind",
          "memberId",
          "memberName",
          "label",
          "text",
          "fit",
          "clauses"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/ServingAudienceKind"
          },
          "memberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          },
          "label": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "fit": {
            "$ref": "#/components/schemas/BabyPortionFit"
          },
          "clauses": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "short": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "SetDinnerTimeRequest": {
        "required": [
          "time"
        ],
        "type": "object",
        "properties": {
          "time": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": "string"
          },
          "weekdays": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/Weekday"
            }
          }
        }
      },
      "SetDishWishVoteRequest": {
        "required": [
          "vote"
        ],
        "type": "object",
        "properties": {
          "vote": {
            "type": "boolean"
          },
          "forMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "SetFrozenBabyPortionsRequest": {
        "required": [
          "portions"
        ],
        "type": "object",
        "properties": {
          "portions": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "SetOffFoodKeyRequest": {
        "required": [
          "foodKey"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          }
        }
      },
      "SetOffFoodKeyResponse": {
        "required": [
          "barcode",
          "foodKey",
          "written"
        ],
        "type": "object",
        "properties": {
          "barcode": {
            "type": "string"
          },
          "foodKey": {
            "type": "string"
          },
          "written": {
            "type": "boolean"
          }
        }
      },
      "SetProductPreferenceRequest": {
        "required": [
          "chainCode",
          "productName"
        ],
        "type": "object",
        "properties": {
          "chainCode": {
            "maxLength": 50,
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "productName": {
            "maxLength": 300,
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          }
        }
      },
      "SetRecipeExclusionRequest": {
        "required": [
          "excluded"
        ],
        "type": "object",
        "properties": {
          "excluded": {
            "type": "boolean"
          }
        }
      },
      "SetRecipeFavoriteRequest": {
        "required": [
          "householdMemberId",
          "favorite"
        ],
        "type": "object",
        "properties": {
          "householdMemberId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "favorite": {
            "type": "boolean"
          }
        }
      },
      "SetRecipeImageRequest": {
        "required": [
          "imageBase64",
          "contentType"
        ],
        "type": "object",
        "properties": {
          "imageBase64": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          }
        }
      },
      "SetReferenceLinkRequest": {
        "required": [
          "linkedRecipeId"
        ],
        "type": "object",
        "properties": {
          "linkedRecipeId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          }
        }
      },
      "SetServingTipUsedRequest": {
        "required": [
          "used"
        ],
        "type": "object",
        "properties": {
          "used": {
            "type": "boolean"
          }
        }
      },
      "SetUnitWeightRequest": {
        "required": [
          "grams"
        ],
        "type": "object",
        "properties": {
          "grams": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "ShoppingAisle": {
        "enum": [
          "Produce",
          "Bakery",
          "MeatAndFish",
          "Dairy",
          "Frozen",
          "DryGoods",
          "Household",
          "Other"
        ],
        "default": "Other"
      },
      "ShoppingItemReason": {
        "enum": [
          "OptionalOnly",
          "PartiallyAtHome",
          "StaleEvidence",
          "UnlikelyAtHome",
          "NothingAtHome",
          "RecentlyDepleted",
          "Unmeasured",
          "NoLongerPlanned",
          "Staple",
          "FullyAtHome"
        ]
      },
      "ShoppingItemStatus": {
        "enum": [
          "Buy",
          "CheckAtHome",
          "Optional",
          "Checked",
          "Purchased",
          "AtHome"
        ]
      },
      "ShoppingListItemResponse": {
        "required": [
          "id",
          "foodKey",
          "displayName",
          "requiredQuantity",
          "unit",
          "status",
          "purchasedQuantity",
          "reasonCode",
          "haveQuantity",
          "relatedPlanEntryIds"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "foodKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "requiredQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "unit": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/ShoppingItemStatus"
          },
          "purchasedQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "reasonCode": {
            "$ref": "#/components/schemas/ShoppingItemReason"
          },
          "haveQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "relatedPlanEntryIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "suggestedStoreName": {
            "type": [
              "null",
              "string"
            ]
          },
          "suggestedChainCode": {
            "type": [
              "null",
              "string"
            ]
          },
          "suggestedPrice": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "suggestedProductName": {
            "type": [
              "null",
              "string"
            ]
          },
          "suggestedAlternatives": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/SuggestedAlternativeResponse"
            }
          },
          "suggestedPriceCertainty": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PriceCertainty"
              }
            ]
          },
          "addedAfterShopping": {
            "type": "boolean",
            "default": false
          },
          "displayQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "displayUnit": {
            "type": [
              "null",
              "string"
            ]
          },
          "suggestedPackage": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/PackageSuggestionResponse"
              }
            ]
          },
          "suggestedPriceObservedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "suggestedPriceIsAggregate": {
            "type": "boolean",
            "default": false
          },
          "suggestedPriceSource": {
            "$ref": "#/components/schemas/SuggestedPriceSource"
          },
          "suggestedPriceHouseholdCount": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "aisle": {
            "$ref": "#/components/schemas/ShoppingAisle"
          },
          "isManual": {
            "type": "boolean",
            "default": false
          },
          "lastSeenAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "householdChoice": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/HouseholdChoiceResponse"
              }
            ]
          },
          "preferenceFit": {
            "$ref": "#/components/schemas/PreferenceFit"
          },
          "dearerThanCheapest": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "suggestedSugarsPer100g": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "suggestedSaltPer100g": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "tracesDropped": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/TracesDroppedResponse"
              }
            ]
          },
          "haveUnit": {
            "type": [
              "null",
              "string"
            ]
          },
          "purchasedAsQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "purchasedAsUnit": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "ShoppingListResponse": {
        "required": [
          "id",
          "householdId",
          "weeklyPlanId",
          "generatedAt",
          "items"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "weeklyPlanId": {
            "type": "string",
            "format": "uuid"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ShoppingListItemResponse"
            }
          },
          "basketEstimate": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/BasketEstimateResponse"
              }
            ]
          },
          "completedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "tripSummary": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/ShoppingTripSummaryResponse"
              }
            ]
          },
          "sharingOff": {
            "type": "boolean",
            "default": false
          },
          "skippedReferences": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/SkippedReferenceResponse"
            }
          },
          "priceCatalogueOn": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "ShoppingTripSummaryResponse": {
        "required": [
          "stockedItemCount",
          "skippedItemCount"
        ],
        "type": "object",
        "properties": {
          "stockedItemCount": {
            "type": "integer",
            "format": "int32"
          },
          "skippedItemCount": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "SkippedReferenceResponse": {
        "required": [
          "referenceId",
          "label",
          "sourceHint"
        ],
        "type": "object",
        "properties": {
          "referenceId": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "sourceHint": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "StaleReason": {
        "enum": [
          "Guests",
          "Household",
          "Mixed",
          null
        ]
      },
      "StartCheckoutRequest": {
        "required": [
          "planCode",
          "returnUrl"
        ],
        "type": "object",
        "properties": {
          "planCode": {
            "type": "string"
          },
          "returnUrl": {
            "type": "string"
          }
        }
      },
      "StopChatTurnResponse": {
        "required": [
          "stopped"
        ],
        "type": "object",
        "properties": {
          "stopped": {
            "type": "boolean"
          }
        }
      },
      "StorageLocation": {
        "enum": [
          "Fridge",
          "Freezer",
          "Pantry",
          "Unknown"
        ]
      },
      "StoreBasketEstimateResponse": {
        "required": [
          "chainCode",
          "storeName",
          "total",
          "pricedItems",
          "estimatedItems"
        ],
        "type": "object",
        "properties": {
          "chainCode": {
            "type": "string"
          },
          "storeName": {
            "type": "string"
          },
          "total": {
            "type": "number",
            "format": "double"
          },
          "pricedItems": {
            "type": "integer",
            "format": "int32"
          },
          "estimatedItems": {
            "type": "integer",
            "format": "int32"
          },
          "seenItems": {
            "type": "integer",
            "format": "int32",
            "default": 0
          },
          "totalItems": {
            "type": "integer",
            "format": "int32",
            "default": 0
          },
          "coverage": {
            "$ref": "#/components/schemas/StoreCoverage"
          }
        }
      },
      "StoreCoverage": {
        "enum": [
          "Unknown",
          "Full",
          "Partial"
        ],
        "default": "Unknown"
      },
      "StoreLinksResponse": {
        "required": [
          "ios",
          "android"
        ],
        "type": "object",
        "properties": {
          "ios": {
            "type": [
              "null",
              "string"
            ]
          },
          "android": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "StoreSelection": {
        "required": [
          "externalStoreId",
          "chainCode",
          "name",
          "address"
        ],
        "type": "object",
        "properties": {
          "externalStoreId": {
            "type": [
              "null",
              "string"
            ]
          },
          "chainCode": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "SubmitFeedbackRequest": {
        "required": [
          "message",
          "client"
        ],
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          },
          "client": {
            "$ref": "#/components/schemas/FeedbackClientInfo"
          },
          "householdId": {
            "type": [
              "null",
              "string"
            ],
            "format": "uuid"
          },
          "route": {
            "type": [
              "null",
              "string"
            ]
          },
          "recentFailures": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/FeedbackApiFailure"
            }
          },
          "sentryEventId": {
            "type": [
              "null",
              "string"
            ]
          },
          "screenshotBase64": {
            "type": [
              "null",
              "string"
            ]
          },
          "screenshotContentType": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "SuggestedAlternativeResponse": {
        "required": [
          "storeName",
          "chainCode",
          "price",
          "productName"
        ],
        "type": "object",
        "properties": {
          "storeName": {
            "type": "string"
          },
          "chainCode": {
            "type": "string"
          },
          "price": {
            "type": "number",
            "format": "double"
          },
          "productName": {
            "type": "string"
          }
        }
      },
      "SuggestedPriceSource": {
        "enum": [
          "None",
          "OwnReceipt",
          "HouseholdAverage",
          "Catalogue",
          "IndexEstimate"
        ],
        "default": "None"
      },
      "SyncDeviceCalendarRequest": {
        "required": [
          "deviceKey",
          "name",
          "events"
        ],
        "type": "object",
        "properties": {
          "deviceKey": {
            "type": "string"
          },
          "name": {
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeviceCalendarEvent"
            }
          }
        }
      },
      "TimeZonesResponse": {
        "required": [
          "ids"
        ],
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "TracesDroppedResponse": {
        "required": [
          "allergen",
          "memberName"
        ],
        "type": "object",
        "properties": {
          "allergen": {
            "type": "string"
          },
          "memberName": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UnitWeightResponse": {
        "required": [
          "foodKey",
          "unit",
          "displayName",
          "grams",
          "updatedAt"
        ],
        "type": "object",
        "properties": {
          "foodKey": {
            "type": "string"
          },
          "unit": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "grams": {
            "type": "number",
            "format": "double"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UpcomingCalendarEventsResponse": {
        "required": [
          "feeds",
          "from",
          "to",
          "busyEvenings"
        ],
        "type": "object",
        "properties": {
          "feeds": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CalendarFeedEventsResponse"
            }
          },
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "busyEvenings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BusyEveningResponse"
            }
          }
        }
      },
      "UpdateChatPermissionModeRequest": {
        "required": [
          "permissionMode"
        ],
        "type": "object",
        "properties": {
          "permissionMode": {
            "$ref": "#/components/schemas/ChatPermissionMode"
          }
        }
      },
      "UpdateDietaryConstraintRequest": {
        "required": [
          "kind",
          "foodOrCategory",
          "severity",
          "notes"
        ],
        "type": "object",
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/DietaryConstraintKind"
          },
          "foodOrCategory": {
            "type": "string"
          },
          "severity": {
            "$ref": "#/components/schemas/ConstraintSeverity"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UpdateFoodPreferenceRequest": {
        "required": [
          "subjectType",
          "subject",
          "sentiment"
        ],
        "type": "object",
        "properties": {
          "subjectType": {
            "$ref": "#/components/schemas/PreferenceSubjectType"
          },
          "subject": {
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          },
          "sentiment": {
            "$ref": "#/components/schemas/PreferenceSentiment"
          }
        }
      },
      "UpdateHouseholdMemberRequest": {
        "required": [
          "name",
          "birthDate",
          "defaultDinnerParticipant",
          "isActive"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "birthDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "defaultDinnerParticipant": {
            "type": "boolean"
          },
          "isActive": {
            "type": "boolean"
          },
          "isBreastfed": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "isGuest": {
            "type": "boolean",
            "default": false
          },
          "portionFactor": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "nutritionOverrides": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/NutritionOverridesDto"
              }
            ]
          },
          "isPregnant": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "safetyAdviceOnly": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdateHouseholdRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ]
          },
          "timeZoneId": {
            "type": [
              "null",
              "string"
            ]
          },
          "defaultDinnerTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "preferredLanguage": {
            "type": [
              "null",
              "string"
            ]
          },
          "country": {
            "type": [
              "null",
              "string"
            ]
          },
          "preferOwnRecipes": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "prefersLeftoverDinners": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "maxNewDishesPerWeek": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "alwaysNewDishes": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "pantryWeight": {
            "type": [
              "null",
              "string"
            ]
          },
          "address": {
            "type": [
              "null",
              "string"
            ]
          },
          "loyaltyPrograms": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string"
            }
          },
          "measurementSystem": {
            "type": [
              "null",
              "string"
            ]
          },
          "dryGoodsMeasure": {
            "type": [
              "null",
              "string"
            ]
          },
          "shareReceiptPrices": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "shareUnitWeights": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "tailorServingToTastes": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "showSoftServingAdvice": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "babyEatsSeparatelyWhenNeeded": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "useStarterLibrary": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "showChildTasks": {
            "type": [
              "null",
              "boolean"
            ]
          },
          "madeAgainLift": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdateMealEventRequest": {
        "required": [
          "date",
          "eventType",
          "servingTime",
          "availableCookingMinutes",
          "notes",
          "guests",
          "memberAttendance"
        ],
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "eventType": {
            "$ref": "#/components/schemas/MealEventType"
          },
          "servingTime": {
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9](\\.[0-9]+)?)?$",
            "type": [
              "null",
              "string"
            ]
          },
          "availableCookingMinutes": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "notes": {
            "type": [
              "null",
              "string"
            ]
          },
          "guests": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/GuestGroupRequest"
            }
          },
          "memberAttendance": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/MemberAttendanceRequest"
            }
          }
        }
      },
      "UpdateMealEventRunRequest": {
        "required": [
          "ids",
          "days"
        ],
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpdateMealEventRequest"
            }
          }
        }
      },
      "UpdateMealRuleRequest": {
        "required": [
          "payload",
          "strength",
          "scope",
          "startDate",
          "endDate"
        ],
        "type": "object",
        "properties": {
          "payload": {
            "$ref": "#/components/schemas/MealRulePayloadDto"
          },
          "strength": {
            "$ref": "#/components/schemas/RuleStrength"
          },
          "scope": {
            "$ref": "#/components/schemas/RuleScope"
          },
          "startDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          },
          "endDate": {
            "type": [
              "null",
              "string"
            ],
            "format": "date"
          }
        }
      },
      "UpdateMeRequest": {
        "required": [
          "displayName"
        ],
        "type": "object",
        "properties": {
          "displayName": {
            "maxLength": 80,
            "minLength": 1,
            "pattern": "\\S",
            "type": "string"
          }
        }
      },
      "UpdatePushDeviceRequest": {
        "required": [
          "listChanges",
          "householdNews",
          "runDone"
        ],
        "type": "object",
        "properties": {
          "listChanges": {
            "type": "boolean"
          },
          "householdNews": {
            "type": "boolean"
          },
          "runDone": {
            "type": "boolean"
          },
          "reminders": {
            "type": [
              "null",
              "boolean"
            ]
          }
        }
      },
      "UpdateShoppingItemRequest": {
        "required": [
          "status"
        ],
        "type": "object",
        "properties": {
          "status": {
            "$ref": "#/components/schemas/ShoppingItemStatus"
          },
          "purchasedQuantity": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "purchasedUnit": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "UpgradeMoment": {
        "enum": [
          "Introduction",
          "QuotaHigh",
          "SustainedHighUse",
          "OverQuota"
        ]
      },
      "UpgradePromptAction": {
        "enum": [
          "Shown",
          "Dismissed",
          "Opened"
        ]
      },
      "UpgradePromptPeriodResponse": {
        "required": [
          "startsAt",
          "endsAt",
          "creditsUsed",
          "creditsLimit"
        ],
        "type": "object",
        "properties": {
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time"
          },
          "creditsUsed": {
            "type": "integer",
            "format": "int32"
          },
          "creditsLimit": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "UpgradePromptResponse": {
        "required": [
          "moment",
          "showOnHome",
          "currentPlanCode",
          "suggestedPlanCode",
          "suggestedMonthlyCredits",
          "suggestedPriceMinor",
          "creditsUsed",
          "creditsLimit",
          "resetsAt",
          "recentPeriods"
        ],
        "type": "object",
        "properties": {
          "moment": {
            "$ref": "#/components/schemas/UpgradeMoment"
          },
          "showOnHome": {
            "type": "boolean"
          },
          "currentPlanCode": {
            "type": "string"
          },
          "suggestedPlanCode": {
            "type": "string"
          },
          "suggestedMonthlyCredits": {
            "type": "integer",
            "format": "int32"
          },
          "suggestedPriceMinor": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "creditsUsed": {
            "type": "integer",
            "format": "int32"
          },
          "creditsLimit": {
            "type": "integer",
            "format": "int32"
          },
          "resetsAt": {
            "type": "string",
            "format": "date-time"
          },
          "recentPeriods": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/UpgradePromptPeriodResponse"
            }
          }
        }
      },
      "UpgradePromptsResponse": {
        "required": [
          "prompts"
        ],
        "type": "object",
        "properties": {
          "prompts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UpgradePromptResponse"
            }
          }
        }
      },
      "UploadPurchaseDocumentRequest": {
        "required": [
          "fileName",
          "contentType",
          "text",
          "base64Data"
        ],
        "type": "object",
        "properties": {
          "fileName": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "text": {
            "type": [
              "null",
              "string"
            ]
          },
          "base64Data": {
            "type": [
              "null",
              "string"
            ]
          },
          "purpose": {
            "$ref": "#/components/schemas/ImportPurpose"
          },
          "snapshotLocation": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/StorageLocation"
              }
            ]
          },
          "latitude": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "longitude": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "additionalPages": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PurchaseDocumentPage"
            }
          }
        }
      },
      "UsageCreditsResponse": {
        "required": [
          "used",
          "limit",
          "remaining",
          "percentage",
          "bonus",
          "bonusGranted"
        ],
        "type": "object",
        "properties": {
          "used": {
            "type": "integer",
            "format": "int32"
          },
          "limit": {
            "type": "integer",
            "format": "int32"
          },
          "remaining": {
            "type": "integer",
            "format": "int32"
          },
          "percentage": {
            "type": "number",
            "format": "double"
          },
          "bonus": {
            "type": "integer",
            "format": "int32"
          },
          "bonusGranted": {
            "type": "integer",
            "format": "int32"
          },
          "welcomeRemaining": {
            "type": "integer",
            "format": "int32",
            "default": 0
          },
          "monthlyRemaining": {
            "type": "integer",
            "format": "int32",
            "default": 0
          },
          "drawsFrom": {
            "$ref": "#/components/schemas/CreditPot"
          }
        }
      },
      "UsageLimitsResponse": {
        "required": [
          "softLimitReached",
          "allowanceSpent",
          "hardLimitReached"
        ],
        "type": "object",
        "properties": {
          "softLimitReached": {
            "type": "boolean"
          },
          "allowanceSpent": {
            "type": "boolean"
          },
          "hardLimitReached": {
            "type": "boolean"
          }
        }
      },
      "UsagePeriodResponse": {
        "required": [
          "startsAt",
          "endsAt"
        ],
        "type": "object",
        "properties": {
          "startsAt": {
            "type": "string",
            "format": "date-time"
          },
          "endsAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UsagePlanResponse": {
        "required": [
          "code",
          "name"
        ],
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "UsageResponse": {
        "required": [
          "period",
          "credits",
          "limits",
          "plan",
          "chatTurnCredits"
        ],
        "type": "object",
        "properties": {
          "period": {
            "$ref": "#/components/schemas/UsagePeriodResponse"
          },
          "credits": {
            "$ref": "#/components/schemas/UsageCreditsResponse"
          },
          "limits": {
            "$ref": "#/components/schemas/UsageLimitsResponse"
          },
          "plan": {
            "$ref": "#/components/schemas/UsagePlanResponse"
          },
          "chatTurnCredits": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "UsePriority": {
        "enum": [
          "Urgent",
          "Soon",
          "Normal",
          "Stable",
          "Unknown"
        ]
      },
      "UserResponse": {
        "required": [
          "id",
          "email",
          "displayName"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": [
              "null",
              "string"
            ]
          },
          "displayName": {
            "type": "string"
          }
        }
      },
      "VariantFromTipsRequest": {
        "required": [
          "planId",
          "entryId",
          "tipIndexes"
        ],
        "type": "object",
        "properties": {
          "planId": {
            "type": "string",
            "format": "uuid"
          },
          "entryId": {
            "type": "string",
            "format": "uuid"
          },
          "tipIndexes": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int32"
            }
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "WaitlistConfirmationResponse": {
        "required": [
          "confirmed"
        ],
        "type": "object",
        "properties": {
          "confirmed": {
            "type": "boolean"
          }
        }
      },
      "WaitlistEntryResponse": {
        "required": [
          "email",
          "joinedAt",
          "invited"
        ],
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "joinedAt": {
            "type": "string",
            "format": "date-time"
          },
          "invited": {
            "type": "boolean"
          }
        }
      },
      "Weekday": {
        "enum": [
          "Sunday",
          "Monday",
          "Tuesday",
          "Wednesday",
          "Thursday",
          "Friday",
          "Saturday"
        ]
      },
      "WeeklyPlanResponse": {
        "required": [
          "id",
          "householdId",
          "weekStartDate",
          "status",
          "plannerVersion",
          "scoreBreakdown",
          "entries",
          "createdAt",
          "activatedAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "householdId": {
            "type": "string",
            "format": "uuid"
          },
          "weekStartDate": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "$ref": "#/components/schemas/PlanStatus"
          },
          "plannerVersion": {
            "type": "string"
          },
          "scoreBreakdown": {
            "type": "object",
            "additionalProperties": {
              "type": "integer",
              "format": "int32"
            }
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlanEntryResponse"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "activatedAt": {
            "type": [
              "null",
              "string"
            ],
            "format": "date-time"
          },
          "fromStarterLibrary": {
            "type": "boolean",
            "default": false
          },
          "shoppingListStale": {
            "type": "boolean",
            "default": false
          },
          "staleDates": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "staleReason": {
            "anyOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/StaleReason"
              }
            ]
          },
          "deviations": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/PlanDeviationResponse"
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request could not be read as the route needs it: a body that is not the documented shape (a required field left out, a null where none is allowed, a value too long for its field), a parameter that does not parse, or a NUL character anywhere (#3540, #3541, #3543). Code validation_failed or value_too_long.",
        "headers": {
          "X-Trace-Id": {
            "$ref": "#/components/headers/X-Trace-Id"
          },
          "Server-Timing": {
            "$ref": "#/components/headers/Server-Timing"
          },
          "X-Diagnostics": {
            "$ref": "#/components/headers/X-Diagnostics"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        },
        "x-problem-codes": [
          "validation_failed",
          "value_too_long"
        ]
      },
      "Unauthorized": {
        "description": "No session, or the session is no longer valid.",
        "headers": {
          "X-Trace-Id": {
            "$ref": "#/components/headers/X-Trace-Id"
          },
          "Server-Timing": {
            "$ref": "#/components/headers/Server-Timing"
          },
          "X-Diagnostics": {
            "$ref": "#/components/headers/X-Diagnostics"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        }
      },
      "ForbiddenHousehold": {
        "description": "The caller is not a member of this household, or the route asks for a role (adult, owner) the caller does not have. The problem body's code says which: not_a_member, owner_required or junior_forbidden (#2646).",
        "headers": {
          "X-Trace-Id": {
            "$ref": "#/components/headers/X-Trace-Id"
          },
          "Server-Timing": {
            "$ref": "#/components/headers/Server-Timing"
          },
          "X-Diagnostics": {
            "$ref": "#/components/headers/X-Diagnostics"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        },
        "x-problem-codes": [
          "junior_forbidden",
          "not_a_member",
          "owner_required"
        ]
      },
      "UpgradeRequired": {
        "description": "The build named in X-App-Version is below its platform's version floor (ADR-070). Problem type app-version-unsupported; the only way forward is the store. Callers without the header are never turned away.",
        "headers": {
          "X-Trace-Id": {
            "$ref": "#/components/headers/X-Trace-Id"
          },
          "Server-Timing": {
            "$ref": "#/components/headers/Server-Timing"
          },
          "X-Diagnostics": {
            "$ref": "#/components/headers/X-Diagnostics"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        },
        "x-problem-codes": [
          "app_version_unsupported"
        ]
      },
      "TooManyRequests": {
        "description": "Too many calls in the window; Retry-After says how long.",
        "headers": {
          "X-Trace-Id": {
            "$ref": "#/components/headers/X-Trace-Id"
          },
          "Server-Timing": {
            "$ref": "#/components/headers/Server-Timing"
          },
          "X-Diagnostics": {
            "$ref": "#/components/headers/X-Diagnostics"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            }
          }
        },
        "x-problem-codes": [
          "too_many_requests"
        ]
      }
    },
    "headers": {
      "X-Trace-Id": {
        "description": "The W3C trace id of this request — the same id in the log and in the traceId of a problem. Quote it when reporting.",
        "schema": {
          "type": "string"
        }
      },
      "Server-Timing": {
        "description": "Where the time went: total, db;dur=…;desc=\"N queries\", and every step the handler named (ADR-163).",
        "schema": {
          "type": "string"
        }
      },
      "X-Diagnostics": {
        "description": "Only when the request asked with X-Diagnostics: 1 or ?diagnostics=1: the same breakdown as one JSON object, with the trace id (ADR-163).",
        "schema": {
          "type": "string"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait when a rate limit answered, or an HTTP date when the household's AI is paused. Absent when the household's AI quota for the period is used up — that resets by itself.",
        "schema": {
          "type": "string"
        }
      }
    },
    "securitySchemes": {
      "session": {
        "type": "apiKey",
        "description": "The signed-in session, set by POST /api/v1/auth/login or a social login callback. Sent by the browser and the apps on every call.",
        "name": "bryti_session",
        "in": "cookie"
      }
    }
  },
  "security": [
    {
      "session": [ ]
    }
  ],
  "tags": [
    {
      "name": "Auth",
      "description": "Sign up, sign in, social login, and the caller's own account."
    },
    {
      "name": "Households",
      "description": "The household itself: create it, read it, change its settings."
    },
    {
      "name": "Members",
      "description": "The people at the table — adults, Juniors, babies and guests."
    },
    {
      "name": "Memberships & invites",
      "description": "Who belongs to which household, and how they got in."
    },
    {
      "name": "Constraints",
      "description": "Allergies, intolerances and diets the planner filters dishes by."
    },
    {
      "name": "Preferences",
      "description": "What the household likes and does not like."
    },
    {
      "name": "Rules",
      "description": "Household meal rules the planner plans around."
    },
    {
      "name": "Events",
      "description": "Meal events on the calendar that change what a day needs."
    },
    {
      "name": "Plans",
      "description": "The week: plan it, read it, swap a dinner, lock a day."
    },
    {
      "name": "Candidates",
      "description": "Dinner candidates Bryti suggests for a day."
    },
    {
      "name": "Recipes",
      "description": "The household's recipe book."
    },
    {
      "name": "Wishes",
      "description": "The household's wish list: dishes the members would like, and their thumbs-up."
    },
    {
      "name": "Imports",
      "description": "Recipes and receipts brought in from photos, files and links."
    },
    {
      "name": "Inventory & shopping",
      "description": "The pantry, leftovers and the shopping list."
    },
    {
      "name": "Stores",
      "description": "The stores the household shops at and the prices Bryti has seen."
    },
    {
      "name": "Outcomes",
      "description": "What happened after a dinner: cooked, skipped, leftovers."
    },
    {
      "name": "Home",
      "description": "The day as the home screen shows it."
    },
    {
      "name": "Chat",
      "description": "Bryti in the chat: conversations, turns and proposed actions."
    },
    {
      "name": "Calendar feeds",
      "description": "Subscribe to the week from a calendar app."
    },
    {
      "name": "Push",
      "description": "The devices that receive push notifications."
    },
    {
      "name": "Usage",
      "description": "AI credits used and left this period."
    },
    {
      "name": "Billing",
      "description": "Subscriptions and the Stripe checkout."
    },
    {
      "name": "Privacy",
      "description": "Export the household's data, or delete it."
    },
    {
      "name": "Intake",
      "description": "Whether sign-up is open, and the waiting list."
    },
    {
      "name": "App",
      "description": "Version floor and health, for the apps and the platform."
    },
    {
      "name": "Data",
      "description": "Data Bryti shares back, such as the Open Food Facts derivative under ODbL."
    },
    {
      "name": "Feedback",
      "description": "Report a problem from inside the app."
    }
  ],
  "x-problem-codes": [
    "ai_paused",
    "ai_provider_timeout",
    "ai_provider_unavailable",
    "already_subscribed",
    "app_version_unsupported",
    "bad_request",
    "billing_disabled",
    "billing_webhook_rejected",
    "candidate_pool_in_plan",
    "conflict",
    "duplicate_ai_request",
    "github_not_configured",
    "junior_forbidden",
    "not_a_member",
    "not_found",
    "outcome_already_recorded",
    "outcome_cannot_be_undone",
    "owner_required",
    "planning_conflict",
    "quota_exceeded",
    "recipe_in_plan",
    "too_many_requests",
    "validation_failed",
    "value_too_long",
    "week_planning_in_progress"
  ]
}