openapi: 3.0.0
info:
  title: 'Family Planner API'
  description: 'Backend pro Receptoid (recepty, jídelníčky, dietní plány) a TohleKup (nákupní seznamy, domácnost, finance).'
  termsOfService: 'https://receptoid.cz/tos'
  contact:
    name: 'Štěpán Zrník'
    url: 'https://stepan.zrnik.eu'
    email: stepan.zrnik@gmail.com
  license:
    name: Proprietary
    url: 'https://receptoid.cz/tos'
  version: 1.0.0
paths:
  /admin/ai-spending:
    get:
      tags:
        - 'Admin AI'
      summary: 'AI spending overview'
      operationId: adminAiSpending
      responses:
        200:
          description: 'AI spending stats'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminAiSpendingResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/login:
    post:
      tags:
        - Auth
      summary: 'Admin login'
      operationId: adminLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdminLoginRequest'
      responses:
        200:
          description: 'JWT token'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: 'Invalid credentials'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/google:
    post:
      tags:
        - Auth
      summary: 'Admin login with Google'
      operationId: adminGoogleLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GoogleLoginRequest'
      responses:
        200:
          description: 'JWT token'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        401:
          description: 'Not admin or invalid token'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        503:
          description: 'Google OAuth not configured'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/finance/banks:
    get:
      tags:
        - 'Admin Finance'
      summary: 'List finance banks with codes'
      operationId: adminFinanceBankList
      responses:
        200:
          description: 'Finance banks list'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AdminFinanceBankItemResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/finance/banks/{bankId}':
    put:
      tags:
        - 'Admin Finance'
      summary: 'Rename a finance bank'
      operationId: adminFinanceBankRename
      parameters:
        -
          name: bankId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdminBankRenameRequest'
      responses:
        200:
          description: 'Bank renamed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/households:
    get:
      tags:
        - 'Admin Households'
      summary: 'List all households with their members'
      operationId: adminListHouseholds
      responses:
        200:
          description: 'All households'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AdminHouseholdResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/households/{householdId}':
    delete:
      tags:
        - 'Admin Households'
      summary: 'Delete household and all its data (cascade)'
      operationId: adminDeleteHousehold
      responses:
        200:
          description: 'Household deleted'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/households/{householdId}/members/{memberId}':
    delete:
      tags:
        - 'Admin Households'
      summary: 'Remove a member from household (revoke access)'
      operationId: adminRemoveHouseholdMember
      responses:
        200:
          description: 'Member removed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/households/{householdId}/members/{memberId}/finance-access':
    patch:
      tags:
        - 'Admin Households'
      summary: 'Toggle finance access for a household member'
      operationId: adminToggleFinanceAccess
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceAccessRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        404:
          description: 'Member not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/products/merge-candidates:
    get:
      tags:
        - 'Admin Products'
      summary: 'List product pairs that are merge candidates (pg_trgm similarity > 0.75)'
      operationId: adminListProductMergeCandidates
      responses:
        200:
          description: 'Product merge candidates'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AdminMergeCandidateResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/products/merge-suggest:
    get:
      tags:
        - 'Admin Products'
      summary: 'Ask Claude to suggest which product should be master'
      operationId: adminSuggestProductMergeMaster
      responses:
        200:
          description: 'Claude suggestion'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminMergeSuggestionResponse'
        400:
          description: 'Missing idA or idB'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/products/merge:
    post:
      tags:
        - 'Admin Products'
      summary: 'Merge duplicate product into master (atomic FK relinking)'
      operationId: adminMergeProducts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                masterId:
                  type: string
                  format: uuid
                duplicateId:
                  type: string
                  format: uuid
              type: object
      responses:
        200:
          description: Merged
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Missing fields'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/products/merge-dismiss:
    post:
      tags:
        - 'Admin Products'
      summary: 'Dismiss a product merge pair (will not appear in candidates again)'
      operationId: adminDismissProductMerge
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                idA:
                  type: string
                  format: uuid
                idB:
                  type: string
                  format: uuid
              type: object
      responses:
        200:
          description: Dismissed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Missing fields'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/products:
    get:
      tags:
        - 'Admin Products'
      summary: 'List all global products (household_id IS NULL)'
      operationId: adminListGlobalProducts
      responses:
        200:
          description: 'Global products'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AdminGlobalProductItemResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/brands/merge:
    post:
      tags:
        - 'Admin Products'
      summary: 'Merge duplicate brand into master (atomic FK relinking)'
      operationId: adminMergeBrands
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                masterId:
                  type: string
                  format: uuid
                duplicateId:
                  type: string
                  format: uuid
              type: object
      responses:
        200:
          description: Merged
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Missing fields'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/recipes:
    get:
      tags:
        - 'Admin Recipes'
      summary: 'List all recipes with pagination and filters'
      operationId: adminListRecipes
      responses:
        200:
          description: 'Paginated recipe list'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminRecipeListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/recipes/parse:
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Parse raw recipe text via Claude AI'
      operationId: adminParseRecipe
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdminRecipeParseRequest'
      responses:
        200:
          description: 'Parsed recipe'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminRecipeParseResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/recipes/feedback:
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Generate AI feedback questions for a recipe'
      operationId: adminRecipeFeedback
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecipeFeedbackRequest'
      responses:
        200:
          description: 'Feedback questions'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeFeedbackResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/recipes/feedback/apply:
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Apply feedback answers to recipe via AI'
      operationId: adminRecipeFeedbackApply
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecipeFeedbackApplyRequest'
      responses:
        200:
          description: 'Updated recipe metadata'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeFeedbackApplyResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/recipes/queue:
    get:
      tags:
        - 'Admin Recipes'
      summary: 'List recipes pending approval'
      operationId: adminListRecipeQueue
      responses:
        200:
          description: 'Recipes pending approval'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AdminRecipeQueueResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipes/{recipeId}':
    get:
      tags:
        - 'Admin Recipes'
      summary: 'Get full recipe detail for admin review'
      operationId: adminGetRecipe
      responses:
        200:
          description: 'Recipe detail'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminRecipeDetailResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipes/{recipeId}/approve':
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Approve a recipe for public library'
      operationId: adminApproveRecipe
      responses:
        200:
          description: 'Recipe approved'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipes/{recipeId}/reject':
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Reject a recipe (return to queue)'
      operationId: adminRejectRecipe
      responses:
        200:
          description: 'Recipe rejected'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipes/{recipeId}/refactor':
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Trigger Claude refactoring for a recipe'
      operationId: adminRefactorRecipe
      responses:
        200:
          description: 'Refactoring done'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - 'Admin Recipes'
      summary: 'Dismiss refactored draft (keep original)'
      operationId: adminDismissRefactor
      responses:
        200:
          description: 'Refactor dismissed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipes/{recipeId}/refactor-diff':
    get:
      tags:
        - 'Admin Recipes'
      summary: 'Get original vs refactored recipe diff'
      operationId: adminGetRefactorDiff
      responses:
        200:
          description: 'Refactor diff'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminRecipeRefactorResponse'
        400:
          description: 'No refactored draft available'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipes/{recipeId}/refactor-accept':
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Accept refactored recipe as canonical'
      operationId: adminAcceptRefactor
      responses:
        200:
          description: 'Refactor accepted'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/products/quick-create:
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Create a product without required category (for recipe import)'
      operationId: adminQuickCreateProduct
      responses:
        201:
          description: 'Product created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminQuickProductResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/claude-models:
    get:
      tags:
        - 'Admin Recipes'
      operationId: adminClaudeModels
      responses:
        200:
          description: 'List of available Claude models'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClaudeModelsResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/recipe-reports:
    get:
      tags:
        - 'Admin Recipes'
      summary: 'List recipe error reports'
      operationId: adminListRecipeReports
      responses:
        200:
          description: 'Report list'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeReportResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipe-reports/{reportId}':
    delete:
      tags:
        - 'Admin Recipes'
      summary: 'Dismiss a recipe report'
      operationId: adminDeleteRecipeReport
      responses:
        200:
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/stores/merge-candidates:
    get:
      tags:
        - 'Admin Stores'
      summary: 'List store pairs that are merge candidates (pg_trgm similarity > 0.75)'
      operationId: adminListStoreMergeCandidates
      responses:
        200:
          description: 'Store merge candidates'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AdminMergeCandidateResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/stores/merge:
    post:
      tags:
        - 'Admin Stores'
      summary: 'Merge duplicate store into master (atomic FK relinking)'
      operationId: adminMergeStores
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                masterId:
                  type: string
                  format: uuid
                duplicateId:
                  type: string
                  format: uuid
              type: object
      responses:
        200:
          description: Merged
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Missing fields'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/stores/merge-dismiss:
    post:
      tags:
        - 'Admin Stores'
      summary: 'Dismiss a store merge pair (will not appear in candidates again)'
      operationId: adminDismissStoreMerge
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                idA:
                  type: string
                  format: uuid
                idB:
                  type: string
                  format: uuid
              type: object
      responses:
        200:
          description: Dismissed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Missing fields'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/categories/merge:
    post:
      tags:
        - 'Admin Stores'
      summary: 'Merge duplicate category into master (atomic FK relinking)'
      operationId: adminMergeCategories
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                masterId:
                  type: string
                  format: uuid
                duplicateId:
                  type: string
                  format: uuid
              type: object
      responses:
        200:
          description: Merged
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Missing fields'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /brands:
    get:
      tags:
        - Brands
      summary: 'List brands'
      operationId: listBrands
      responses:
        200:
          description: Brands
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/BrandResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Brands
      summary: 'Create brand'
      operationId: createBrand
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BrandRequest'
      responses:
        201:
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BrandResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/brands/{brandId}':
    delete:
      tags:
        - Brands
      summary: 'Delete brand'
      operationId: deleteBrand
      parameters:
        -
          name: brandId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        403:
          description: 'Locked (public brand)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/products/{productId}/brands/{brandId}':
    post:
      tags:
        - Brands
      summary: 'Add brand to product'
      operationId: addProductBrand
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        -
          name: brandId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Added
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Brands
      summary: 'Remove brand from product'
      operationId: removeProductBrand
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        -
          name: brandId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /categories:
    get:
      tags:
        - Categories
      summary: 'List categories'
      operationId: listCategories
      parameters:
        -
          name: is_food
          in: query
          required: false
          schema:
            type: boolean
        -
          name: page
          in: query
          required: false
          schema:
            type: integer
            default: 1
        -
          name: search
          in: query
          required: false
          schema:
            type: string
      responses:
        200:
          description: 'Categories list'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Categories
      summary: 'Create a category'
      operationId: createCategory
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CategoryRequest'
      responses:
        201:
          description: 'Category created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoryResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /categories/order:
    patch:
      tags:
        - Categories
      summary: 'Set household category order'
      operationId: updateCategoryOrder
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CategoryOrderRequest'
      responses:
        204:
          description: 'Order saved'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/categories/{categoryId}/products':
    get:
      tags:
        - Categories
      summary: 'List products in a category'
      operationId: listCategoryProducts
      parameters:
        -
          name: categoryId
          in: path
          required: true
      responses:
        200:
          description: 'Products list'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProductResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/categories/{categoryId}/products/order':
    patch:
      tags:
        - Categories
      summary: 'Set product order within category'
      operationId: updateCategoryProductOrder
      parameters:
        -
          name: categoryId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CategoryProductOrderRequest'
      responses:
        204:
          description: 'Order saved'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/categories/{categoryId}':
    delete:
      tags:
        - Categories
      summary: 'Delete an empty private category'
      operationId: deleteCategory
      parameters:
        -
          name: categoryId
          in: path
          schema:
            type: string
            format: uuid
      responses:
        204:
          description: Deleted
        400:
          description: 'Category is not empty'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not allowed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /centrifugo/token:
    get:
      tags:
        - Auth
      summary: 'Get Centrifugo connection token'
      operationId: getCentrifugoToken
      responses:
        200:
          description: 'Centrifugo JWT token'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /device-token:
    post:
      tags:
        - Auth
      summary: 'Create a new device token'
      operationId: createDeviceToken
      responses:
        201:
          description: 'Device token created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceTokenResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /diet/plan/generate:
    post:
      tags:
        - Diet
      summary: 'Generate AI weekly meal plan'
      operationId: generateDietPlan
      responses:
        200:
          description: 'Generated plan'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DietPlanResponse'
        400:
          description: 'Missing profile'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /diet/plan/current:
    get:
      tags:
        - Diet
      summary: 'Get active meal plan'
      operationId: getCurrentDietPlan
      responses:
        200:
          description: 'Active plan or null'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DietPlanResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/diet/plan/{planId}':
    get:
      tags:
        - Diet
      summary: 'Get specific meal plan'
      operationId: getDietPlan
      parameters:
        -
          name: planId
          in: path
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: 'Plan with meals'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DietPlanResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/diet/plan/{planId}/regenerate-meal':
    post:
      tags:
        - Diet
      summary: 'Regenerate single meal via AI'
      operationId: regenerateDietPlanMeal
      parameters:
        -
          name: planId
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmptyResponse'
      responses:
        200:
          description: 'Updated plan'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DietPlanResponse'
        400:
          description: 'Invalid input'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/diet/plan/{planId}/meal/{mealId}':
    patch:
      tags:
        - Diet
      summary: 'Swap recipe for a meal'
      operationId: updateDietPlanMeal
      parameters:
        -
          name: planId
          in: path
          schema:
            type: string
            format: uuid
        -
          name: mealId
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmptyResponse'
      responses:
        200:
          description: 'Updated plan'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DietPlanResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/diet/plan/{planId}/archive':
    post:
      tags:
        - Diet
      summary: 'Archive a meal plan'
      operationId: archiveDietPlan
      parameters:
        -
          name: planId
          in: path
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Archived
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/diet/plan/{planId}/send-to-meal-plan':
    post:
      tags:
        - Diet
      summary: 'Send diet plan meals to TohleKup meal plan'
      operationId: sendDietPlanToMealPlan
      parameters:
        -
          name: planId
          in: path
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendToMealPlanRequest'
      responses:
        200:
          description: 'Entries added'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EntriesAddedResponse'
        400:
          description: 'Invalid input'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /diet/profile:
    get:
      tags:
        - Diet
      summary: 'Get nutrition profile with TDEE calculation'
      operationId: getDietProfile
      responses:
        200:
          description: 'Profile with TDEE'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DietProfileResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      tags:
        - Diet
      summary: 'Create or update nutrition profile'
      operationId: updateDietProfile
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DietProfileResponse'
      responses:
        200:
          description: 'Updated profile'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DietProfileResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /diet/weight-log:
    get:
      tags:
        - Diet
      summary: 'Get weight history'
      operationId: getWeightLog
      responses:
        200:
          description: 'Weight log entries'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/WeightLogResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Diet
      summary: 'Add or update weight entry'
      operationId: addWeightLog
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WeightLogResponse'
      responses:
        200:
          description: 'Weight entry saved'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WeightLogResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/diet/weight-log/{date}':
    delete:
      tags:
        - Diet
      summary: 'Delete weight entry by date'
      operationId: deleteWeightLog
      parameters:
        -
          name: date
          in: path
          schema:
            type: string
            format: date
      responses:
        204:
          description: Deleted
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/documents/{service}/{document}':
    get:
      tags:
        - Documents
      summary: 'Get current version of a legal document (Markdown)'
      operationId: getDocument
      parameters:
        -
          name: service
          in: path
          schema:
            type: string
            enum:
              - receptoid
              - tohlekup
              - finance
        -
          name: document
          in: path
          schema:
            type: string
            enum:
              - terms_of_service
              - privacy_policy
      responses:
        200:
          description: 'Document content'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentResponse'
        404:
          description: 'Document not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/documents/{service}/{document}/raw':
    get:
      tags:
        - Documents
      summary: 'Get current version as raw Markdown'
      operationId: getDocumentRaw
      parameters:
        -
          name: service
          in: path
          schema:
            type: string
            enum:
              - receptoid
              - tohlekup
              - finance
        -
          name: document
          in: path
          schema:
            type: string
            enum:
              - terms_of_service
              - privacy_policy
      responses:
        200:
          description: 'Raw Markdown'
          content:
            text/markdown:
              schema:
                type: string
        404:
          description: 'Document not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/document-versions/{service}/{document}':
    get:
      tags:
        - Documents
      summary: 'List all available versions of a document'
      operationId: listDocumentVersions
      parameters:
        -
          name: service
          in: path
          schema:
            type: string
            enum:
              - receptoid
              - tohlekup
              - finance
        -
          name: document
          in: path
          schema:
            type: string
            enum:
              - terms_of_service
              - privacy_policy
      responses:
        200:
          description: 'Version list'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentVersionsResponse'
        404:
          description: 'Document not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/document-versions/{service}/{document}/diff':
    get:
      tags:
        - Documents
      summary: 'Get diff between two versions of a document'
      operationId: diffDocumentVersions
      parameters:
        -
          name: service
          in: path
          schema:
            type: string
            enum:
              - receptoid
              - tohlekup
              - finance
        -
          name: document
          in: path
          schema:
            type: string
            enum:
              - terms_of_service
              - privacy_policy
        -
          name: from
          in: query
          required: true
          schema:
            type: string
            example: '1.0'
        -
          name: to
          in: query
          required: false
          schema:
            description: 'Defaults to current version'
            type: string
            example: '1.1'
      responses:
        200:
          description: 'Diff between versions'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentDiffResponse'
        404:
          description: 'Version not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/document-versions/{service}/{document}/{version}':
    get:
      tags:
        - Documents
      summary: 'Get a specific version of a document'
      operationId: getDocumentVersion
      parameters:
        -
          name: service
          in: path
          schema:
            type: string
            enum:
              - receptoid
              - tohlekup
              - finance
        -
          name: document
          in: path
          schema:
            type: string
            enum:
              - terms_of_service
              - privacy_policy
        -
          name: version
          in: path
          schema:
            type: string
            example: '1.0'
      responses:
        200:
          description: 'Document content'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentResponse'
        404:
          description: 'Version not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /document-consent/status:
    get:
      tags:
        - Documents
      summary: 'Get consent status for all documents of a service'
      operationId: getConsentStatus
      parameters:
        -
          name: service
          in: query
          required: true
          schema:
            type: string
            enum:
              - receptoid
              - tohlekup
      responses:
        200:
          description: 'Consent status'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentConsentStatusResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /document-consent:
    post:
      tags:
        - Documents
      summary: 'Accept a document version'
      operationId: acceptDocument
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentConsentRequest'
      responses:
        200:
          description: Accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Invalid request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/event-categories':
    get:
      tags:
        - Events
      summary: 'List event categories for a household'
      operationId: listEventCategories
      parameters:
        -
          name: householdId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: 'List of event categories'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EventCategoryResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/event-categories:
    post:
      tags:
        - Events
      summary: 'Create a global event category (admin only)'
      operationId: adminCreateEventCategory
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EventCategoryRequest'
      responses:
        201:
          description: 'Category created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventCategoryResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/event-categories/{categoryId}':
    delete:
      tags:
        - Events
      summary: 'Soft-delete a global event category (admin only)'
      operationId: adminDeleteEventCategory
      parameters:
        -
          name: categoryId
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/events':
    get:
      tags:
        - Events
      summary: 'List household events sorted by days until next occurrence'
      operationId: listEvents
      parameters:
        -
          name: householdId
          in: path
          required: true
          schema:
            type: string
      responses:
        200:
          description: 'List of events'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EventResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Events
      summary: 'Create a household event'
      operationId: createEvent
      parameters:
        -
          name: householdId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EventRequest'
      responses:
        201:
          description: 'Event created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/events/{eventId}':
    put:
      tags:
        - Events
      summary: 'Update a household event'
      operationId: updateEvent
      parameters:
        -
          name: householdId
          in: path
          required: true
          schema:
            type: string
        -
          name: eventId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EventRequest'
      responses:
        200:
          description: 'Event updated'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Events
      summary: 'Delete a household event'
      operationId: deleteEvent
      parameters:
        -
          name: householdId
          in: path
          required: true
          schema:
            type: string
        -
          name: eventId
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/accounts:
    get:
      tags:
        - Finance
      summary: 'List own finance accounts'
      operationId: financeListAccounts
      responses:
        200:
          description: Accounts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceAccountListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Finance
      summary: 'Manually add own finance account'
      operationId: financeCreateAccount
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceAccountRequest'
      responses:
        201:
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceAccountResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/finance/accounts/{accountId}':
    patch:
      tags:
        - Finance
      summary: 'Rename own finance account'
      operationId: financeUpdateAccount
      parameters:
        -
          name: accountId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceAccountNameRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/categories:
    get:
      tags:
        - Finance
      summary: 'List finance categories with mappings'
      operationId: financeListCategories
      responses:
        200:
          description: Categories
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceCategoryListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Finance
      summary: 'Create finance category'
      operationId: financeCreateCategory
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceCategoryRequest'
      responses:
        200:
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceCategoryResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/finance/categories/{categoryId}':
    delete:
      tags:
        - Finance
      summary: 'Delete finance category'
      operationId: financeDeleteCategory
      parameters:
        -
          name: categoryId
          in: path
          required: true
      responses:
        200:
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/categories/mapping:
    put:
      tags:
        - Finance
      summary: 'Assign or remove description-category mapping'
      operationId: financeSetCategoryMapping
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceCategoryMappingRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceOkResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/counterparties:
    get:
      tags:
        - Finance
      summary: 'List counterparties with income classification'
      operationId: financeListCounterparties
      responses:
        200:
          description: Counterparties
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceCounterpartyListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/counterparties/type:
    put:
      tags:
        - Finance
      summary: 'Set counterparty classification'
      operationId: financeSetCounterpartyType
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceCounterpartyTypeRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceOkResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/counterparty-labels:
    get:
      tags:
        - Finance
      summary: 'List user-defined counterparty names'
      operationId: financeListCounterpartyLabels
      responses:
        200:
          description: Labels
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceCounterpartyLabelListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      tags:
        - Finance
      summary: 'Set or remove user-defined counterparty name'
      operationId: financeSetCounterpartyLabel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceCounterpartyLabelRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceOkResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/stats:
    get:
      tags:
        - Finance
      summary: 'Get finance dashboard stats'
      operationId: financeStats
      responses:
        200:
          description: Stats
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceStatsResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/stats/income-breakdown:
    get:
      tags:
        - Finance
      summary: 'Get income transactions breakdown for last 12 months'
      operationId: financeIncomeBreakdown
      responses:
        200:
          description: 'Income breakdown by month'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceIncomeBreakdownResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/finance/transactions/{id}/exclude-from-income':
    patch:
      tags:
        - Finance
      summary: 'Toggle is_internal_transfer flag on a transaction'
      operationId: financeExcludeFromIncome
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceOkResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/batches:
    get:
      tags:
        - Finance
      summary: 'List import batches'
      operationId: financeListBatches
      responses:
        200:
          description: Batches
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceBatchListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/finance/batches/{batchId}':
    get:
      tags:
        - Finance
      summary: 'Get import batch detail'
      operationId: financeGetBatch
      parameters:
        -
          name: batchId
          in: path
          required: true
      responses:
        200:
          description: 'Batch detail'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceBatchDetailResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Finance
      summary: 'Delete import batch'
      operationId: financeDeleteBatch
      parameters:
        -
          name: batchId
          in: path
          required: true
      responses:
        200:
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      tags:
        - Finance
      summary: 'Toggle batch active state'
      operationId: financeUpdateBatch
      parameters:
        -
          name: batchId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceBatchUpdateRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/import:
    post:
      tags:
        - Finance
      summary: 'Import bank CSV/XML files'
      operationId: financeImport
      responses:
        200:
          description: 'Import result'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceImportResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/finance/batches/{batchId}/reprocess':
    post:
      tags:
        - Finance
      summary: 'Reprocess import batch'
      operationId: financeReprocessBatch
      parameters:
        -
          name: batchId
          in: path
          required: true
      responses:
        200:
          description: Queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/batches/reprocess-all:
    post:
      tags:
        - Finance
      summary: 'Reprocess all done/failed import batches'
      operationId: financeReprocessAllBatches
      responses:
        200:
          description: Queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/banks:
    get:
      tags:
        - Finance
      summary: 'List banks with transactions'
      operationId: financeListBanks
      responses:
        200:
          description: Banks
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceBankListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /finance/transactions:
    get:
      tags:
        - Finance
      summary: 'List transactions'
      operationId: financeListTransactions
      responses:
        200:
          description: Transactions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FinanceTransactionListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /diet/food-log/parse:
    post:
      tags:
        - Diet
      summary: 'AI parse free-text food description into structured items'
      operationId: parseFoodLog
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FoodLogParseRequest'
      responses:
        200:
          description: 'Parsed food items'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FoodLogEntryResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /diet/food-log:
    get:
      tags:
        - Diet
      summary: 'List food log entries by date range'
      operationId: listFoodLog
      parameters:
        -
          name: from
          in: query
          schema:
            type: string
            format: date
        -
          name: to
          in: query
          schema:
            type: string
            format: date
      responses:
        200:
          description: 'Food log entries'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FoodLogEntryResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Diet
      summary: 'Save confirmed food log entries'
      operationId: saveFoodLog
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FoodLogSaveRequest'
      responses:
        200:
          description: 'Saved entries'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FoodLogEntryResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/diet/food-log/{entryId}':
    delete:
      tags:
        - Diet
      summary: 'Delete a food log entry'
      operationId: deleteFoodLogEntry
      parameters:
        -
          name: entryId
          in: path
          schema:
            type: string
            format: uuid
      responses:
        204:
          description: Deleted
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /diet/food-log/summary:
    get:
      tags:
        - Diet
      summary: 'Daily calorie and macro summaries'
      operationId: getFoodLogSummary
      parameters:
        -
          name: from
          in: query
          schema:
            type: string
            format: date
        -
          name: to
          in: query
          schema:
            type: string
            format: date
      responses:
        200:
          description: 'Daily summaries'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FoodLogSummaryResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Premium required'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /households:
    post:
      tags:
        - Household
      summary: 'Create a household'
      operationId: createHousehold
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HouseholdRequest'
      responses:
        201:
          description: 'Household created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HouseholdResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}':
    patch:
      tags:
        - Household
      summary: 'Rename a household'
      operationId: renameHousehold
      parameters:
        -
          name: householdId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HouseholdRequest'
      responses:
        200:
          description: 'Household renamed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HouseholdResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/invite-code':
    post:
      tags:
        - Household
      summary: 'Generate invite code'
      operationId: generateInviteCode
      parameters:
        -
          name: householdId
          in: path
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: 'Invite code generated'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InviteCodeResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /households/join:
    post:
      tags:
        - Household
      summary: 'Join household via invite code'
      operationId: joinHousehold
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InviteCodeRequest'
      responses:
        200:
          description: Joined
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HouseholdResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Invalid or expired code'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/meal-plan':
    get:
      tags:
        - 'Meal Plan'
      summary: 'Get meal plan for a day or date range'
      operationId: getMealPlan
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: date
          in: query
          required: false
        -
          name: from
          in: query
          required: false
        -
          name: to
          in: query
          required: false
      responses:
        200:
          description: 'Meal plan grouped by slots'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MealPlanSlotResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - 'Meal Plan'
      summary: 'Add a recipe to a meal plan slot'
      operationId: addToMealPlan
      parameters:
        -
          name: householdId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MealPlanEntryRequest'
      responses:
        201:
          description: 'Added to meal plan'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MealPlanEntryResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Slot or recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/meal-plan/{entryId}':
    delete:
      tags:
        - 'Meal Plan'
      summary: 'Remove a recipe from the meal plan'
      operationId: removeFromMealPlan
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: entryId
          in: path
          required: true
      responses:
        204:
          description: Deleted
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/meal-plan/generate-shopping-list':
    post:
      tags:
        - 'Meal Plan'
      summary: 'Generate shopping list from meal plan date range'
      operationId: generateMealPlanShoppingList
      parameters:
        -
          name: householdId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateShoppingListRequest'
      responses:
        200:
          description: 'Shopping list items added'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ItemsAddedResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/meal-plan/{entryId}/confirm':
    post:
      tags:
        - 'Meal Plan'
      summary: 'Confirm a meal plan entry as cooked'
      operationId: confirmMealPlanEntry
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: entryId
          in: path
          required: true
      responses:
        200:
          description: Confirmed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MealPlanConfirmResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/meal-slots':
    get:
      tags:
        - 'Meal Slots'
      summary: 'List meal slots for household'
      operationId: listMealSlots
      parameters:
        -
          name: householdId
          in: path
          required: true
      responses:
        200:
          description: 'Meal slots'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MealSlotResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - 'Meal Slots'
      summary: 'Create a custom meal slot'
      operationId: createMealSlot
      parameters:
        -
          name: householdId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MealSlotRequest'
      responses:
        201:
          description: 'Meal slot created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MealSlotResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/meal-slots/{slotId}':
    put:
      tags:
        - 'Meal Slots'
      summary: 'Update a meal slot'
      operationId: updateMealSlot
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: slotId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MealSlotRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MealSlotResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - 'Meal Slots'
      summary: 'Delete a custom meal slot'
      operationId: deleteMealSlot
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: slotId
          in: path
          required: true
      responses:
        204:
          description: Deleted
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /member/profile:
    get:
      tags:
        - Members
      summary: 'Get member profile'
      operationId: getMemberProfile
      responses:
        200:
          description: Profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfileResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      tags:
        - Members
      summary: 'Update member profile'
      operationId: updateMemberProfile
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MemberProfileRequest'
      responses:
        200:
          description: 'Updated profile'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemberProfileResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/members':
    get:
      tags:
        - Members
      summary: 'List household members'
      operationId: listHouseholdMembers
      parameters:
        -
          name: householdId
          in: path
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: 'Members list'
          content:
            application/json:
              schema:
                type: array
                items:
                  properties: { id: { type: string, format: uuid }, display_name: { type: string, nullable: true }, email: { type: string } }
                  type: object
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/members/{memberId}':
    delete:
      tags:
        - Members
      summary: 'Remove member from household'
      operationId: removeMember
      parameters:
        -
          name: householdId
          in: path
          schema:
            type: string
            format: uuid
        -
          name: memberId
          in: path
          schema:
            type: string
            format: uuid
      responses:
        204:
          description: 'Member removed'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /member/delete-account:
    delete:
      tags:
        - Members
      summary: 'Permanently delete user account and all personal data'
      operationId: deleteAccount
      responses:
        204:
          description: 'Account deleted'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/members/{memberId}/finance-access':
    patch:
      tags:
        - Members
      summary: 'Toggle finance access for a household member (household admin)'
      operationId: toggleMemberFinanceAccess
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: memberId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FinanceAccessRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Member not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/presence/{date}':
    get:
      tags:
        - Members
      summary: 'Get absent member IDs for a given day'
      operationId: getDayPresence
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: date
          in: path
          description: 'Date in YYYY-MM-DD format'
          required: true
          schema:
            type: string
            format: date
            example: '2026-03-14'
      responses:
        200:
          description: 'Day presence info'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MemberPresenceResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/presence/{date}/mark-absent':
    post:
      tags:
        - Members
      summary: 'Mark a household member as absent for a given day'
      operationId: markMemberAbsent
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: date
          in: path
          description: 'Date in YYYY-MM-DD format'
          required: true
          schema:
            type: string
            format: date
            example: '2026-03-14'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MemberPresenceRequest'
      responses:
        200:
          description: 'Member marked as absent'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AbsenceResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Member not found in household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/presence/{date}/{memberId}':
    delete:
      tags:
        - Members
      summary: 'Mark a household member as present (remove absence record) for a given day'
      operationId: markMemberPresent
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: date
          in: path
          description: 'Date in YYYY-MM-DD format'
          required: true
          schema:
            type: string
            format: date
            example: '2026-03-14'
        -
          name: memberId
          in: path
          required: true
      responses:
        204:
          description: 'Absence record removed (member is present)'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /name-days/today:
    get:
      tags:
        - NameDays
      summary: 'Get name days for today'
      operationId: getNameDaysToday
      responses:
        200:
          description: 'Name day entry for today'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NameDayEntryResponse'
  '/name-days/month/{month}':
    get:
      tags:
        - NameDays
      summary: 'Get all name days for a given month'
      operationId: getNameDaysByMonth
      parameters:
        -
          name: month
          in: path
          required: true
          schema:
            type: integer
      responses:
        200:
          description: 'List of name day entries for the month'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/NameDayEntryResponse'
  '/name-days/{month}/{day}':
    get:
      tags:
        - NameDays
      summary: 'Get name days for a specific date'
      operationId: getNameDaysByDate
      parameters:
        -
          name: month
          in: path
          required: true
          schema:
            type: integer
        -
          name: day
          in: path
          required: true
          schema:
            type: integer
      responses:
        200:
          description: 'Name day entry for the given date'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NameDayEntryResponse'
  /name-days/search:
    get:
      tags:
        - NameDays
      summary: 'Search name days by name (diacritic-tolerant)'
      operationId: searchNameDays
      parameters:
        -
          name: q
          in: query
          required: true
          schema:
            type: string
      responses:
        200:
          description: 'Search results with optional mark flags'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/NameDaySearchResultResponse'
  /name-days/upcoming:
    get:
      tags:
        - NameDays
      summary: 'Get upcoming name days for marked names (14 days, max 5)'
      operationId: getUpcomingNameDays
      responses:
        200:
          description: 'Upcoming name days with mark flags'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/NameDaySearchResultResponse'
  /name-days/mark:
    post:
      tags:
        - NameDays
      summary: 'Mark a name as important (personal)'
      operationId: markNamePersonal
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MarkNameRequest'
      responses:
        204:
          description: 'Name marked'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
  '/name-days/mark/{slug}':
    delete:
      tags:
        - NameDays
      summary: 'Unmark a personal name'
      operationId: unmarkNamePersonal
      parameters:
        -
          name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          description: 'Name unmarked'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
  /name-days/household-mark:
    post:
      tags:
        - NameDays
      summary: 'Mark a name as important (household)'
      operationId: markNameHousehold
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MarkNameRequest'
      responses:
        204:
          description: 'Name marked for household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
  '/name-days/household-mark/{slug}':
    delete:
      tags:
        - NameDays
      summary: 'Unmark a household name'
      operationId: unmarkNameHousehold
      parameters:
        -
          name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        204:
          description: 'Name unmarked for household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
  /products:
    get:
      tags:
        - Products
      summary: 'List products for household'
      operationId: listProducts
      parameters:
        -
          name: page
          in: query
          required: false
          schema:
            type: integer
            default: 1
        -
          name: search
          in: query
          required: false
          schema:
            type: string
      responses:
        200:
          description: 'Products list'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Products
      summary: 'Create a product'
      operationId: createProduct
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductRequest'
      responses:
        201:
          description: 'Product created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /products/search:
    get:
      tags:
        - Products
      summary: 'Search products'
      operationId: searchProducts
      responses:
        200:
          description: 'Search results'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProductResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/products/{productId}':
    get:
      tags:
        - Products
      summary: 'Get product detail'
      operationId: getProduct
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: 'Product detail'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/products/{productId}/allergens':
    patch:
      tags:
        - Products
      summary: 'Update product allergens'
      operationId: patchProductAllergens
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductAllergenRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/products/{productId}/food-flag':
    patch:
      tags:
        - Products
      summary: 'Update product food flag'
      operationId: patchProductFoodFlag
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductFoodFlagRequest'
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/products/{productId}/suggested-unit':
    get:
      tags:
        - Products
      summary: 'Get most commonly used unit for a product in this household'
      operationId: getProductSuggestedUnit
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: 'Suggested unit'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductSuggestedUnitResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/products/{productId}/variants':
    get:
      tags:
        - Products
      summary: 'List product variants'
      operationId: listProductVariants
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Variants
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ProductVariantResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Products
      summary: 'Create product variant'
      operationId: createProductVariant
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProductVariantRequest'
      responses:
        201:
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductVariantResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/products/{productId}/variants/{variantId}/default':
    patch:
      tags:
        - Products
      summary: 'Set variant as default'
      operationId: setDefaultProductVariant
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        -
          name: variantId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductVariantResponse'
        403:
          description: 'Locked (public variant)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/products/{productId}/variants/{variantId}':
    delete:
      tags:
        - Products
      summary: 'Delete product variant'
      operationId: deleteProductVariant
      parameters:
        -
          name: productId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        -
          name: variantId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        403:
          description: 'Locked (public variant)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /recipes:
    get:
      tags:
        - Recipes
      summary: 'List public recipes (no auth required)'
      operationId: listPublicRecipes
      parameters:
        -
          name: q
          in: query
          description: 'Search query (filters by title/description)'
          required: false
          schema:
            type: string
        -
          name: 'mealType[]'
          in: query
          description: 'Filter by meal type tag (OR)'
          required: false
          schema:
            type: array
            items:
              type: string
        -
          name: 'dietaryTag[]'
          in: query
          description: 'Filter by dietary tag (OR)'
          required: false
          schema:
            type: array
            items:
              type: string
        -
          name: 'cookingMethod[]'
          in: query
          description: 'Filter by cooking method tag (OR)'
          required: false
          schema:
            type: array
            items:
              type: string
      responses:
        200:
          description: 'Public recipes list'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RecipeResponse'
    post:
      tags:
        - Recipes
      summary: 'Create recipe (no household required)'
      operationId: createRecipeForUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecipeRequest'
      responses:
        201:
          description: 'Recipe created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /recipes/mine:
    get:
      tags:
        - Recipes
      summary: 'List recipes owned by the authenticated user'
      operationId: listMyRecipes
      responses:
        200:
          description: 'User-owned recipes list'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RecipeResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}/qr':
    get:
      tags:
        - Recipes
      summary: 'Get QR code SVG for recipe'
      operationId: getRecipeQr
      parameters:
        -
          name: recipeId
          in: path
          required: true
      responses:
        200:
          description: 'SVG QR code'
          content:
            image/svg+xml:
              schema:
                type: string
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}':
    get:
      tags:
        - Recipes
      summary: 'Get recipe detail with ingredients (accepts UUID or slug)'
      operationId: getRecipeDetail
      responses:
        200:
          description: 'Recipe detail'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeDetailResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      tags:
        - Recipes
      summary: 'Update user-owned recipe'
      operationId: updateRecipeForUser
      parameters:
        -
          name: recipeId
          in: path
          required: true
      responses:
        200:
          description: 'Recipe updated'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeResponse'
        400:
          description: 'Bad Request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Recipes
      summary: 'Delete user-owned recipe'
      operationId: deleteRecipeForUser
      responses:
        204:
          description: 'Recipe deleted'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/recipes':
    get:
      tags:
        - Recipes
      summary: 'List recipes for household (public + private)'
      operationId: listHouseholdRecipes
      parameters:
        -
          name: 'mealType[]'
          in: query
          description: 'Filter by meal type tag (OR)'
          required: false
          schema:
            type: array
            items:
              type: string
        -
          name: 'dietaryTag[]'
          in: query
          description: 'Filter by dietary tag (OR)'
          required: false
          schema:
            type: array
            items:
              type: string
        -
          name: 'cookingMethod[]'
          in: query
          description: 'Filter by cooking method tag (OR)'
          required: false
          schema:
            type: array
            items:
              type: string
      responses:
        200:
          description: 'Recipes list'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RecipeResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of this household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Recipes
      summary: 'Create recipe in household (deprecated — use POST /recipes with household_id in body)'
      operationId: createRecipe
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecipeRequest'
      responses:
        201:
          description: 'Recipe created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of this household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      deprecated: true
  '/households/{householdId}/recipes/{recipeId}':
    get:
      tags:
        - Recipes
      summary: 'Get recipe detail for household member (includes step_groups and timing)'
      operationId: getHouseholdRecipeDetail
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: recipeId
          in: path
          required: true
      responses:
        200:
          description: 'Recipe detail with step_groups'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeDetailResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of this household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    put:
      tags:
        - Recipes
      summary: 'Update recipe'
      operationId: updateRecipe
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecipeRequest'
      responses:
        200:
          description: 'Recipe updated'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of this household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Recipes
      summary: 'Delete recipe'
      operationId: deleteRecipe
      responses:
        204:
          description: 'Recipe deleted'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of this household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}/visibility':
    patch:
      tags:
        - Recipes
      summary: 'Switch recipe visibility between private and household'
      operationId: patchRecipeVisibility
      parameters:
        -
          name: recipeId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                visibility:
                  type: string
                  enum: [private, household]
              type: object
      responses:
        200:
          description: 'Visibility updated'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}/households/{householdId}':
    post:
      tags:
        - Recipes
      summary: 'Share recipe to a household'
      operationId: shareRecipeToHousehold
      parameters:
        -
          name: recipeId
          in: path
          required: true
        -
          name: householdId
          in: path
          required: true
      responses:
        201:
          description: 'Recipe shared'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Recipes
      summary: 'Remove recipe from a household'
      operationId: unshareRecipeFromHousehold
      parameters:
        -
          name: recipeId
          in: path
          required: true
        -
          name: householdId
          in: path
          required: true
      responses:
        200:
          description: 'Recipe unshared'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}/households':
    get:
      tags:
        - Recipes
      summary: 'Get households the recipe is shared with'
      operationId: getRecipeHouseholds
      parameters:
        -
          name: recipeId
          in: path
          required: true
      responses:
        200:
          description: 'Household IDs'
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}/submit-public':
    post:
      tags:
        - Recipes
      summary: 'Submit recipe for public approval'
      operationId: submitRecipeForPublic
      parameters:
        -
          name: recipeId
          in: path
          required: true
      responses:
        200:
          description: 'Recipe submitted'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{slug}/report':
    post:
      tags:
        - Recipes
      summary: 'Report an error in a recipe (no auth required)'
      operationId: reportRecipe
      parameters:
        -
          name: slug
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecipeReportRequest'
      responses:
        200:
          description: 'Report submitted'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        429:
          description: 'Too many reports'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}/proposals':
    post:
      tags:
        - Recipes
      summary: 'Submit a proposal to change a public or pending-approval recipe'
      operationId: createRecipeProposal
      parameters:
        -
          name: recipeId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProposalCreateRequest'
      responses:
        201:
          description: 'Proposal created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProposalCreateResponse'
        400:
          description: 'Recipe is not locked (not public or pending approval)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /admin/recipe-proposals:
    get:
      tags:
        - 'Admin Recipes'
      summary: 'List pending recipe proposals'
      operationId: adminListProposals
      responses:
        200:
          description: 'Pending proposals'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RecipeProposalResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipe-proposals/{proposalId}':
    get:
      tags:
        - 'Admin Recipes'
      summary: 'Get side-by-side diff for a proposal'
      operationId: adminGetProposalDiff
      parameters:
        -
          name: proposalId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: 'Proposal diff'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeProposalDiffResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Proposal not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipe-proposals/{proposalId}/accept':
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Accept a recipe proposal (copies content to recipe)'
      operationId: adminAcceptProposal
      parameters:
        -
          name: proposalId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: 'Proposal accepted'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Proposal not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/admin/recipe-proposals/{proposalId}/reject':
    post:
      tags:
        - 'Admin Recipes'
      summary: 'Reject a recipe proposal'
      operationId: adminRejectProposal
      parameters:
        -
          name: proposalId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: false
        content:
          application/json:
            schema:
              properties:
                rejection_reason:
                  type: string
                  nullable: true
              type: object
      responses:
        200:
          description: 'Proposal rejected'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Proposal not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/households/{householdId}/recipe-suggestions':
    get:
      tags:
        - Recipes
      summary: 'Get recipe suggestions sorted by recency (least recently cooked first)'
      operationId: getRecipeSuggestions
      parameters:
        -
          name: householdId
          in: path
          required: true
        -
          name: date
          in: query
          description: 'Date YYYY-MM-DD for seasonal filtering (default: today)'
          required: false
      responses:
        200:
          description: 'Sorted recipe suggestions'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RecipeSuggestionResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /recipe-tags:
    get:
      tags:
        - Recipes
      summary: 'Get available recipe tag vocabulary (no auth required)'
      operationId: getRecipeTags
      responses:
        200:
          description: 'Recipe tag vocabulary'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecipeTagsResponse'
  '/households/{householdId}/shopping-lists/{listId}/add-recipe/{recipeId}':
    post:
      tags:
        - Recipes
      summary: 'Add recipe ingredients to shopping list (deprecated — use POST /shopping-lists/{listId}/add-recipe)'
      operationId: addRecipeToShoppingList
      responses:
        200:
          description: 'Ingredients added'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ItemsAddedResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of this household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe or shopping list not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      deprecated: true
  '/shopping-lists/{listId}/add-recipe':
    post:
      tags:
        - Shopping
      summary: 'Add recipe ingredients to shopping list'
      operationId: addRecipeToShoppingListSimple
      parameters:
        -
          name: listId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddRecipeToShoppingListRequest'
      responses:
        200:
          description: 'Ingredients added'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ItemsAddedResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of this household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe or shopping list not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}/link-variant/{targetId}':
    post:
      tags:
        - Recipes
      summary: 'Link two recipes as variants'
      operationId: linkRecipeVariant
      parameters:
        -
          name: recipeId
          in: path
          required: true
        -
          name: targetId
          in: path
          required: true
      responses:
        200:
          description: 'Variants linked'
          content:
            application/json:
              schema:
                properties:
                  group_id: { type: string }
                  variants: { type: array, items: { properties: { id: { type: string }, title: { type: string } }, type: object } }
                type: object
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        409:
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}/unlink-variant':
    delete:
      tags:
        - Recipes
      summary: 'Remove recipe from its variant group'
      operationId: unlinkRecipeVariant
      parameters:
        -
          name: recipeId
          in: path
          required: true
      responses:
        200:
          description: Unlinked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/recipes/{recipeId}/variant-master':
    put:
      tags:
        - Recipes
      summary: 'Set recipe as master of its variant group'
      operationId: setVariantMaster
      parameters:
        -
          name: recipeId
          in: path
          required: true
      responses:
        200:
          description: 'Master set'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Recipe has no variant group'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Recipe not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /shopping-lists:
    get:
      tags:
        - Shopping
      summary: 'List shopping lists for household'
      operationId: listShoppingLists
      responses:
        200:
          description: 'Shopping lists'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ShoppingListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Shopping
      summary: 'Create a shopping list'
      operationId: createShoppingList
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ShoppingListRequest'
      responses:
        201:
          description: 'Shopping list created'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShoppingListResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/shopping-lists/{listId}':
    get:
      tags:
        - Shopping
      summary: 'Get a shopping list with optional store-ordered view'
      operationId: getShoppingList
      parameters:
        -
          name: listId
          in: path
          required: true
        -
          name: store_id
          in: query
          required: false
      responses:
        200:
          description: 'Shopping list detail'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShoppingListDetailResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Shopping
      summary: 'Delete a shopping list'
      operationId: deleteShoppingList
      parameters:
        -
          name: listId
          in: path
          required: true
      responses:
        204:
          description: Deleted
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/shopping-lists/{listId}/checkout':
    post:
      tags:
        - Shopping
      summary: 'Mark all in_cart items as bought'
      operationId: checkoutShoppingList
      parameters:
        -
          name: listId
          in: path
          required: true
      responses:
        200:
          description: 'Checkout done'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CheckoutResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/shopping-lists/{listId}/stores/{storeId}':
    post:
      tags:
        - Shopping
      summary: 'Add a store to a shopping list'
      operationId: addStoreToShoppingList
      parameters:
        -
          name: listId
          in: path
          required: true
        -
          name: storeId
          in: path
          required: true
      responses:
        204:
          description: 'Store added'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Shopping
      summary: 'Remove a store from a shopping list (min 1 required)'
      operationId: removeStoreFromShoppingList
      parameters:
        -
          name: listId
          in: path
          required: true
        -
          name: storeId
          in: path
          required: true
      responses:
        204:
          description: 'Store removed'
        400:
          description: 'Cannot remove last store'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/shopping-lists/{listId}/items':
    post:
      tags:
        - Shopping
      summary: 'Add item to a shopping list'
      operationId: addShoppingListItem
      parameters:
        -
          name: listId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ShoppingListItemRequest'
      responses:
        201:
          description: 'Item added'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShoppingListItemResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Shopping list not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/shopping-list-items/{itemId}/status':
    patch:
      tags:
        - Shopping
      summary: 'Update shopping list item status'
      operationId: updateShoppingListItemStatus
      parameters:
        -
          name: itemId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                status:
                  type: string
                  enum: [pending, in_cart, bought]
              type: object
      responses:
        200:
          description: 'Status updated'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShoppingListItemResponse'
        400:
          description: 'Invalid status value'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Item not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/shopping-list-items/{itemId}':
    put:
      tags:
        - Shopping
      summary: 'Update shopping list item quantity, unit and preferred store'
      operationId: updateShoppingListItem
      parameters:
        -
          name: itemId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              properties:
                quantity:
                  type: number
                unit:
                  type: string
                  nullable: true
                preferred_store_id:
                  type: string
                  nullable: true
              type: object
      responses:
        200:
          description: 'Item updated'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShoppingListItemResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Item not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Shopping
      summary: 'Delete a shopping list item'
      operationId: deleteShoppingListItem
      parameters:
        -
          name: itemId
          in: path
          required: true
      responses:
        204:
          description: 'Item deleted'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        404:
          description: 'Item not found'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /stores:
    get:
      tags:
        - Stores
      summary: 'List stores for household'
      operationId: listStores
      parameters:
        -
          name: page
          in: query
          required: false
          schema:
            type: integer
            default: 1
        -
          name: search
          in: query
          required: false
          schema:
            type: string
      responses:
        200:
          description: 'Stores list'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoreListResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Stores
      summary: 'Create a store'
      operationId: createStore
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StoreRequest'
      responses:
        200:
          description: 'Similar stores found — user must confirm'
          content:
            application/json:
              schema:
                properties:
                  matches: { type: array, items: { $ref: '#/components/schemas/StoreMatchItemResponse' } }
                  created: { oneOf: [{ $ref: '#/components/schemas/StoreCreatedResponse' }], nullable: true }
                type: object
        201:
          description: 'Store created (no duplicates found)'
          content:
            application/json:
              schema:
                properties:
                  matches: { type: array, items: { $ref: '#/components/schemas/StoreMatchItemResponse' } }
                  created: { $ref: '#/components/schemas/StoreCreatedResponse' }
                type: object
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/stores/{storeId}/category-order':
    get:
      tags:
        - Stores
      summary: 'Get category order for store'
      operationId: getStoreCategoryOrder
      parameters:
        -
          name: storeId
          in: path
          required: true
      responses:
        200:
          description: 'Category order'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoreCategoryOrderResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      tags:
        - Stores
      summary: 'Set category order in a store'
      operationId: setStoreCategoryOrder
      parameters:
        -
          name: storeId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StoreCategoryOrderRequest'
      responses:
        200:
          description: 'Order saved'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/stores/{storeId}/category-product-order':
    patch:
      tags:
        - Stores
      summary: 'Set product order within category in a store'
      operationId: setStoreCategoryProductOrder
      parameters:
        -
          name: storeId
          in: path
          required: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StoreCategoryProductOrderRequest'
      responses:
        200:
          description: 'Order saved'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'Not a member of any household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/stores/{storeId}/favorite':
    post:
      tags:
        - Stores
      summary: 'Add store to household favorites'
      operationId: addStoreFavorite
      parameters:
        -
          name: storeId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Favorited
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'No household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      tags:
        - Stores
      summary: 'Remove store from household favorites'
      operationId: removeStoreFavorite
      parameters:
        -
          name: storeId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        200:
          description: Removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        403:
          description: 'No household'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /version:
    get:
      tags:
        - System
      summary: 'Get API version info'
      operationId: getVersion
      responses:
        200:
          description: 'Version info'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VersionResponse'
  /health:
    get:
      tags:
        - System
      summary: 'Health check'
      operationId: getHealth
      responses:
        200:
          description: 'Health status'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
  /units:
    get:
      tags:
        - Recipes
      summary: 'Get available ingredient units (no auth required)'
      operationId: listUnits
      responses:
        200:
          description: 'List of units'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnitsResponse'
  /auth/register:
    post:
      tags:
        - Auth
      summary: 'Register new user'
      operationId: userRegister
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserRegisterRequest'
      responses:
        201:
          description: Registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        409:
          description: 'Email already registered'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /auth/login:
    post:
      tags:
        - Auth
      summary: 'Login with email and password'
      operationId: userLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserLoginRequest'
      responses:
        200:
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        401:
          description: 'Invalid credentials'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /auth/google:
    post:
      tags:
        - Auth
      summary: 'Login with Google ID token'
      operationId: googleLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GoogleLoginRequest'
      responses:
        200:
          description: Authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        401:
          description: 'Invalid Google token'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        503:
          description: 'Google OAuth not configured'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /auth/refresh:
    post:
      tags:
        - Auth
      summary: 'Refresh access token'
      operationId: refreshToken
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshRequest'
      responses:
        200:
          description: 'New tokens'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        401:
          description: 'Invalid or expired refresh token'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /auth/change-password:
    post:
      tags:
        - Auth
      summary: 'Change user password'
      operationId: changePassword
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChangePasswordRequest'
      responses:
        200:
          description: 'Password changed'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        401:
          description: 'Unauthorized or wrong password'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /user/cravings:
    get:
      tags:
        - Recipes
      summary: 'Get craving queue for current user'
      operationId: getCravings
      responses:
        200:
          description: 'Craving recipe IDs'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CravingResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    post:
      tags:
        - Recipes
      summary: 'Add recipe to craving queue'
      operationId: addCraving
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AddCravingRequest'
      responses:
        201:
          description: Added
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyResponse'
        400:
          description: 'Bad request'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  '/user/cravings/{recipeId}':
    delete:
      tags:
        - Recipes
      summary: 'Remove recipe from craving queue'
      operationId: removeCraving
      parameters:
        -
          name: recipeId
          in: path
          required: true
      responses:
        204:
          description: Removed
        401:
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    AbsenceResponse:
      properties:
        absent:
          type: boolean
      type: object
    AddCravingRequest:
      type: object
    AddRecipeToShoppingListRequest:
      required:
        - recipe_id
      properties:
        recipe_id:
          type: string
          format: uuid
      type: object
    AdminAiSpendingByModel:
      properties:
        model:
          type: string
        total_cost_usd:
          type: number
        total_input_tokens:
          type: integer
        total_output_tokens:
          type: integer
        calls:
          type: integer
      type: object
    AdminAiSpendingByOperation:
      properties:
        operation:
          type: string
        total_cost_usd:
          type: number
        calls:
          type: integer
      type: object
    AdminAiSpendingRecent:
      properties:
        id:
          type: string
        model:
          type: string
        operation:
          type: string
        cost_usd:
          type: number
        created_at:
          type: string
      type: object
    AdminAiSpendingResponse:
      properties:
        totals:
          $ref: '#/components/schemas/AdminAiSpendingTotals'
        by_model:
          type: array
          items:
            $ref: '#/components/schemas/AdminAiSpendingByModel'
        by_operation:
          type: array
          items:
            $ref: '#/components/schemas/AdminAiSpendingByOperation'
        recent:
          type: array
          items:
            $ref: '#/components/schemas/AdminAiSpendingRecent'
        usd_to_czk:
          type: number
      type: object
    AdminAiSpendingTotals:
      properties:
        total_cost_usd:
          type: number
          nullable: true
        total_input_tokens:
          type: integer
          nullable: true
        total_output_tokens:
          type: integer
          nullable: true
        total_calls:
          type: integer
      type: object
    AdminBankRenameRequest:
      properties:
        name:
          type: string
      type: object
    AdminFinanceBankItemResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        country:
          type: string
        codes:
          type: array
          items:
            type: string
      type: object
    AdminGlobalProductItemResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        webalizeSlug:
          type: string
      type: object
    AdminHouseholdResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        members:
          type: array
          items:
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
                nullable: true
              userId:
                type: string
                format: uuid
            type: object
      type: object
    AdminLoginRequest:
      properties:
        email:
          type: string
          format: email
        password:
          type: string
      type: object
    AdminMergeCandidateResponse:
      properties:
        idA:
          type: string
          format: uuid
        nameA:
          type: string
        idB:
          type: string
          format: uuid
        nameB:
          type: string
        similarity:
          type: number
          format: float
      type: object
    AdminMergeSuggestionResponse:
      properties:
        suggestedMaster:
          description: '"A" nebo "B"'
          type: string
        reason:
          type: string
      type: object
    AdminQuickProductResponse:
      properties:
        id:
          type: string
        name:
          type: string
      type: object
    AdminRecipeDetailResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        instructions:
          type: string
          nullable: true
        claudeScore:
          type: integer
          nullable: true
        claudeNote:
          type: string
          nullable: true
        needsRefactor:
          type: boolean
        hasRefactoredDraft:
          type: boolean
      type: object
    AdminRecipeListResponse:
      properties:
        recipes:
          type: array
          items:
            type: object
        total:
          type: integer
        page:
          type: integer
        per_page:
          type: integer
      type: object
    AdminRecipeParseRequest:
      required:
        - text
      properties:
        text:
          description: 'Raw recipe text to parse'
          type: string
        hint:
          description: 'Optional admin hint for re-parsing'
          type: string
        current:
          description: 'Current form state for hint-based re-parsing'
          type: object
      type: object
    AdminRecipeParseResponse:
      properties:
        title:
          type: string
        description:
          type: string
          nullable: true
        prep_time_minutes:
          type: integer
          nullable: true
        calories_per_serving:
          type: integer
          nullable: true
        servings:
          type: integer
          nullable: true
        ingredients:
          type: array
          items:
            type: object
        step_groups:
          type: array
          items:
            type: object
        tags:
          type: object
        call_cost_usd:
          type: number
          format: float
      type: object
    AdminRecipeQueueResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        householdId:
          type: string
          format: uuid
          nullable: true
        claudeScore:
          type: integer
          nullable: true
        claudeNote:
          type: string
          nullable: true
        needsRefactor:
          type: boolean
        claudeEvaluatedAt:
          type: string
          format: date-time
          nullable: true
        hasRefactoredDraft:
          type: boolean
      type: object
    AdminRecipeRefactorResponse:
      properties:
        recipeId:
          type: string
          format: uuid
        originalDescription:
          type: string
        originalInstructions:
          type: string
        refactoredDescription:
          type: string
        refactoredInstructions:
          type: string
      type: object
    AuthResponse:
      properties:
        access_token:
          type: string
          example: eyJhbGc...
        refresh_token:
          type: string
          example: eyJhbGc...
      type: object
    BrandRequest:
      required:
        - name
      properties:
        name:
          type: string
          maxLength: 255
      type: object
    BrandResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        is_locked:
          type: boolean
      type: object
    CategoryListResponse:
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/CategoryResponse'
        total:
          type: integer
        page:
          type: integer
        per_page:
          type: integer
      type: object
    CategoryOrderRequest:
      required:
        - items
      properties:
        items:
          type: array
          items:
            properties:
              category_id:
                type: string
                format: uuid
              fractional_index:
                type: string
            type: object
      type: object
    CategoryProductOrderRequest:
      required:
        - items
      properties:
        items:
          type: array
          items:
            properties:
              product_id:
                type: string
                format: uuid
              fractional_index:
                type: string
            type: object
      type: object
    CategoryRequest:
      properties:
        name:
          type: string
        is_food:
          type: boolean
          nullable: true
      type: object
    CategoryResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        webalize_slug:
          type: string
        household_id:
          type: string
          format: uuid
          nullable: true
        is_food:
          type: boolean
      type: object
    ChangePasswordRequest:
      properties:
        old_password:
          type: string
          example: oldpassword
        new_password:
          type: string
          example: newpassword
      type: object
    CheckoutResponse:
      properties:
        updated_count:
          type: integer
      type: object
    ClaudeModelsResponse:
      type: array
      items:
        properties:
          value:
            type: string
          label:
            type: string
        type: object
    CravingResponse:
      type: object
    DeviceTokenResponse:
      properties:
        token:
          type: string
          format: uuid
      type: object
    DietPlanResponse:
      properties:
        id:
          type: string
          format: uuid
        week_start:
          type: string
          format: date
        target_calories:
          type: integer
        status:
          type: string
        days:
          type: array
          items:
            type: object
      type: object
    DietProfileResponse:
      properties:
        weight_kg:
          type: number
          nullable: true
        height_cm:
          type: integer
          nullable: true
        birth_year:
          type: integer
          nullable: true
        sex:
          type: string
          nullable: true
        activity_level:
          type: string
          nullable: true
        goal:
          type: string
          nullable: true
        deficit_amount:
          type: integer
        food_restrictions:
          type: array
          items:
            type: string
        meal_prep_mode:
          type: boolean
        tdee:
          type: object
          nullable: true
      type: object
    DocumentConsentRequest:
      required:
        - service
        - document
        - version
      properties:
        service:
          type: string
          example: receptoid
        document:
          type: string
          example: terms_of_service
        version:
          type: string
          example: '1.0'
      type: object
    DocumentConsentStatusResponse:
      required:
        - pending
        - accepted
      properties:
        pending:
          type: array
          items:
            properties:
              service:
                type: string
              document:
                type: string
              current_version:
                type: string
              accepted_version:
                type: string
                nullable: true
            type: object
        accepted:
          type: array
          items:
            properties:
              service:
                type: string
              document:
                type: string
              version:
                type: string
              accepted_at:
                type: string
            type: object
      type: object
    DocumentDiffResponse:
      required:
        - service
        - document
        - from_version
        - to_version
        - diff
        - from_content
        - to_content
      properties:
        service:
          type: string
          example: receptoid
        document:
          type: string
          example: terms_of_service
        from_version:
          type: string
          example: '1.0'
        to_version:
          type: string
          example: '1.1'
        diff:
          type: array
          items:
            properties:
              type:
                type: string
              line:
                type: string
            type: object
        from_content:
          type: string
        to_content:
          type: string
      type: object
    DocumentResponse:
      required:
        - service
        - document
        - version
        - content
      properties:
        service:
          type: string
          example: receptoid
        document:
          type: string
          example: terms_of_service
        version:
          type: string
          example: '1.0'
        content:
          description: 'Markdown content'
          type: string
      type: object
    DocumentVersionsResponse:
      required:
        - service
        - document
        - versions
      properties:
        service:
          type: string
          example: receptoid
        document:
          type: string
          example: terms_of_service
        versions:
          type: array
          items:
            type: string
            example: '1.0'
      type: object
    EmptyResponse:
      type: object
    EntriesAddedResponse:
      properties:
        entries_added:
          type: integer
      type: object
    ErrorResponse:
      required:
        - error
      properties:
        error:
          type: string
      type: object
    EventCategoryRequest:
      required:
        - name
      properties:
        name:
          type: string
          maxLength: 100
          example: Narozeniny
      type: object
    EventCategoryResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          maxLength: 100
        sort_order:
          type: integer
      type: object
    EventRequest:
      required:
        - name
        - category_id
        - event_month
        - event_day
      properties:
        name:
          type: string
          maxLength: 255
        category_id:
          type: string
          format: uuid
        event_month:
          type: integer
          maximum: 12
          minimum: 1
        event_day:
          type: integer
          maximum: 31
          minimum: 1
        event_year:
          description: 'null = roční opakování, číslo = jednorázová událost'
          type: integer
          nullable: true
      type: object
    EventResponse:
      properties:
        id:
          type: string
          format: uuid
        household_id:
          type: string
          format: uuid
        category_id:
          type: string
          format: uuid
        category_name:
          type: string
        name:
          type: string
        event_month:
          type: integer
          maximum: 12
          minimum: 1
        event_day:
          type: integer
          maximum: 31
          minimum: 1
        event_year:
          type: integer
          nullable: true
        is_annual:
          type: boolean
        days_until:
          description: 'Počet dní do příští události'
          type: integer
        ordinal:
          description: 'Pořadí výročí (jen pro jednorázové události s rokem)'
          type: integer
          nullable: true
      type: object
    FinanceAccessRequest:
      properties:
        has_finance_access:
          type: boolean
      type: object
    FinanceAccountListResponse:
      properties:
        data:
          type: array
          items:
            type: object
      type: object
    FinanceAccountNameRequest:
      properties:
        name:
          type: string
      type: object
    FinanceAccountRequest:
      properties:
        account_number:
          type: string
        name:
          type: string
      type: object
    FinanceAccountResponse:
      properties:
        id:
          type: string
        iban:
          type: string
        name:
          type: string
        bank:
          type: string
      type: object
    FinanceBankListResponse:
      properties:
        data:
          type: array
          items:
            type: object
      type: object
    FinanceBatchDetailResponse:
      properties:
        id:
          type: string
        filename:
          type: string
        bank:
          type: string
          nullable: true
        status:
          type: string
        transaction_count:
          type: integer
      type: object
    FinanceBatchListResponse:
      properties:
        data:
          type: array
          items:
            type: object
        total:
          type: integer
      type: object
    FinanceBatchUpdateRequest:
      properties:
        is_active:
          type: boolean
      type: object
    FinanceCategoryListResponse:
      properties:
        data:
          type: array
          items:
            type: object
      type: object
    FinanceCategoryMappingRequest:
      properties:
        description:
          type: string
        category_id:
          type: string
          nullable: true
      type: object
    FinanceCategoryRequest:
      properties:
        name:
          type: string
      type: object
    FinanceCategoryResponse:
      properties:
        id:
          type: string
        name:
          type: string
        sort_order:
          type: integer
        mappings:
          type: array
          items:
            type: object
      type: object
    FinanceCounterpartyLabelListResponse:
      properties:
        data:
          type: array
          items:
            type: object
      type: object
    FinanceCounterpartyLabelRequest:
      properties:
        description:
          type: string
        label:
          type: string
          nullable: true
      type: object
    FinanceCounterpartyListResponse:
      properties:
        data:
          type: array
          items:
            type: object
      type: object
    FinanceCounterpartyTypeRequest:
      properties:
        description:
          type: string
        type:
          type: string
          nullable: true
      type: object
    FinanceImportResponse:
      properties:
        results:
          type: array
          items:
            type: object
      type: object
    FinanceIncomeBreakdownResponse:
      properties:
        months:
          type: array
          items:
            type: object
      type: object
    FinanceOkResponse:
      properties:
        ok:
          type: boolean
      type: object
    FinanceStatsResponse:
      properties:
        transaction_count:
          type: integer
        date_from:
          type: string
          nullable: true
        date_to:
          type: string
          nullable: true
        banks:
          type: array
          items:
            type: string
        processing_batch_id:
          type: string
          nullable: true
        avg_monthly_income_12m:
          type: number
      type: object
    FinanceTransactionListResponse:
      properties:
        data:
          type: array
          items:
            type: object
        total:
          type: integer
      type: object
    FoodLogEntryResponse:
      properties:
        id:
          type: string
          format: uuid
        date:
          type: string
          format: date
        meal_type:
          type: string
        description:
          type: string
        calories:
          type: integer
        protein:
          type: integer
          nullable: true
        fat:
          type: integer
          nullable: true
        carbs:
          type: integer
          nullable: true
        quantity_grams:
          type: integer
          nullable: true
      type: object
    FoodLogParseRequest:
      required:
        - text
      properties:
        text:
          description: 'Free-text food description'
          type: string
      type: object
    FoodLogSaveRequest:
      required:
        - date
        - meal_type
        - items
      properties:
        date:
          type: string
          format: date
        meal_type:
          type: string
        items:
          type: array
          items:
            properties:
              description:
                type: string
              calories:
                type: integer
              protein:
                type: integer
                nullable: true
              fat:
                type: integer
                nullable: true
              carbs:
                type: integer
                nullable: true
              quantity_grams:
                type: integer
                nullable: true
            type: object
      type: object
    FoodLogSummaryResponse:
      properties:
        date:
          type: string
          format: date
        total_calories:
          type: integer
        total_protein:
          type: integer
        total_fat:
          type: integer
        total_carbs:
          type: integer
      type: object
    GenerateShoppingListRequest:
      properties:
        list_id:
          type: string
          format: uuid
        from:
          type: string
          format: date
        to:
          type: string
          format: date
      type: object
    GoogleLoginRequest:
      properties:
        id_token:
          type: string
          example: eyJhbGc...
      type: object
    HealthResponse:
      properties:
        status:
          type: string
          example: ok
      type: object
    HouseholdRequest:
      properties:
        name:
          type: string
        admin_email:
          type: string
          format: email
          nullable: true
      type: object
    HouseholdResponse:
      properties:
        household_id:
          type: string
          format: uuid
        name:
          type: string
      type: object
    InviteCodeRequest:
      properties:
        code:
          type: string
      type: object
    InviteCodeResponse:
      properties:
        code:
          type: string
        expires_at:
          type: string
          format: date-time
      type: object
    ItemsAddedResponse:
      properties:
        items_added:
          type: integer
      type: object
    MarkNameRequest:
      required:
        - name
      properties:
        name:
          type: string
          example: Josef
      type: object
    MealPlanConfirmResponse:
      properties:
        confirmed:
          type: boolean
      type: object
    MealPlanEntryRequest:
      properties:
        slot_id:
          type: string
          format: uuid
        recipe_id:
          type: string
          format: uuid
        plan_date:
          type: string
          format: date
      type: object
    MealPlanEntryResponse:
      properties:
        id:
          type: string
          format: uuid
        slot_id:
          type: string
          format: uuid
        recipe_id:
          type: string
          format: uuid
        plan_date:
          type: string
          format: date
      type: object
    MealPlanSlotResponse:
      properties:
        slot_id:
          type: string
          format: uuid
        slot_name:
          type: string
        sort_order:
          type: integer
        entries:
          type: array
          items:
            type: object
      type: object
    MealSlotRequest:
      properties:
        name:
          type: string
        sort_order:
          type: integer
          nullable: true
        allowed_meal_types:
          type: array
          items:
            type: string
          nullable: true
      type: object
    MealSlotResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        sort_order:
          type: integer
        is_system:
          type: boolean
        allowed_meal_types:
          type: array
          items:
            type: string
      type: object
    MemberPresenceRequest:
      properties:
        member_id:
          type: string
          format: uuid
      type: object
    MemberPresenceResponse:
      properties:
        date:
          type: string
          format: date
        absent_member_ids:
          type: array
          items:
            type: string
            format: uuid
      type: object
    MemberProfileRequest:
      properties:
        display_name:
          type: string
          nullable: true
        allergens:
          type: array
          items:
            type: string
        avoided_foods:
          type: array
          items:
            type: string
      type: object
    MemberProfileResponse:
      properties:
        member_id:
          type: string
          format: uuid
        display_name:
          type: string
          nullable: true
        allergens:
          type: array
          items:
            type: string
        avoided_foods:
          type: array
          items:
            type: string
      type: object
    MemberResponse:
      properties:
        member_id:
          type: string
          format: uuid
        household_id:
          type: string
          format: uuid
        household_name:
          type: string
        display_name:
          type: string
          nullable: true
        avatar_url:
          type: string
          nullable: true
        allergens:
          type: array
          items:
            type: string
        avoided_foods:
          type: array
          items:
            type: string
        joined_at:
          type: string
          format: date-time
      type: object
    NameDayEntryResponse:
      properties:
        month:
          type: integer
          example: 3
        day:
          type: integer
          example: 19
        names:
          type: array
          items:
            $ref: '#/components/schemas/NameDayNameResponse'
      type: object
    NameDayNameResponse:
      properties:
        name:
          type: string
          example: Josef
        gender:
          type: string
          enum:
            - M
            - F
          example: M
        is_marked_personal:
          type: boolean
      type: object
    NameDaySearchResultResponse:
      properties:
        name:
          type: string
          example: Štěpán
        gender:
          type: string
          enum:
            - M
            - F
          example: M
        month:
          type: integer
          example: 9
        day:
          type: integer
          example: 28
        is_marked_personal:
          type: boolean
        is_marked_household:
          type: boolean
      type: object
    ProductAllergenRequest:
      properties:
        allergens:
          type: array
          items:
            type: string
      type: object
    ProductBrandRequest:
      properties:
        brand_id:
          type: string
          format: uuid
          nullable: true
      type: object
    ProductFoodFlagRequest:
      properties:
        is_food:
          type: boolean
      type: object
    ProductListResponse:
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/ProductResponse'
        total:
          type: integer
        page:
          type: integer
        per_page:
          type: integer
      type: object
    ProductRequest:
      properties:
        name:
          type: string
        category_ids:
          type: array
          items:
            type: string
            format: uuid
      type: object
    ProductResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        webalize_slug:
          type: string
        household_id:
          type: string
          format: uuid
          nullable: true
        allergens:
          type: array
          items:
            type: string
        is_food:
          type: boolean
        brands:
          type: array
          items:
            $ref: '#/components/schemas/BrandResponse'
        categories:
          type: array
          items:
            $ref: '#/components/schemas/CategoryResponse'
        variants:
          type: array
          items:
            $ref: '#/components/schemas/ProductVariantResponse'
      type: object
    ProductSuggestedUnitResponse:
      properties:
        unit:
          type: string
          nullable: true
      type: object
    ProductVariantRequest:
      required:
        - name
      properties:
        name:
          type: string
          maxLength: 100
        is_default:
          type: boolean
          nullable: true
      type: object
    ProductVariantResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        is_default:
          type: boolean
        is_locked:
          type: boolean
      type: object
    ProposalCreateRequest:
      properties:
        title:
          type: string
        description:
          type: string
          nullable: true
        instructions:
          type: string
          nullable: true
        ingredients:
          description: mixed>>|null
          type: array
          items:
            type: object
          nullable: true
        step_groups:
          description: mixed>>|null
          type: array
          items:
            type: object
          nullable: true
        servings:
          type: integer
          nullable: true
        prep_time_minutes:
          type: integer
          nullable: true
        season_from:
          type: integer
          nullable: true
        season_to:
          type: integer
          nullable: true
      type: object
    ProposalCreateResponse:
      properties:
        id:
          type: string
          format: uuid
      type: object
    RecipeDetailResponse:
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type: string
          nullable: true
        photo_url:
          type: string
          nullable: true
        total_time_minutes:
          type: integer
          nullable: true
        active_time_minutes:
          type: integer
          nullable: true
        calories_per_serving:
          type: integer
          nullable: true
        protein_per_serving:
          type: integer
          nullable: true
        fat_per_serving:
          type: integer
          nullable: true
        carbs_per_serving:
          type: integer
          nullable: true
        servings:
          type: integer
          nullable: true
        season_from:
          type: integer
          nullable: true
        season_to:
          type: integer
          nullable: true
        household_id:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        ingredients:
          type: array
          items:
            properties:
              product_id:
                type: string
                format: uuid
              product_name:
                type: string
              quantity:
                type: number
                format: float
              unit:
                type: string
                nullable: true
              variant_id:
                type: string
                format: uuid
                nullable: true
              variant_name:
                type: string
                nullable: true
            type: object
        visibility:
          type: string
        pending_approval:
          type: boolean
        rejection_reason:
          type: string
          nullable: true
        is_locked:
          type: boolean
        is_author:
          type: boolean
        authors:
          description: 'User IDs of authors'
          type: array
          items:
            type: string
        step_groups:
          type: array
          items:
            $ref: '#/components/schemas/RecipeStepGroupSchema'
        variant_group_id:
          type: string
          format: uuid
          nullable: true
        is_variant_master:
          type: boolean
        variants:
          type: array
          items:
            $ref: '#/components/schemas/RecipeVariantResponse'
      type: object
    RecipeFeedbackApplyRequest:
      required:
        - recipe
        - answers
      properties:
        recipe:
          description: 'Current recipe state'
          type: object
        answers:
          description: 'Map of question_id to answer'
          type: object
        model:
          description: 'Claude model override'
          type: string
      type: object
    RecipeFeedbackApplyResponse:
      properties:
        title:
          type: string
        description:
          type: string
          nullable: true
        servings:
          type: integer
          nullable: true
        prep_time_minutes:
          type: integer
          nullable: true
        tags:
          type: object
        step_groups:
          type: array
          items:
            type: object
        call_cost_usd:
          type: number
          format: float
      type: object
    RecipeFeedbackRequest:
      required:
        - recipe
      properties:
        recipe:
          description: 'Current recipe state'
          type: object
        model:
          description: 'Claude model override'
          type: string
      type: object
    RecipeFeedbackResponse:
      properties:
        questions:
          type: array
          items:
            type: object
        call_cost_usd:
          type: number
          format: float
      type: object
    RecipeIngredientRequest:
      required:
        - product_id
        - quantity
      properties:
        product_id:
          type: string
          format: uuid
        quantity:
          type: number
          format: float
        unit:
          type: string
          nullable: true
      type: object
    RecipeProposalDiffResponse:
      properties:
        proposal_id:
          type: string
          format: uuid
        recipe_id:
          type: string
          format: uuid
        original_title:
          type: string
        original_description:
          type: string
        original_instructions:
          type: string
        original_ingredients:
          type: array
          items:
            type: object
          nullable: true
        original_step_groups:
          type: array
          items:
            type: object
          nullable: true
        original_servings:
          type: integer
          nullable: true
        original_prep_time_minutes:
          type: integer
          nullable: true
        original_season_from:
          type: integer
          nullable: true
        original_season_to:
          type: integer
          nullable: true
        proposed_title:
          type: string
        proposed_description:
          type: string
        proposed_instructions:
          type: string
        proposed_ingredients:
          type: array
          items:
            type: object
          nullable: true
        proposed_step_groups:
          type: array
          items:
            type: object
          nullable: true
        proposed_servings:
          type: integer
          nullable: true
        proposed_prep_time_minutes:
          type: integer
          nullable: true
        proposed_season_from:
          type: integer
          nullable: true
        proposed_season_to:
          type: integer
          nullable: true
        proposer_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
      type: object
    RecipeProposalResponse:
      properties:
        id:
          type: string
          format: uuid
        recipe_id:
          type: string
          format: uuid
        proposer_id:
          type: string
          format: uuid
        proposed_title:
          type: string
        status:
          type: string
        created_at:
          type: string
          format: date-time
      type: object
    RecipeReportRequest:
      properties:
        message:
          type: string
          example: 'Chybí ingredience máslo'
        email:
          type: string
          example: jan@example.com
          nullable: true
      type: object
    RecipeReportResponse:
      type: array
      items:
        properties:
          id:
            type: string
            format: uuid
          recipe_id:
            type: string
            format: uuid
          recipe_title:
            type: string
            nullable: true
          recipe_slug:
            type: string
            nullable: true
          message:
            type: string
          reporter_email:
            type: string
            nullable: true
          ip_address:
            type: string
          created_at:
            type: string
            format: date-time
        type: object
    RecipeRequest:
      properties:
        title:
          type: string
        description:
          type: string
          nullable: true
        photo_url:
          type: string
          nullable: true
        calories_per_serving:
          type: integer
          nullable: true
        protein_per_serving:
          type: integer
          nullable: true
        fat_per_serving:
          type: integer
          nullable: true
        carbs_per_serving:
          type: integer
          nullable: true
        servings:
          type: integer
          nullable: true
        season_from:
          type: integer
          nullable: true
        season_to:
          type: integer
          nullable: true
        meal_type_tags:
          type: array
          items:
            type: string
          nullable: true
        dietary_tags:
          type: array
          items:
            type: string
          nullable: true
        cooking_method_tags:
          type: array
          items:
            type: string
          nullable: true
        household_id:
          description: 'Share to household on creation'
          type: string
          format: uuid
          nullable: true
        visibility:
          type: string
          enum:
            - public
            - shared
            - private
          nullable: true
        step_groups:
          type: array
          items:
            $ref: '#/components/schemas/RecipeStepGroupSchema'
          nullable: true
        ingredients:
          type: array
          items:
            $ref: '#/components/schemas/RecipeIngredientRequest'
          nullable: true
      type: object
    RecipeResponse:
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type: string
          nullable: true
        photo_url:
          type: string
          nullable: true
        total_time_minutes:
          type: integer
          nullable: true
        calories_per_serving:
          type: integer
          nullable: true
        protein_per_serving:
          type: integer
          nullable: true
        fat_per_serving:
          type: integer
          nullable: true
        carbs_per_serving:
          type: integer
          nullable: true
        servings:
          type: integer
          nullable: true
        season_from:
          type: integer
          nullable: true
        season_to:
          type: integer
          nullable: true
        household_id:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        meal_type_tags:
          type: array
          items:
            type: string
        dietary_tags:
          type: array
          items:
            type: string
        cooking_method_tags:
          type: array
          items:
            type: string
        visibility:
          type: string
        authors:
          description: 'User IDs of authors'
          type: array
          items:
            type: string
      type: object
    RecipeStepGroupSchema:
      properties:
        id:
          type: string
        name:
          type: string
          nullable: true
        fractional_index:
          type: string
        steps:
          type: array
          items:
            properties:
              id:
                type: string
              text:
                type: string
              time_minutes:
                type: integer
                nullable: true
              is_active:
                type: boolean
              fractional_index:
                type: string
            type: object
      type: object
    RecipeSuggestionResponse:
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        photo_url:
          type: string
          nullable: true
        prep_time_minutes:
          type: integer
          nullable: true
        season_from:
          type: integer
          nullable: true
        season_to:
          type: integer
          nullable: true
        last_cooked:
          type: string
          nullable: true
        days_since_cooked:
          type: number
          nullable: true
      type: object
    RecipeTagLabeledItem:
      properties:
        value:
          type: string
        label:
          type: string
      type: object
    RecipeTagsResponse:
      properties:
        meal_type:
          type: array
          items:
            type: string
        dietary:
          type: array
          items:
            type: string
        cooking_method:
          type: array
          items:
            type: string
      type: object
    RecipeVariantResponse:
      properties:
        id:
          type: string
        title:
          type: string
        is_master:
          type: boolean
        differing_ingredients:
          description: 'Ingredient names present in this variant but not in the current recipe'
          type: array
          items:
            type: string
      type: object
    RefreshRequest:
      properties:
        refresh_token:
          type: string
          example: eyJhbGc...
      type: object
    SendToMealPlanRequest:
      properties:
        household_id:
          type: string
          format: uuid
      type: object
    ShoppingListDetailResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        household_id:
          type: string
          format: uuid
        store_id:
          type: string
          format: uuid
          nullable: true
        stores:
          type: array
          items:
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
            type: object
        items:
          type: array
          items:
            $ref: '#/components/schemas/ShoppingListItemResponse'
      type: object
    ShoppingListItemRequest:
      properties:
        product_id:
          type: string
          format: uuid
        quantity:
          type: number
        unit:
          type: string
          nullable: true
        preferred_store_id:
          type: string
          format: uuid
          nullable: true
      type: object
    ShoppingListItemResponse:
      properties:
        id:
          type: string
          format: uuid
        product_id:
          type: string
          format: uuid
        quantity:
          type: number
        unit:
          type: string
          nullable: true
        status:
          type: string
        preferred_store_id:
          type: string
          format: uuid
          nullable: true
      type: object
    ShoppingListRequest:
      required:
        - name
        - store_ids
      properties:
        name:
          type: string
        store_ids:
          type: array
          items:
            type: string
            format: uuid
      type: object
    ShoppingListResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        household_id:
          type: string
          format: uuid
        stores:
          type: array
          items:
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
            type: object
        created_at:
          type: string
          format: date-time
        item_count:
          type: integer
      type: object
    StoreCategoryOrderRequest:
      required:
        - items
      properties:
        items:
          type: array
          items:
            properties:
              category_id:
                type: string
                format: uuid
              fractional_index:
                type: string
            type: object
      type: object
    StoreCategoryOrderResponse:
      properties:
        items:
          type: array
          items:
            properties:
              category_id:
                type: string
                format: uuid
              fractional_index:
                type: string
              category_name:
                type: string
            type: object
      type: object
    StoreCategoryProductOrderRequest:
      required:
        - items
      properties:
        items:
          type: array
          items:
            properties:
              category_id:
                type: string
                format: uuid
              product_id:
                type: string
                format: uuid
              fractional_index:
                type: string
            type: object
      type: object
    StoreCreatedResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
      type: object
    StoreListResponse:
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/StoreResponse'
        total:
          type: integer
        page:
          type: integer
        per_page:
          type: integer
      type: object
    StoreMatchItemResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        sim:
          type: number
          format: float
      type: object
    StoreRequest:
      properties:
        name:
          type: string
        street:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        zip:
          type: string
          nullable: true
      type: object
    StoreResponse:
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        street:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        zip:
          type: string
          nullable: true
        webalize_slug:
          type: string
        household_id:
          type: string
          format: uuid
          nullable: true
        is_favorited:
          type: boolean
      type: object
    TokenResponse:
      properties:
        token:
          type: string
      type: object
    UnitsResponse:
      properties:
        units:
          type: array
          items:
            properties:
              value:
                type: string
              label:
                type: string
            type: object
      type: object
    UserLoginRequest:
      properties:
        email:
          type: string
          example: user@example.com
        password:
          type: string
          example: secret123
      type: object
    UserProfileResponse:
      properties:
        first_name:
          type: string
        last_name:
          type: string
        is_premium:
          type: boolean
        premium_until:
          type: string
          nullable: true
        memberships:
          type: array
          items:
            $ref: '#/components/schemas/MemberResponse'
      type: object
    UserRegisterRequest:
      properties:
        email:
          type: string
          example: user@example.com
        password:
          type: string
          example: secret123
        first_name:
          type: string
          example: Jan
        last_name:
          type: string
          example: Novak
        username:
          type: string
          example: jannovak
          nullable: true
      type: object
    VersionResponse:
      properties:
        min_version:
          type: string
          example: 1.0.0
        latest_version:
          type: string
          example: 1.0.0
      type: object
    WeightLogResponse:
      properties:
        weight_kg:
          type: number
        date:
          type: string
          format: date
      type: object
    PaginationData:
      description: 'Calculated page values.'
      properties:
        page:
          type: integer
        pageCount:
          type: integer
        firstPage:
          type: integer
        lastPage:
          type: integer
        itemsPerPage:
          type: integer
        itemCount:
          type: integer
        hasNextPage:
          type: boolean
        hasPreviousPage:
          type: boolean
        firstItemIndex:
          type: integer
        lastItemIndex:
          type: integer
      type: object
tags:
  -
    name: Auth
  -
    name: Household
  -
    name: Members
  -
    name: Recipes
  -
    name: 'Meal Plan'
  -
    name: 'Meal Slots'
  -
    name: Shopping
  -
    name: Products
  -
    name: Categories
  -
    name: Stores
  -
    name: Brands
  -
    name: NameDays
  -
    name: Events
  -
    name: Finance
  -
    name: Diet
  -
    name: Documents
  -
    name: System
  -
    name: 'Admin Recipes'
    description: 'Recipe management (admin)'
  -
    name: 'Admin Products'
    description: 'Product merge/management (admin)'
  -
    name: 'Admin Stores'
    description: 'Store merge/management (admin)'
  -
    name: 'Admin Households'
    description: 'Household management (admin)'
  -
    name: 'Admin Finance'
    description: 'Finance bank management (admin)'
  -
    name: 'Admin AI'
    description: 'AI spending overview (admin)'
